Command Injection

Command Injection and escapeshellarg

When PHP shells out with exec(), system(), shell_exec() or passthru(), any untrusted value spliced into the command is a command-injection hole: metacharacters such as ;, |, && and $( ) let the attacker append their own commands. Avoid the shell where you can; otherwise validate each argument and wrap it in escapeshellarg().

cmd.php: a raw command (for learning) and the escaped versionPHP
$host = $_GET['host'] ?? '127.0.0.1';
if (($_GET['mode'] ?? '') === 'safe') {
    if (!preg_match('/^[a-z0-9.-]+$/i', $host)) exit("bad host\n");        // validate
    echo shell_exec('ping -c1 -W1 ' . escapeshellarg($host) . ' 2>&1 | head -1');
} else {
    echo shell_exec('ping -c1 -W1 ' . $host . ' 2>&1 | head -1');          // splices raw input
}

Passing the host value 127.0.0.1; id to each branch with curl 3,008 gives:

Output of 186
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
...
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bad host

The ; id ran after ping as a separate command because the shell treats the semicolon as a separator; escapeshellarg() would have quoted it into one harmless argument, and validation rejected it first. It protects one argument; the weaker escapeshellcmd() escapes a whole command yet still lets the attacker add arguments. Best of all, skip the shell with proc_open() and an argument array.