# php2shell, el fallo de software que dormía una década en PHP

> Cuatro primitivas de corrupción de memoria en el motor de PHP dan ejecución remota sin credenciales desde la 5.4. Parches y cómo detectarlo.
> Por José Maldonado · Ciberseguridad · 2026-10-22

Un servidor con PHP actualizado, un CMS al día y ningún código propio en la ruta del ataque. Con eso bastaba para ejecutar órdenes en WordPress, Drupal o Joomla sin credenciales, según la demostración que [Wiz Research publicó el 21 de octubre](https://www.wiz.io/blog/php2shell-php-rce). Los dos fallos que lo permiten, CVE-2026-109618 y CVE-2026-109619, están en el motor del lenguaje: el código vulnerable lleva más de una década ahí y lo que cambió fue la capacidad de convertirlo en un exploit.

El análisis de Wiz llegó el mismo día que los parches, PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35. La ventana fue de horas porque el secreto se rompió la víspera: los identificadores circularon el 20 de octubre hacia las 15:00 UTC con una puntuación CVSS falsa de 10.0, el proyecto adelantó su preaviso a las 19:30 y la divulgación coordinada, prevista para el 22, se movió al 21.

La consecuencia para quien administra sistemas es incómoda. Un inventario por CMS o por framework se queda corto, porque el fallo está debajo, en cómo PHP libera memoria cuando una petición muere por límite de memoria. Afecta a instalaciones que nunca llamaron a session\_start() y deja poco rastro en los registros de la aplicación, que apenas conservan contexto del motor.

## Un error fatal que no termina con la petición

Conviene fijar la escala antes de entrar en las primitivas. No hay una configuración que apague el fallo, ninguna extensión que lo tape y ningún framework que lo abstraiga, porque el estado que se recicla es el del propio intérprete.

PHP impone su memory\_limit con longjmp(), un salto que aborta la petición sin desenrollar la pila de llamadas. El atajo es rapidísimo y lleva décadas en el motor: en vez de devolver un error por cada nivel, PHP salta al cierre y libera de golpe la memoria pedida. CVE-2026-109618 demuestra que también deja cabos sueltos. Si el límite se alcanza cuando una estructura interna está a medio inicializar, la limpieza trata datos inválidos como válidos y la memoria liberada conserva lo que dejó otra asignación anterior.

### Un fallo sin límites

El segundo fallo (CVE-2026-109619) no necesita el límite. Un error fatal en el punto equivocado deja el estado global del worker apuntando a memoria de la petición anterior y, como PHP atiende peticiones en procesos que sobreviven, ese puntero huérfano abre la puerta de la siguiente visita. El proyecto lo resume en su aviso: el estado de una petición puede sobrevivir a la memoria que lo respaldaba y una petición posterior del mismo worker puede usarlo antes de que la inicialización normal lo reinicie.

La distinción importa para el diagnóstico. Un 500 por falta de memoria es rutina en cualquier hosting compartido, y lo anómalo no es el error sino que el worker que lo atendió siga vivo y sirva la petición siguiente con recuerdos de la anterior.

Ambos fallos comparten raíz y gravedad, con CVSS 9.2 cada uno, según el [aviso de CERT-EU](https://cert.europa.eu/publications/security-advisories/2026-018/). También comparten una propiedad que los hace difíciles de esquivar desde fuera: el requisito no es un bug de la aplicación, sino una petición diseñada para morir en el momento justo. Cualquier código PHP ofrece esa superficie, por pequeño que sea.

## Cuatro maneras de heredar memoria ajena

Wiz describió cuatro instancias explotables, agrupadas en esos dos identificadores y con impactos distintos. La primera es un use-after-free en el unserializer del progreso de subida: el decodificador guarda su contabilidad en una variable global y un fallo de asignación la deja publicada mientras se destruye la memoria de la petición. La segunda no busca ejecutar código, sino leer. El identificador de sesión vive en PS(id) y, si el cierre se interrumpe, PHP confía en un puntero obsoleto y devuelve en la cookie PHPSESSID memoria reciclada.

Primitiva

CVE

Impacto

Cadena demostrada

unserializer de progreso de subida, use-after-free

109618

corrupción y ejecución de código

RCE preauth en WordPress, Drupal y Joomla

cookie PHPSESSID, divulgación de memoria del worker

109618

lectura de memoria de otras peticiones

sin autenticación y sin session\_start()

backtrace del último error fatal, use-after-free

109619

corrupción entre peticiones

RCE preauth en Drupal

confusión de tipos en OPcache

109619

corrupción compartida entre workers

RCE preauth en WordPress

Las otras dos viven en el error y en la caché. El backtrace del último error fatal se conserva en EG(last\_fatal\_error\_backtrace) y, si la petición muere sin borrarlo, el worker arrastra un puntero a memoria liberada que puede leerse en la siguiente. La cuarta es más sutil: OPcache persiste las estructuras compiladas en memoria compartida y adelanta un contador local antes de publicar el compartido, así que un fallo de asignación deja objetos a medio persistir en huecos que otros workers dan por libres. Si otro proceso los reutiliza para un objeto de otro tipo, los metadatos resuelven un puntero equivocado.

Esas cuatro no agotan el problema: hay variantes rastreadas y probablemente otras sin rastrear, con requisitos distintos y bajo los mismos dos CVE. El parche ataca la raíz y no cada síntoma, aunque nadie puede afirmar que las cuatro eran las peores.

## Todo cae ante el ataque

El escenario de mayor riesgo no es el más moderno. PHP considera mod\_php la configuración más expuesta porque el disparo ocurre antes de que se ejecute código de la aplicación: el motor analiza la petición multipart durante el arranque, cuando buena parte de los módulos aún no se ha inicializado. Con PHP-FPM el arranque es distinto, aunque el proyecto confirmó ejecución remota también por esa vía a través de rutas de la aplicación.

Wiz eligió tres objetivos conocidos para la demostración pública: las imágenes de contenedor más recientes de WordPress, Drupal y Joomla en el momento de las pruebas. No eran instalaciones antiguas con plugins abandonados, sino el estado del arte de tres CMS que sostienen buena parte de la web. La explotación no dependía de ninguno: incluso un programa que solo imprima un saludo cae sobre una versión sin parche.

Ese detalle separa este caso de un aviso de plugin. La superficie no la define lo que instala el administrador, sino la versión del intérprete, y esa versión la impone el hosting o la imagen base.

La segunda mitad del hallazgo se parece más a Heartbleed que a un robo de credenciales. Sin llegar a ejecutar código, el atacante lee una región de memoria del worker del tamaño que elija, con contraseñas y tokens de otras peticiones, además de claves de API y certificados del servidor. Wiz compara el fallo con el incidente de OpenSSL de 2014, cuando lo grave no fue el cifrado, sino lo que se colaba en las respuestas.

## De la auditoría de 2024 al embargo roto

La historia del fallo empieza en una auditoría y termina en una filtración, con dos años y medio entre ambas cosas. La [cronología que publicó la PHP Foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/) permite reconstruirla con horas en casi todos los hitos.

Fecha

Qué ocurrió

2024

Auditoría de Quarkslab financiada por la Sovereign Tech Agency vía OSTIF; al arreglar otro hallazgo aparece el primer indicio

30 jul 2026

Calif empieza su auditoría de PHP en el programa Patch the Planet

11 ago 2026

Calif reporta una ejecución remota incondicional con la misma causa raíz; OpenAI, lo mismo ese día

19 al 24 ago 2026

Wiz encuentra el fallo, confirma RCE contra Drupal y WordPress y lo reporta

18 al 21 sep 2026

OpenAI aporta vectores adicionales; PHP reconoce los informes y empieza el parche coordinado

20 oct, 15:00 UTC

Los dos CVE se filtran con una hora falsa y un CVSS 10.0

20 oct, 19:30 UTC

Preaviso adelantado y release movida del 22 al 21

21 oct, 07:41 UTC

Chainguard rompe el embargo con imágenes ya parcheadas

21 oct, 13:30 a 14:00 UTC

Salen 8.5.12, 8.4.27, 8.3.36 y 8.2.35; CERT-EU registra las 13:30, el proyecto fijó las 14:00

El vídeo que Calif publicó por error hacia las 20:00 UTC mostraba la prueba funcionando contra objetivos reales y aceleró el desarrollo de PoCs ajenas. Chainguard había recibido el parche por adelantado, así que estudiar la corrección en binario fue posible seis horas antes que para el resto. El intento más antiguo ya estaba registrado: [Amazon Threat Intelligence lo fechó el 17 de octubre](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/), cuatro días antes del anuncio, desde una dirección de una red comercial de anonimización. Entre las 02:00 y las 08:00 UTC del 21 aparecieron PoCs y más intentos contra su red de honeypots, con payloads distintos y varios actores.

Cloudflare se movió antes que todos. Sus reglas de WAF entraron en vigor a las 03:02 UTC del 21 para todos los planes, también el gratuito, unas [nueve horas antes del anuncio](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/), y la compañía documentó intentos de mutación del payload para esquivar la firma. Su comunicado admite que el filtro por petición no bastaba: hubo que mirar también la respuesta del origen en la misma transacción HTTP, capacidad que estrenaron aquí.

## Lo que la IA cambió en la economía del exploit

Wiz encontró el fallo el 19 de agosto y tuvo ejecución remota contra Drupal y WordPress en dos días. Lo hizo con su plataforma Atlas y con dos modelos frontera, Gemini 4 Argon y GPT-5.6 Sol, y atribuye a Argon una contribución decisiva en el descubrimiento, el exploit y la validación del parche. La frase con la que resumen el cambio de escala es la más incómoda del episodio: un investigador completó en una semana lo que antes habría exigido meses a un equipo de primer nivel.

El calendario ayuda a medir el salto. Entre el hallazgo y la ejecución remota pasaron 48 horas, y entre esta y el parche, dos meses de trabajo coordinado con el proyecto de PHP y con otros dos equipos.

Para explotar estos fallos había que encadenar varias primitivas y ajustar el exploit a cómo cada versión de PHP y cada aplicación reparte la memoria. Un ataque que funcionaba en un despliegue se rompía en otro y exigía depuración nueva. Ese trabajo, reproducir el mismo error en cien configuraciones distintas, era el filtro real, y los modelos lo abarataron.

La otra cara es que el mismo efecto se aplica al otro lado. La PHP Foundation usa herramientas de IA para validar informes y agrupar los que comparten causa raíz, y desde el 1 de julio ha publicado 15 CVE y ha descartado más de cien reportes duplicados o no reproducibles. AWS añade la parte que rompe las planificaciones: convertir el diff de un parche en un exploit también se ha vuelto barato, así que las ventanas de siete o treinta días ya no describen la realidad.

## Actualiza versiones y protégete

El mapa de versiones es corto y no admite interpretación. El [aviso del proyecto en php.net](https://www.php.net/archive/2026.php) fija las cuatro ramas con soporte, deja fuera todo lo anterior y recuerda que las distribuciones recibieron el parche antes de la release.

Rama

Versión con el arreglo

Situación

PHP 8.5

8.5.12

soportada

PHP 8.4

8.4.27

soportada

PHP 8.3

8.3.36

soportada

PHP 8.2

8.2.35

soportada

PHP 8.1 y anteriores

sin release del proyecto

fin de vida; hay backports mínimos de código para 7.4, 8.0 y 8.1

Quien dependa de repositorios de terceros tiene un problema extra. El proyecto avisa de que rpms.remirepo.net, mantenido por voluntarios, no tendría actualizaciones el día de la release y recomienda pasarse a los paquetes de la distribución o compilar. CISA, en su [alerta del 21 de octubre](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching), pide inventariar todo PHP de la organización, incluido el embebido, y sacar de línea los sistemas que sigan en 8.1 o anterior.

Las mitigaciones existen y ninguna cierra el problema. El proyecto propone poner memory\_limit en -1 mediante php\_admin\_value para quitar de la ecuación el fallo controlable por límite, bajar post\_max\_size a 8M o 32M y desactivar session.upload\_progress.enabled; en PHP 8.5 hay que apagar además fatal\_error\_backtraces, y en todos los casos reiniciar los workers. Su conclusión es que actualizar es la única opción segura, y AWS lo dice con otras palabras: no existe un workaround de configuración que cubra los dos CVE.

## Señales que deja un ataque en los registros

Los fallos se ejecutan por debajo de la capa de aplicación, donde los logs de WordPress o de Symfony no ven nada. Wiz propone tres patrones que sí quedan en el servidor web: peticiones multipart anómalas seguidas de errores de límite de memoria o de series de 500 en la misma ruta, cookies PHPSESSID malformadas o de tamaño inesperado, que son la huella de la divulgación, y una rotación de workers que nadie explica por carga o despliegue.

*   Buscar la cadena PHP\_SESSION\_UPLOAD\_PROGRESS en el cuerpo de los POST y ver si aparece en formularios que no suben nada.
*   Cruzar los picos de 500 con procesos PHP que lanzan shells o descargan herramientas, la conducta que CISA pide vigilar.
*   Revisar las direcciones que AWS publicó como indicadores, sabiendo que son anonimización compartida y que sirven para investigar, no para bloquear.
*   Inventariar el software de terceros que embebe PHP con la lista del [NCSC neerlandés en GitHub](https://github.com/NCSC-NL/php2shell), porque el problema alcanza paneles de electrodomésticos, appliances de red y consolas de gestión.

La analista de Forrester Madelein van der Hout recordó en la cobertura de [TechTarget](https://www.techtarget.com/cybersecurity/news/366652444/PHP2Shell-22M-exposed-servers-at-risk-as-emergency-patches-released) que parchear no elimina el acceso que un atacante ya hubiera conseguido. Esa cobertura recoge la estimación de Palo Alto Networks de 22,6 millones de instancias expuestas, cifra de escaneo y no censo. Para dimensionar el mercado, el recuento de [W3Techs del 22 de octubre](https://w3techs.com/technologies/details/pl-php) sitúa PHP en el 69,3% de los sitios cuyo lenguaje de servidor se conoce, de ahí el 70% redondeado de los comunicados.

## Un motor que se arregla a mano y a contrarreloj

La parte del episodio que menos se va a recordar es la que más debería pesar. El equipo de seguridad de PHP tardó meses en elegir entre soluciones incompatibles: romper compatibilidad hacia atrás, abrir una vía nueva de denegación de servicio o exigir cambios a extensiones que nadie podía coordinar. La fundación lo describe sin paños calientes: el retraso fue un fallo estructural, causado por el proceso de decisión y por la falta de recursos.

Ese diagnóstico convive con un contexto regulatorio que ya no admite la excusa de la escala. Desde el 11 de septiembre de 2026, los fabricantes de productos con elementos digitales deben notificar las vulnerabilidades explotadas por la plataforma única de la CRA, como recordó [ENISA en su declaración conjunta con la Comisión Europea, CERT-EU, Europol y la red de CSIRTs](https://www.enisa.europa.eu/news/joint-statement-on-critical-php-vulnerabilities). Un motor escrito en C, sostenido por un puñado de personas financiadas y con veinte años de decisiones de diseño encima, encaja mal si el dinero sigue llegando por proyecto.

La complejidad dejó de ser una defensa. Durante una década, estos fallos no se explotaban a escala porque encadenarlos exigía un esfuerzo humano desproporcionado; en agosto bastó un investigador con dos modelos frontera y una semana.
