PHP Command Injection Vulnerabilities: Analysis and Prevention
Command injection vulnerabilities occur when an application passes unsafe user input directly to a shell command interpreter. This allows an attacker to execute arbitrary commands on the host operating system, typically with the privileges of the vulnerable application.
A Basic Example of a Vulnerability
Consider the following vulnerable code:
<?php
$hostname = $_GET['host'];
$result = shell_exec('ping ' . $hostname);
echo "<pre>$result</pre>";
?>
When a user requests vuln.php?host=127.0.0.1, the executed command is ping 127.0.0.1. However, an attacker can submit vuln.php?host=127.0.0.1; whoami. This concatenates the user input directly, resulting in the shell executing ping 127.0.0.1; whoami. The semicolon acts as a command separator, causing the whoami command to run after the ping completes, revealing the server's process owner.
PHP Functions that Execute Shell Commands
Several PHP functions can be vectors for command injection if user input is past to them unsafely:
system(): Executes a command and outputs the result directly.exec(): Executes a command and returns the last line of output; full output can be captured in an optional array parameter.shell_exec(): Executes a command via the shell and returns the complete output as a string.passthru(): Executes a command and passes raw output directly to the browser.popen()/proc_open(): Execute a command and return a file pointer for reading/writing.- Backticks (
`): An operator that executes shell commands and returns the output, functionally identical toshell_exec().
<?php
// Examples of potentially dangerous usage
// $userInput = $_GET['cmd'];
// system($userInput); // Dangerous!
// echo shell_exec('dir ' . $userInput); // Dangerous!
?>
Command Injection Techniques and Bypasses
Attackers use shell metacharacters to break out of the intended command context and inject new commands. The specific characters usable depend on the underlying operating system (Windows vs. Linux/Unix).
Linux/Unix Shell Metacharacters:
;: Command separator. Runs commands sequential (cmd1; cmd2).|: Pipe. Takes the output ofcmd1as input tocmd2(cmd1 | cmd2).&: Background operator. Runscmd1in the background and immediately proceeds tocmd2.&&: Logical AND. Executescmd2only ifcmd1succeeds.||: Logical OR. Executescmd2only ifcmd1fails.`or$(): Command substitution. Executes a command inside and uses its output.
Windows Command Shell Metacharacters:
&: Command separator (cmd1 & cmd2).|: Pipe (cmd1 | cmd2).||: Executescmd2only ifcmd1fails.&&: Executescmd2only ifcmd1succeeds.
Bypass techniques often involve using alternative characters not filtered by the application, encoding, or abusing argument injection.
Preventing Command Injection Vulnerabilities
The most secure approach is to avoid passing user input to shell functions altogether. When unavoidable, rigorous validation and escaping are required.
Primary Defence: Input Validation and Sanitization
- Use Whitelists Over Blacklists: Define a strict pattern of allowed characters. For example, for an IP address input, verify it matches the correct numeric format.
<?php $inputIp = $_GET['ip']; // Validate it is a valid IPv4 address if (filter_var($inputIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // Input is safe for use in the ping command context $output = shell_exec('ping -c 4 ' . $inputIp); echo "<pre>$output</pre>"; } else { echo "Invalid IP address provided."; } ?> - Use Built-in Escaping Functions: PHP provides functions to escape shell arguments and commands.
escapeshellarg(): Adds single quotes around a string and escapes any existing single quotes. It ensures the string is treated as a single, safe argument.escapeshellcmd(): Escapes shell metacharacters within a string, such as;,|,&, etc. Crucially,escapeshellcmd()is not sufficient on its own when user input forms part of the command string. The correct practice is to useescapeshellarg()on each user-supplied argument.
<?php $userInput = $_GET['filename']; // e.g., 'file.txt; cat /etc/passwd' // INCORRECT: Command is still vulnerable // $command = 'cat ' . escapeshellcmd($userInput); // 'cat file.txt\; cat /etc/passwd' // CORRECT: Argument is properly quoted $safeArgument = escapeshellarg($userInput); // Results in '\'file.txt; cat /etc/passwd\'' $command = 'cat ' . $safeArgument; // Command is: cat 'file.txt; cat /etc/passwd' $output = shell_exec($command); // Safely tries to cat a file with a semicolon in its name. echo "<pre>$output</pre>"; ?>
Secondary Defenses:
- Run the web server process with the minimum necessary privileges (e.g., a non-root user).
- Disable dangerous PHP functions (
system,exec,passthru,shell_exec,popen,proc_open,pcntl_exec) inphp.iniusing thedisable_functionsdirective if they are not required. - Implement proper output encoding when displaying command results to prevent cross-site scripting (XSS) from any malicious output.
By combining strict input validation, proper argument escaping with escapeshellarg(), and principle of least privilege, the risk of command injection can be effectively mitigated.