| Code exec | eval($x) | source of $x, any prior filtering |
| Code exec | assert($x) (PHP < 8) | assert can execute strings until PHP 8.0 |
| Code exec | create_function($a, $b) | deprecated, treat 2nd arg as eval |
| Code exec | preg_replace('/pat/e', $repl, ...) | /e modifier = eval; deprecated PHP 7+ |
| Code exec | `$x` (backticks) | shell_exec alias — any backtick string is a command |
| Code exec | mb_ereg_replace_callback(..., $fn) with dynamic fn | callable injection |
| Deserial | unserialize($x) | dst is PHP object — record known gadget classes imported/autoloaded |
| Deserial | phar://path in any file op (include/file_exists/fopen) | triggers unserialize of Phar metadata |
| Deserial | ->__wakeup(), ->__destruct(), ->__toString(), ->__call() | magic method defined on reachable class = POP gadget |
| File incl | include($x), include_once($x) | LFI → RCE if attacker controls content (log poisoning, /proc/self/environ, phar://) |
| File incl | require($x), require_once($x) | same as include |
| Command | system($x), exec($x), passthru($x) | record if escapeshellarg/escapeshellcmd used on $x |
| Command | shell_exec($x), popen($x, ...), proc_open($x, ...) | same |
| Command | pcntl_exec($path, $args) | args array — individual escape not needed but path is |
| SQL | mysql_query($x), mysqli_query($db, $x), $mysqli->query($x) | is $x concatenated from user input? |
| SQL | pg_query($db, $x) | same |
| SQL | $pdo->query($x), $pdo->exec($x) | if query uses concat — SQLi. prepare() with ? / :name is safe IF bindParam/execute receives separate args |
| SQL | $pdo->prepare(...) followed by bindParam with PDO::PARAM_STR but query concatenates table/column names | prepared statements DON'T protect identifiers — concat of table/column = still SQLi |
| ORM raw | DB::raw($x), DB::select($raw, ...), Eloquent::whereRaw($x) (Laravel) | is $x user input? bindings array separate? |
| ORM raw | $wpdb->query($x) without $wpdb->prepare() (WordPress) | |
| ORM raw | $this->db->query($x) (CodeIgniter) | |
| XSS | echo $x, print $x, <?= $x ?>, printf($fmt, $x) | escaped? (htmlspecialchars with ENT_QUOTES & correct charset? context-correct: HTML body vs attribute vs JS vs URL vs CSS?) |
| XSS | echo "<img src=$x>" inside onclick= etc. | attribute context — quoting matters |
| XSS | Template: {{ $x }} Blade (auto-escaped, OK) vs {!! $x !!} Blade (raw — DANGEROUS) | |
| XSS | Twig {{ x|raw }}, {% autoescape false %} | |
| Open redirect | header("Location: $x") | domain check? |
| Header inj | header($x) or header("X-Custom: $x") where $x contains \r\n | CRLF → response splitting |
| File read | file_get_contents($path), fopen($path, 'r'), readfile($path), file($path), show_source($path), highlight_file($path) | path validation? realpath + basedir check? .. filter? null byte (\0)? |
| File write | file_put_contents($path, $content), fwrite($fp, ...), copy($src, $dst), move_uploaded_file($tmp, $dst) | where is $dst chosen? extension check? MIME on server? |
| File delete | unlink($path), rmdir($path) | path traversal = arbitrary delete |
| Dir | scandir($path), glob($path), opendir($path) | info disclosure |
| SSRF | curl_exec($ch) with user URL, file_get_contents("http..."), fsockopen($host, $port), stream_socket_client, get_headers($url) | URL allowlist? followed redirects? gopher/file scheme blocked? |
| XXE | simplexml_load_string($xml), simplexml_load_file($xml), DOMDocument::loadXML, SoapClient::__doRequest | libxml_disable_entity_loader(true) called? LIBXML_NOENT flag? PHP 8.0+ default is safer |
| Dynamic call | call_user_func($fn, ...), call_user_func_array($fn, ...), $obj->$method(), $fn(...) where $fn is variable | attacker picks function/method → RCE |
| Dynamic class | new $cls($args), $cls::method() | attacker-picked class instantiation — constructor RCE via phpggc-like chain |
| Type juggle | == (loose compare) on auth token / hash / secret | "0e1234..." == "0e5678..." both truthy (both parse to 0e scientific notation → 0) |
| Type juggle | in_array($x, $arr) without 3rd arg true | loose compare, same bypass |
| Type juggle | strcmp($a, $b) with array arg (PHP < 8) | returns NULL, loosely-equal to 0 → "match" |
| Auth bypass | md5($pass) == $stored, sha1(...) | weak + type juggling |
| Auth bypass | hash_equals missing → timing side-channel | |
| LDAP | ldap_search($ds, $base, $filter) | filter concat = LDAP injection |
| XPath | $xml->xpath($x), DOMXPath::query($x) | concat = XPath injection |
| Mail | mail($to, $subj, $body, $headers) | header injection via \r\n in $to/$subj/$headers |
| Session fixation | session_id($x) where $x from user | attacker sets victim session id |
| Regex DoS | preg_match($pattern, $subject) where $pattern is user | ReDoS + /e (if PHP < 7) |
| Template inj | Twig\Environment::createTemplate($user), Smarty display("string:$user"), Blade compile($user) | SSTI |
| Mass assignment | $user->fill($_POST), $user->forceFill(...), (new User)->guard([])->fill(...) | $fillable vs $guarded mis-config → privilege escalation |
| Path concat in stream wrappers | "php://filter/resource=$file", "zip://$x", "data://text/plain,$x" | filter chain for arbitrary read/write / code injection via convert.base64-decode |
| Insecure rand | rand(), mt_rand() for tokens/CSRF/reset | use random_bytes / random_int |
| Debug | var_dump, print_r, phpinfo(), debug_print_backtrace, xdebug_* in production | info disclosure if reachable |