PHP preg_replace() /e RCE
preg_replace() es la función de PHP para sustituir por expresiones regulares. Durante años admitió el modificador /e (eval), que cambiaba por completo su comportamiento: en lugar de tratar el reemplazo como texto, evaluaba el reemplazo como código PHP. Si cualquier parte de ese código sale de una entrada controlada por el usuario, hay ejecución remota de código.
El patrón vulnerable tiene esta forma:
// el modificador /e evalua el tercer argumento como PHP
preg_replace('/(.*)/e', 'strtoupper("\\1")', $_GET['content']);Como el texto capturado por la expiración regular se interpola dentro del código evaluado, basta con enviar un content que cierre la llamada e inyecte PHP. El disparador clásico es \1 (o $1), que inserta el grupo capturado en el punto donde se hace el eval:
# el reemplazo se convierte en: strtoupper("system('id')")
content = system('id')En un escenario real (por ejemplo una API que actualiza el contenido de un post con preg_replace('/.../e', $texto, ...)), se inyecta primero un system('whoami') para confirmar la ejecución y, luego, se persiste una webshell sin depender de caracteres conflictivos en la línea de comandos:
file_put_contents('cmd.php', base64_decode('<webshell en base64>'))base64_decode evita que comillas, < o > rompan la cadena; el resultado es un cmd.php?cmd= estable desde el que ya se lanza la reverse shell.
Requisitos. PHP < 7 (el modificador /e se marcó obsoleto en 5.5 y se eliminó en PHP 7) y una llamada a preg_replace cuyo patrón o reemplazo dependa de entrada del usuario.
Cómo detectarlo. La firma en el log de acceso es un parámetro con llamadas a funciones PHP donde debería ir texto: system(, passthru(, file_put_contents(, base64_decode(, algo que ninguna entrada legítima lleva; un WAF que las inspeccione lo marca. La confirmación es el proceso web (php/apache2) lanzando sh/bash como hijo tras la petición (auditd/Sysmon for Linux) y la aparición de un .php nuevo en el webroot (FIM), la webshell persistida. Un PHP < 7 en el banner es la exposición previa.
Defensa. Nunca usar /e; su sustituto es preg_replace_callback(), donde el reemplazo es una función y no se evalúa texto. Actualizar a PHP 7+ elimina el vector de raíz. Conecta con inyección de comandos y subida de webshell.
Máquinas donde se aplica
- Symfonos 6VulnHub