VNX-PHP-002 – Dangerous Function in PHP
Overview
This rule flags any .php file that calls one of PHP’s command-execution or code-evaluation functions: eval(), exec(), system(), passthru(), shell_exec(), popen(), and proc_open(). When the argument to any of these functions includes attacker-controlled data, the result is remote code execution (RCE) on the server.
Severity: High | CWE: CWE-78 – Improper Neutralization of Special Elements used in an OS Command | CAPEC: CAPEC-88 | ATT&CK: T1059
PHP default behavior: All of these functions are enabled by default in a standard PHP installation. The
disable_functionsdirective inphp.iniis empty by default. Production environments that do not use these functions should explicitly disable them.
Why This Matters
When an attacker controls the argument to one of these functions, they gain the ability to run any OS command with the privileges of the web server process — typically www-data or apache on Linux. From there they can:
- Read
/etc/passwd,/proc/self/environ,.envfiles, and database credentials - Write a PHP web shell for persistent access
- Exfiltrate data over HTTP or DNS
- Pivot to other services on the internal network
eval() accepts a full PHP string, allowing an attacker to deliver a base64-encoded payload through a form field and execute it without writing any file. Shell functions pass their argument to /bin/sh -c, so shell metacharacters — ;, |, &&, $(), backticks — all work as separators and substitution operators.
OWASP ASVS v4.0 mapping: V5.2.4 — Verify that the application does not use eval() or other dynamic code execution features; V5.2.2 — Verify that the application protects against OS command injection.
What Gets Flagged
Any .php line that contains one of the dangerous function names followed by (.
// FLAGGED: unsanitized user input passed to system()
$cmd = $_GET['tool'];
system($cmd);
// FLAGGED: exec() with concatenated user input
exec('ls -la ' . $_POST['dir'], $output);
// FLAGGED: eval() on user-supplied data
eval($_POST['code']);
// FLAGGED: shell_exec() with interpolated variable
$file = $_REQUEST['filename'];
$result = shell_exec("cat $file");
// FLAGGED: passthru() with user value
passthru('/usr/bin/convert ' . $_GET['src'] . ' output.png');
// FLAGGED: proc_open with user-controlled command
proc_open($_POST['cmd'], $descriptors, $pipes);
Remediation
1. Eliminate OS command calls wherever possible. Most tasks developers reach for exec() or system() to accomplish — file conversion, image resizing, PDF generation, archive extraction — have pure-PHP library alternatives that never invoke a shell:
| Task | Shell command to replace | Pure-PHP alternative |
|---|---|---|
| Image resize | convert (ImageMagick) | Imagick extension or GD |
| ZIP creation | zip | ZipArchive |
| PDF generation | wkhtmltopdf | dompdf, TCPDF, FPDF |
| File type detection | file | finfo_open() / mime_content_type() |
2. If you must call a shell command, escape every argument with escapeshellarg().
// SAFE: each argument individually escaped
$filename = escapeshellarg($_GET['filename']);
$output = shell_exec('/usr/bin/file ' . $filename);
escapeshellarg() wraps the value in single quotes and escapes embedded single quotes. Use escapeshellcmd() only on the command path — it is not a substitute for per-argument escaping.
3. Validate input against an allowlist before use.
// SAFE: allowlist + escapeshellarg
$allowed = ['convert', 'resize', 'thumbnail'];
$action = $_GET['action'] ?? '';
if (!in_array($action, $allowed, true)) {
http_response_code(400);
exit('Invalid action');
}
$src = escapeshellarg($_FILES['image']['tmp_name']);
$dest = escapeshellarg('/tmp/output_' . bin2hex(random_bytes(8)) . '.jpg');
exec('/usr/bin/convert ' . $src . ' -resize 200x200 ' . $dest);
4. Never use eval() on external data. There is no safe way to use eval() with user-supplied input. Refactor to a dispatch table (array of closures), a strategy pattern, or a template engine. For user-supplied expressions, use a sandboxed evaluator library.
5. Disable dangerous functions in php.ini for environments that do not need them:
; php.ini — production: disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
Note: eval is a language construct and cannot be disabled via disable_functions. It can only be prevented through code review and static analysis.
6. Run the web server process as a least-privilege user with no write access outside the web root and no access to secrets files.
References
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CAPEC-88: OS Command Injection
- MITRE ATT&CK T1059 – Command and Scripting Interpreter
- OWASP Command Injection
- OWASP PHP Security Cheat Sheet
- OWASP ASVS v4.0 – V5.2 Injection Prevention
- PHP manual: escapeshellarg()
- PHP manual: disable_functions
- PHP manual: exec()