02 - Argument Parsing for OSWE PoCs - argparse vs dotenv
Designing explicit, discoverable configuration for exploit code using argparse and evaluating environment-based alternatives.
This article builds directly on the project structure established in Part 01 and focuses on how configuration is supplied to the PoC at runtime.
Python's argparse module is the standard way to build command-line interfaces (CLI). For building a PoC exploit script, one can control how inputs like targets, proxies, etc... are handled. By implementing a comprehensive design, the need to constantly re-write scripts between runs and between labs is eliminated. Generally, knowing all that might be built into the script is difficult.
How many arguments do you need? Should there be defaults for certain arguments? What arguments will be required? Asking yourself questions like this will guide the shape of your script and the various arguments that are considered. What we're about to do isn't written in stone. Don't be afraid to remove items that don't make sense to your or to add ones that do. It's your skeleton to construct as you please. With that said, let's look at a basic command that we might build using argparse and dotenv.
As an alternative, some projects use an environment file to store configuration values. This approach can reduce command length and centralize configuration, which can be appealing when iterating quickly.
Below is an example .env file containing the same types of values we would otherwise pass via the command line. The goal here is not to build a full configuration system, but to highlight how this approach differs in practice.
uv add python-dotenvTARGET_IP=192.168.122.45
TARGET_PORT=8080
TARGET_API_PORT=5001
LISTENING_IP=10.8.0.5
LISTENING_PORT=9001
PAYLOAD_PORT=9999
DELAY=5
USER_FILE=user.json
SAVE_IDENTITY=output.json
REGISTER=true
COMPLEXITY=high
INCLUDE_ADDRESS=true
INCLUDE_PHONE=false
CHARSET=ascii
PROXY=http://127.0.0.1:8080One major difference between the dotenv and argparse methods is that when using argparse, you have an instant help flag that isn't available with the dotenv method. One can write a help method for a dotenv by adding in code like:
One immediate drawback of the environment file approach is discoverability. Unlike argparse, there is no built-in --help flag. While it’s possible to manually emulate help output, doing so quickly becomes custom, brittle, and repetitive across projects.
For some workflows, environment files are a reasonable choice. However, when developing a reusable PoC skeleton, especially one intended to be run repeatedly, shared, or revisited under time pressure. I found the lack of built-in help and the need for manual type conversion to be limiting. For those reasons, I ultimately preferred an explicit command-line interface built with argparse.
Recall, we have a fictitious uv project in a directory labeled authrise. If you cd into that directory, and use an editor to build a file we'll call poc-args.py. The aim is to start building a workable PoC that we can reuse and doesn't rely on hard-coded values thus providing flexibility to any environment we will end up working in. Here's an example of the beginnings of a simple CLI with arguments.
How would you configure such a script with a simple CLI like the above.
If you consider the content being presented in the case studies, one can figure out what might be good elements to consider for adding into a script as arguments. Some are fairly obvious like the target ip address and port, but some might not be, at least at first glance. Consider what kinds of elements might pop up repeatedly.
A nice feature of argparse is that allows you to group arguments into sections. The add_argument_group method helps facilitate this. The grouping really helps when printing the options if you need a reminder of all the possible elements you can add as input. My grouping broke out into target options, attacker options, identity options, and the aptly named optional options.
For target options, I considered the ip address, port, and a possible api port. I could have expanded this to have a api ip address if I desired. Attacker options centered around my Kali host and how I'd present say a reverse shell port or payload delivery using a web server to the victim. Identity options were configured to make use of my custom identity generator. There are options that allow me to reuse an existing configured user on the web application or set the password complexity, etc...
Once I started seeing the same categories of inputs appear repeatedly across labs, I stopped thinking in terms of individual flags and started thinking in terms of roles. Target details, attacker settings, exploit behavior, and identity data each form a coherent group that benefits from being treated consistently.
The following example represents the point where my argument parsing stabilized. It is intentionally broader than any single lab requires, because its purpose is to be reusable.
At this point, we have a well-defined interface for interacting with the PoC. The script can be configured explicitly, arguments are discoverable, and related options are grouped in a way that reflects how exploits are actually built and operated.
What we do not yet have is a clean way to pass this configuration through the rest of the codebase without threading argument objects everywhere. In the next article, we’ll address that by introducing a structured context object using Python dataclasses, which will allow us to centralize state and simplify the logic of multi-stage exploits.
Next:
03 - Context Management with Dataclasses
In the next article, we’ll introduce a structured context object to carry configuration and runtime state through the exploit cleanly, without passing argument objects through every function.
Last updated