PHP preg_replace() /e RCE
preg_replace() is PHP's regular-expression replacement function. For years it accepted the /e (eval) modifier, which changed its behaviour completely: instead of treating the replacement as text, it evaluated the replacement as PHP code. If any part of that code comes from user-controlled input, you get remote code execution.
The vulnerable pattern looks like this:
// the /e modifier evaluates the third argument as PHP
preg_replace('/(.*)/e', 'strtoupper("\\1")', $_GET['content']);Because the text captured by the regex is interpolated into the evaluated code, it is enough to send a content value that closes the call and injects PHP. The classic trigger is \1 (or $1), which inserts the captured group at the point where the eval happens:
# the replacement becomes: strtoupper("system('id')")
content = system('id')In a real scenario (say an API that updates a post's content with preg_replace('/.../e', $text, ...)), you inject a system('whoami') first to confirm execution and then persist a webshell without relying on characters that break on the command line:
file_put_contents('cmd.php', base64_decode('<webshell in base64>'))base64_decode prevents quotes, < or > from breaking the string; the result is a stable cmd.php?cmd= from which you launch the reverse shell.
Requirements. PHP < 7 (the /e modifier was deprecated in 5.5 and removed in PHP 7) and a preg_replace call whose pattern or replacement depends on user input.
How to detect it. The access-log signature is a parameter with PHP function calls where text should be: system(, passthru(, file_put_contents(, base64_decode(, which no legitimate input carries; a WAF inspecting for those flags it. Confirmation is the web process (php/apache2) spawning sh/bash as a child after the request (auditd/Sysmon for Linux) and a new .php appearing in the webroot (FIM), the persisted webshell. A PHP < 7 banner is the preceding exposure.
Defence. Never use /e; its replacement is preg_replace_callback(), where the replacement is a function and no text is evaluated. Upgrading to PHP 7+ removes the vector at the root. Connects with command injection and webshell upload.
Machines where it's used
- Symfonos 6VulnHub