Command Injection
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches' (d5). The term's detection_hints list semgrep, phpstan, and psalm as tools that can catch patterns like shell_exec( or exec( with variable interpolation. These are specialist SAST tools rather than default linters, and they catch the common case but may miss indirect data flows or less obvious injection points like image processing CLI calls.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates using escapeshellarg() on every argument or preferring PHP-native functions. This is typically a targeted fix within one component — replacing shell_exec calls with native PHP equivalents or wrapping arguments — but may require touching multiple call sites within a file or small set of files.
Closest to 'localised tax' (b3). Command injection vulnerabilities are typically localised to specific code paths that invoke shell commands. The fix doesn't require architectural rework — it's about securing the specific call sites. The applies_to scope (web, cli contexts) is broad, but the burden falls only on code that actually uses shell execution, not the entire codebase.
Closest to 'notable trap' (t5). The misconception field explicitly states that developers believe escapeshellarg() makes all shell calls safe, when it only secures arguments — not the command name itself. This is a documented gotcha that most security-aware devs eventually learn, but it contradicts the intuitive assumption that 'escaping = safe'. The common_mistakes also highlight traps like injection via argument structure even after escaping.
Also Known As
TL;DR
Explanation
Command injection occurs when user-controlled data is incorporated into a shell command without sanitisation. Attackers can append additional commands using shell metacharacters (;, |, &&) to run arbitrary programs with the web server's privileges — reading files, exfiltrating data, installing backdoors, or pivoting to other systems. Prevention: avoid shell functions entirely where possible; if unavoidable, use escapeshellarg() on every argument and escapeshellcmd() on the command.
How It's Exploited
# Deletes your web root
Diagram
flowchart TD
USER_CMD[User input: filename] --> CONCAT2[exec ls -la . filename .]
CONCAT2 -->|input: . ; rm -rf /| EXEC2[Shell executes:<br/>ls -la .<br/>rm -rf /]
EXEC2 --> DISASTER[Files deleted!]
subgraph Fix
ESCAPE[escapeshellarg wraps in quotes<br/>escapes all shell metacharacters]
VALIDATE[Allowlist valid inputs<br/>reject anything else]
AVOID[Use PHP functions directly<br/>avoid shell when possible]
end
style CONCAT2 fill:#f85149,color:#fff
style DISASTER fill:#f85149,color:#fff
style ESCAPE fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using shell_exec(), system(), or exec() with unsanitised user input even after escapeshellarg().
- Passing user-controlled arguments where escapeshellarg() is applied to the whole string but injection happens via argument structure.
- Using PHP functions that invoke a shell implicitly — preg_replace with /e modifier (removed in PHP 7), or older mail() with fifth argument.
- Not considering that command injection can occur in less obvious places like image processing CLI calls.
Code Examples
$file = $_POST['filename'];
exec("convert $file output.pdf");
// escapeshellarg wraps in single quotes and escapes embedded quotes
$file = escapeshellarg($_POST['filename']);
exec('convert ' . $file . ' output.pdf');
// Better: avoid shell entirely — use proc_open with an array
proc_open(['convert', $file, 'output.pdf'], $descriptors, $pipes);