Dangerous PHP sinks reference
TL;DR: Reference list of PHP function families that turn tainted strings into RCE, LFI/RFI, command exec, SQLi or object instantiation. Grep these first when auditing — most PHP CVEs land in one of these buckets.
What it is
A “sink” is a function whose output crosses a trust boundary — e.g. into the OS, the interpreter, the database, or the file system. PHP ships with a large dangerous-function surface, much of it inherited from PHP4 days. Grepping for these names + checking whether arguments are user-influenced is the fastest path to bugs.
Preconditions / where it applies
- PHP source code (any version — many sinks predate PHP 5)
- Tainted variables —
$_GET,$_POST,$_COOKIE,$_REQUEST,$_SERVER(headers), uploaded files, DB rows previously populated by users - Framework helpers that wrap the same sinks (Laravel
Process::run, SymfonyProcess, CodeIgniterevalhelpers)
Technique
Grep aggressively, then triage by sink class.
Code execution — instant RCE if any arg is tainted:
1
2
eval | assert | create_function | preg_replace .*/e (PHP < 7)
mb_ereg_replace .*/e | mb_eregi_replace .*/e
OS command execution — RCE via shell:
1
2
system | exec | shell_exec | passthru | popen | proc_open
backtick operator `...` | pcntl_exec
File inclusion — RCE via LFI/RFI, log poisoning, phar://:
1
include | include_once | require | require_once
Deserialisation — POP-chain RCE if a gadget exists (see php-deserialization-gadgets):
1
2
unserialize | Phar:: (auto-deserialises metadata on file-stat sinks)
file_exists / file_get_contents / fopen / md5_file / filemtime on phar:// URL
SQL — string-concat queries hit mysqli_query | mysql_query | PDO->query | pg_query. Parameterised prepare/execute is safe; concatenated prepare is not.
Filesystem read/write — arbitrary read/write:
1
2
fopen | file_get_contents | file_put_contents | readfile | move_uploaded_file
copy | rename | unlink | chmod | symlink
Header / response splitting: header() with \r\n in user input on PHP < 5.1.2; setcookie() similarly.
Variable injection — overwrites locals, often leads to auth bypass:
1
2
3
extract($_GET); // $_GET[is_admin]=1 wins
parse_str($qs); // same primitive
import_request_variables // legacy
Mail / SMTP injection — mail($to, $subject, $body, $headers) with tainted $headers allows CRLF injection of extra recipients and arbitrary -X sendmail flags via 5th arg.
XXE — simplexml_load_string|DOMDocument::loadXML|libxml_disable_entity_loader(false) enables external entities.
Detection and defence
- WAFs see common payloads (
;id;,php://input,data://) — log and alert on these strings hitting params disable_functionsinphp.inifor the worst sinks (evalcannot be disabled — needs Suhosin/Snuffleupagus)allow_url_include=Off,allow_url_fopen=Offkills RFI andphar://-over-HTTP- Open_basedir restricts filesystem sinks
- Use prepared statements,
escapeshellarg(notescapeshellcmd),is_uploaded_file, and allowlist file extensions
References
- PHP manual — disable_functions — official sink disabling list
- HackTricks — PHP RCE function list — function-by-function notes
- PayloadsAllTheThings — PHP injection — payload corpus
- OWASP Code Review Guide — PHP — review checklist