blog

PHP2Shell: cuando el exploit lo escribe una IA

El 21 de octubre PHP publicó parches para dos fallos críticos, CVE-2026-109618 y CVE-2026-109619, que dejan ejecutar código en el servidor sin autenticarse. Lo nuevo no es el fallo, que llevaba más de veinte años ahí: es que lo encontró una IA y que, cuando se filtró antes de tiempo, bastaron un par de horas para rehacer el exploit. Parchear sigue siendo cosa tuya, y tanto como eso importa hasta dónde llega un worker de PHP el día que alguien entra.

Qué se publicó el 21 de octubre

Si tienes PHP en algún sitio, un WordPress, un panel interno, una API heredada, esta es de las semanas de parchear sin esperar. El 21 de octubre salieron las versiones 8.5.12, 8.4.27, 8.3.36 y 8.2.35, que arreglan dos fallos: CVE-2026-109618 («RCE via OOM corruption») y CVE-2026-109619 («RCE via stale global state»). Los dos tienen un 9,2 sobre 10 en CVSS 4.0 y los dos permiten ejecutar código sin credenciales. Afectan a cualquier PHP que atienda peticiones web: PHP-FPM, Apache con mod_php, php-cgi, FrankenPHP y LiteSpeed. El peor caso es mod_php, donde PHP dice haber demostrado la ejecución remota completa con la configuración por defecto, antes de que corra una sola línea de tu aplicación. Las ramas 8.1 y anteriores ya no reciben parches del proyecto, aunque hay backports mínimos para 7.4, 8.0 y 8.1. Y ojo con el número de versión: muchas distribuciones parchean sin subirlo (Ubuntu 24.04 lo arregla en 8.3.6-0ubuntu0.24.04.13). Entre la gente de seguridad se le conoce como php2shell.

Un fallo de hace más de veinte años

El fallo en sí no es nuevo. En 2004, Stefan Esser describió que el límite de memoria de PHP se podía usar para corromper memoria de forma remota, y no publicó el exploit. En 2024, la primera auditoría externa de PHP en años, pagada por la Sovereign Tech Agency, organizada por OSTIF y hecha por Quarkslab, volvió a toparse con un problema de falta de memoria. Se teorizó un exploit, pero no se halló un vector concreto, así que no se arregló. El equipo de seguridad tenía varias formas de taparlo y todas rompían algo (compatibilidad, un nuevo riesgo de denegación de servicio o cientos de extensiones de terceros), y la decisión estuvo parada y se retomó varias veces a lo largo de meses. La propia PHP Foundation lo ha llamado «un fallo estructural», de su proceso de decisión y de su falta de recursos.

Lo que cambió fue la IA

Lo que convirtió ese problema teórico en un exploit fiable fue la IA. Dos equipos llegaron por separado. Calif, una empresa de seguridad invitada a la iniciativa «Patch the Planet» de OpenAI, dio con el primer fallo el 1 de agosto usando el modelo GPT-5.6 Sol, y lo reportó a PHP el 11. Wiz lo encontró por su cuenta el 19 de agosto con sus herramientas Wiz Atlas, Gemini 4 Argon y GPT-5.6 Sol, y lo reportó el 24. Wiz lo resume así: una sola persona hizo en una semana lo que a un equipo con experiencia le habría costado meses, y los modelos adaptaron el exploit a varias aplicaciones y configuraciones en cuestión de horas. En el relato de Calif, el humano seguía mandando, eligió un objetivo que conocía y partió de las pistas de Esser, pero el trabajo lento de revisar el asignador de memoria y encadenar los fallos lo hicieron los agentes. Tampoco es un botón mágico: según ellos mismos, ningún modelo saca «todos los bugs» con un prompt.

El exploit, a un par de horas

La segunda lección llegó con la filtración. El 20 de octubre, antes de la fecha acordada, empezaron a circular los identificadores CVE, con la hora equivocada y un CVSS de 10,0 que no era el real, y poco después Calif publicó por error un vídeo con su prueba de concepto; lo borró, pero ya corría. Esa misma noche, un investigador rehízo un exploit funcional de uno de los fallos en hora y media, partiendo del vídeo y apoyándose en Codex, y lo confirmó con AddressSanitizer. Era un investigador, no un atacante, pero la moraleja vale igual: con las pistas delante, el exploit está a un prompt.

Los números de esos primeros días lo confirman. Amazon detectó un intento contra sus señuelos MadPot el 17 de octubre, cuatro días antes de la divulgación, desde una IP de una red de anonimización; el día del parche vio pruebas de concepto desde las 02:00 UTC y concluyó que ya se explotaba de forma amplia. Cloudflare, que había activado reglas en su WAF unas nueve horas antes del anuncio, contó el salto: de miles de intentos en las diez horas previas a decenas de miles en los 45 minutos siguientes, cientos de miles en dos horas y millones después. A 22 de octubre nadie había confirmado una brecha en un sistema de producción real. El margen entre el aviso y un exploit funcional, en cambio, ya se mide en horas.

¿Y los atacantes? No hay constancia pública de que ninguno usara IA para montar el exploit de php2shell. Lo que sí dicen quienes responden a incidentes es que el patrón general ha cambiado. CJ Moses, responsable de seguridad de Amazon, lo pone así: «el análisis asistido por IA abarata convertir el diff de un parche o un aviso en un exploit funcional». Antes, la explotación masiva esperaba a que alguien capaz publicara una prueba de concepto; esa dependencia se debilita.

Cómo funciona, por encima

Sin entrar en el exploit, los dos fallos comparten una idea. Una petición puede llevar a PHP a un error fatal en un punto muy concreto del arranque, por ejemplo agotando el límite de memoria con una petición grande que se cuela por debajo de los límites aparentes. Ese corte deja el proceso a medias. En CVE-2026-109618 queda una estructura de datos a medio inicializar que la limpieza de la petición trata luego como válida. En CVE-2026-109619 queda parte del estado global del worker, el de sesión o el de errores, apuntando a memoria que se libera al acabar la petición. Y lo que lo hace grave es esto: el worker sigue vivo y se reutiliza. En mod_php y en PHP-FPM, la siguiente petición la atiende el mismo proceso, que arranca leyendo ese estado viejo antes de reinicializarlo. De ese acceso a memoria ya liberada se pasa a leer memoria de otras peticiones, como contraseñas, tokens o claves, y, encadenándolo, a ejecutar código.

Qué alcanza eso dentro de un cluster

Si tu PHP corre en Kubernetes, para cuando esto es ejecución de código ya no es solo cosa de PHP. Una shell en un worker es código corriendo como ese pod, con todo lo que el pod tenga a mano: el token de su ServiceAccount montado en /var/run/secrets/…, los Secrets que le hayas montado como ficheros o variables, la red hacia los demás servicios del namespace y, si no lo has cortado, el endpoint de metadatos del nodo. El parche de PHP cierra la puerta; lo que decide el daño, si alguien entra antes de que parchees, es hasta dónde llega ese pod. Un ServiceAccount con solo los permisos que usa, los Secrets justos, NetworkPolicies que no dejen salir a cualquier parte y credenciales que caduquen valen aquí tanto como parchear deprisa.

Qué hacer

Lo único que cierra los dos fallos es actualizar: 8.5.12, 8.4.27, 8.3.36 o 8.2.35. Si vas por una rama sin soporte, mira el backport de tu distribución o cambia de versión. Y acuérdate de reiniciar los workers después, porque un proceso que sigue vivo sigue siendo vulnerable. Lo demás son parches de usar y tirar, y conviene saber hasta dónde llegan. Un WAF, como el de Cloudflare, que metió reglas antes del anuncio, filtra los patrones conocidos, pero los atacantes mutan la carga y no sustituye a actualizar. Bajar post_max_size o desactivar session.upload_progress tapa rutas concretas, no el fallo. Y tocar el código de tu aplicación o los plugins no arregla nada, porque el fallo está en el motor de PHP. Lo que tenga que seguir en una versión vieja y sin parche, sácalo de la red.

Los detalles técnicos están en los dos avisos de PHP (CVE-2026-109618 y CVE-2026-109619) y en los análisis de Calif y de Wiz. Picaporte Labs es una sola persona, en España; si ves algo que no cuadra con lo que tienes delante, escríbeme.