php2shell: CVE-2026-109618 y CVE-2026-109619 en PHP

El 21 de octubre de 2026 el proyecto PHP publicó un aviso de seguridad por dos vulnerabilidades críticas de ejecución remota de código (RCE) sin autenticación, conocidas como php2shell. Ambas tienen un puntaje de 9.2 en CVSS v4.0 y CISA las agregó a su catálogo de vulnerabilidades explotadas activamente (KEV) el 26 de octubre de 2026.

Para PHP “no hay mitigaciones completas, actualizar es la única opción segura”.

Resumen

  CVE-2026-109618 CVE-2026-109619
Tipo RCE por corrupción ante falta de memoria (OOM) RCE por estado global obsoleto (stale global state)
Advisory GHSA-w8hw-w9fx-j79w GHSA-6wmf-4rv5-p3rp
CVSS v4.0 9.2 9.2
Autenticación No requiere No requiere
En KEV (CISA) Sí, desde 26/10/2026 Sí, desde 26/10/2026

¿En qué consisten?

CVE-2026-109618: corrupción por falta de memoria

Cuando PHP se queda sin memoria, el manejo del error puede interrumpir la ejecución mientras los datos internos de la petición están inicializados solo parcialmente. Luego, la limpieza de la petición puede tratar esos datos inválidos como válidos, lo que lleva a la corrupción de memoria y a la ejecución de código.

CVE-2026-109619: estado global obsoleto

Parte del estado interno que pertenece a una petición puede sobrevivir después de que se liberó la memoria que lo respalda. Una petición posterior atendida por el mismo worker puede usar ese estado obsoleto antes de que la inicialización normal lo reinicie. Es un caso del tipo use-after-free entre peticiones.

¿Quién las descubrió?

Las vulnerabilidades fueron descubiertas de forma independiente por Calif y por Wiz Research:

  • Calif encontró la falla con GPT-5.6 Sol, dentro de Patch the Planet, una iniciativa de OpenAI para apoyar a mantenedores de código abierto. La investigación es de Ryan Austin.
  • Wiz Research también las descubrió y reportó, usando Wiz Atlas, Gemini 4 Argon y GPT-5.6 Sol.
  • OpenAI coordinó la respuesta: conectó a los mantenedores de PHP con proveedores como Wiz y Cloudflare, y con organismos de gobierno como CISA, para preparar protecciones antes de la divulgación.
  • Los mantenedores de PHP lideraron la corrección y su publicación.

Detalle técnico según Calif

La idea de fondo es vieja: en 2004 Stefan Esser ya había encontrado una vulnerabilidad remota basada en el límite de memoria de PHP. Calif partió de esa historia y buscó “parientes” de ese bug que siguieran en el código.

El patrón es siempre el mismo:

publicar o destruir estado
  → hacer un trabajo que puede fallar
  → inicializar, confirmar o restaurar

si ocurre un error fatal en el medio:
  → se saltea el paso de confirmar o restaurar
  → otra parte del código confía en un estado falso

Paso 1: provocar un error de memoria a voluntad. Calif encontró dos formas:

  • Saltear post_max_size. PHP compara ese límite con el Content-Length declarado, pero no con los bytes que realmente procesa. Una petición chunked en HTTP/1.1, o sin largo en HTTP/2, pasa el control.
  • Inflar $_FILES sin superar post_max_size. Una parte multipart con filename="" no cuenta para max_file_uploads, pero PHP igual crea seis entradas en $_FILES por cada una. Con nombres anidados como files[a][b][c]..., cientos de estos registros ocupan más de los 128 MiB de memoria por defecto con un cuerpo de menos de 8 MiB.

Paso 2: interrumpir PHP en un punto que deje estado roto. Calif encontró tres:

Punto de interrupción Dónde es explotable Versiones
Alta de un elemento en una tabla hash (PHP cuenta el elemento antes de terminar de crearlo) mod_php, contra cualquier script PHP PHP 7.3 o superior
Upload progress de sesiones (las variables globales de sesión quedan apuntando a memoria liberada) mod_php, contra cualquier script PHP PHP 5.4.0 o superior
Estado del deserializador genérico (unserialize) mod_php y PHP-FPM, según la aplicación —

Esto explica por qué mod_php es el caso más grave: si PHP falla al iniciar una petición, mod_php libera la memoria pero reutiliza el mismo proceso, y la siguiente petición consume el estado roto. PHP-FPM, en cambio, termina el worker ante ese error. Para atacar PHP-FPM hace falta que la aplicación deserialice datos, por ejemplo con session_start(), unserialize() o maybe_unserialize() de WordPress.

Calif confirmó RCE sin autenticar en WordPress, Drupal, Joomla, MediaWiki, Roundcube, Magento, phpMyAdmin, Nextcloud, Laravel, Symfony y WHMCS.

Detalle técnico según Wiz Research

Wiz describe el mismo origen: todas son problemas de seguridad de memoria en cómo PHP maneja los errores fatales durante una petición, en especial los de falta de memoria: el error deja un estado interno corrupto que otra petición o worker reutiliza después.

Wiz reportó cuatro bugs:

Bug Qué permite Qué demostró Wiz
Use-after-free en el deserializador de upload progress de sesiones Corromper memoria y ejecutar código RCE sin autenticar en WordPress, Drupal y Joomla
Divulgación remota de memoria del proceso Leer memoria del worker sin autenticar Filtración de memoria independiente de la aplicación
Use-after-free en el backtrace de errores fatales Corromper memoria y ejecutar código RCE sin autenticar en Drupal
Confusión de tipos en OPcache Corromper el estado compartido de PHP y ejecutar código RCE sin autenticar en WordPress

La divulgación de memoria es similar a Heartbleed: PHP copia contenido de memoria liberada dentro de la cookie PHPSESSID de la respuesta. Esa memoria puede contener contraseñas, tokens, claves de API y certificados de otras peticiones. Funciona aunque la aplicación nunca llame a session_start().

WordPress, Drupal y Joomla son solo ejemplos: Wiz aclara que incluso una aplicación “Hello World” sobre una versión afectada puede ser vulnerable. También aclara que existen otras variantes además de estos cuatro bugs, y que la corrección de PHP las resuelve de raíz.

Wiz agrupa estos bugs bajo los dos CVE de forma algo distinta a como los describe PHP (por ejemplo, ubica el bug de upload progress en CVE-2026-109618, mientras que PHP lo mitiga en CVE-2026-109619). En este post sigo las descripciones y mitigaciones del aviso oficial de PHP.

El rol de la IA

El código vulnerable existía desde hacía más de una década. Explotarlo requería encadenar varios bugs y adaptarse a cómo PHP reutiliza la memoria en cada versión y configuración.

  • Wiz afirma que la IA hizo práctico desarrollar y adaptar exploits funcionales en horas: lo que antes habría llevado meses de trabajo a un equipo, un investigador lo completó en una semana.
  • Calif aclara que no usó herramientas especiales ni modelos que no estuvieran disponibles para todos. La clave fue conocer bien el objetivo y su historial de vulnerabilidades: los modelos no buscan solos primitivas de explotación de hace veinte años, hay que guiarlos hacia ahí. Después le pidieron al modelo que generalizara el patrón del primer bug, y así encontraron los siguientes.
  • OpenAI lo considera uno de los problemas de seguridad de mayor impacto de los últimos tiempos. Advierte que las mismas capacidades pueden ayudar a los atacantes a desarrollar exploits rápido, y que espera que los hallazgos de este tipo aumenten en los próximos meses.

Cronología

Fecha Evento
2004 Stefan Esser descubre la primera vulnerabilidad remota de PHP basada en el límite de memoria
2020 Se reporta el bug #78876 de PHP (variables largas en multipart causan falta de memoria)
2024 OSTIF y Quarkslab identifican un bug relacionado, sin demostrar que sea explotable
01/08/2026 Calif descubre varios caminos independientes hacia RCE
11/08/2026 Calif reporta el primer bug a PHP e inicia la divulgación coordinada
14/08/2026 Calif comparte una prueba de concepto de RCE con PHP
15/08/2026 PHP confirma que la prueba de concepto funciona
19/08/2026 Wiz descubre su primera vulnerabilidad
21/08/2026 Calif reporta el segundo bug a PHP
24/08/2026 Wiz reporta las vulnerabilidades a PHP
21/09/2026 Según Wiz, PHP confirma sus reportes y empieza a desarrollar la corrección
21/10/2026 PHP publica las versiones corregidas y se hace la divulgación pública

Cómo se realiza la explotación, paso a paso

Esta sección explica el mecanismo del ataque para entender por qué la actualización es la única defensa real. Es una vista conceptual basada en lo que publicaron Calif y Wiz; no incluye el payload ni el código del exploit.

La idea de fondo

Todas estas fallas siguen el mismo patrón. PHP primero “anuncia” que un dato interno existe y después lo termina de construir. Si en el medio ocurre un error fatal (típicamente por falta de memoria), el paso de construir o de limpiar se saltea, pero el dato quedó anunciado. Más tarde, otra parte de PHP confía en ese dato a medio hacer.

flowchart LR A["1. Publica estado"] --> B["2. Trabajo que puede fallar"] B -->|sin error| C["3. Confirma: estado válido"] B -->|error de memoria| D["3. Se saltea: estado falso queda vivo"] classDef neutro fill:#eef3f6,stroke:#9fb3c0,color:#404040; classDef seguro fill:#e3f0e3,stroke:#4a7a4a,color:#404040; classDef peligro fill:#f7e3e5,stroke:#CE5A67,color:#404040; class A,B neutro; class C seguro; class D peligro;

Paso 1: provocar el error de memoria

El atacante necesita que PHP se quede sin memoria en el momento justo. Para eso tiene que superar el límite memory_limit sin que post_max_size frene la petición antes. Calif describe dos maneras de lograrlo:

  • Enviar la petición de forma que PHP no pueda medir su tamaño real por anticipado (por ejemplo, chunked en HTTP/1.1 o sin largo declarado en HTTP/2).
  • Usar muchas partes de formulario vacías con nombres muy anidados: cada una ocupa poca red, pero obliga a PHP a crear muchas estructuras internas en memoria.

El resultado es que el atacante decide en qué operación interna va a fallar PHP.

Paso 2: dejar un estado "colgado" y que el worker sobreviva

El error tiene que ocurrir antes de que PHP termine de iniciar la petición. En ese punto, variables internas de PHP (por ejemplo, las de la sesión o las del deserializador) ya quedaron apuntando a memoria de la petición. Como la petición falla a medio iniciar, PHP libera esa memoria pero no ejecuta la limpieza que borraría esas variables. Quedan apuntando a memoria liberada.

Acá aparece la diferencia clave entre servidores:

flowchart TD F["Error fatal al iniciar la petición"] --> M["Apache + mod_php"] F --> P["PHP-FPM"] M --> M2["Libera la memoria pero reutiliza el mismo proceso"] M2 --> M3["La siguiente petición ataca"] P --> P2["Termina el worker"] P2 --> P3["Salvo que la app deserialice datos (session_start, unserialize...)"] classDef peligro fill:#f7e3e5,stroke:#CE5A67,color:#404040; classDef seguro fill:#e3f0e3,stroke:#4a7a4a,color:#404040; classDef aviso fill:#fcefe0,stroke:#d08a3e,color:#404040; class F,M,M2,M3 peligro; class P,P2 seguro; class P3 aviso;

Paso 3: una segunda petición consume el estado roto

Con el worker todavía vivo y las variables colgadas, el atacante manda una segunda petición que llega a la función de upload progress de sesiones. Esa función se ejecuta muy temprano, antes de que PHP reinicie esas variables, así que reutiliza el estado obsoleto. Mientras tanto, los datos de esta segunda petición ocupan la memoria que había quedado libre, de modo que el atacante controla su contenido.

%%{init: {"theme":"neutral", "themeVariables": {"noteBkgColor":"#f7e3e5","noteBorderColor":"#CE5A67","noteTextColor":"#404040","actorBkg":"#eef3f6","actorBorder":"#9fb3c0","actorTextColor":"#404040"}, "sequence": {"mirrorActors": false, "noteMargin": 12, "messageMargin": 42}}}%% sequenceDiagram actor At as Atacante participant W as Worker PHP At->>W: Petición A — fuerza OOM a mitad de iniciar Note over At,W: Variables internas quedan apuntando
a memoria liberada. El worker sobrevive. At->>W: Petición B — upload progress de sesiones Note over At,W: PHP reutiliza el estado obsoleto. Los datos
de B ocupan la memoria que quedó libre. W-->>At: Lectura de memoria / ejecución de código

Según el bug concreto, esto le da al atacante dos capacidades:

  • Leer memoria de otras peticiones o del propio servidor, que puede contener contraseñas, tokens y claves (similar a Heartbleed).
  • Controlar dónde escribe PHP un puntero y su tipo, lo que se encadena hasta la ejecución remota de código sin autenticación.

Por qué no hay mitigación completa

Las mitigaciones temporales cierran algún camino puntual (por ejemplo, apagar upload_progress corta la vía de la segunda petición confirmada), pero el problema de raíz —que PHP confíe en estado dejado por una petición que falló— sigue ahí y tiene otras variantes. Por eso la única solución completa es actualizar. | 21/10/2026 | Cloudflare despliega reglas de WAF (3:02 UTC) | | 23/10/2026 | PHP publica el aviso informativo sobre CVE-2026-115560 | | 26/10/2026 | CISA agrega ambos CVE al catálogo KEV |

Explotación activa

Según datos de Wiz citados por The Hacker News, al 25 de octubre de 2026 el porcentaje de organizaciones significativamente afectadas había bajado al 52,1%. Wiz observó dos tipos de actividad:

  1. Intentos de explotación que coinciden con las pruebas de concepto (PoC) públicas.
  2. Herramientas con la marca “PHP2Shell” que en realidad no usan estas vulnerabilidades, sino envenenamiento de sesiones y una inclusión local de archivos (LFI) en la aplicación.

Wiz también señala que agotar la memoria no alcanza por sí solo para completar el exploit: las diferencias entre compilaciones de PHP, límites de memoria y comportamiento de los workers pueden hacer fallar las etapas siguientes. Esto no es motivo para demorar la actualización: como las PoC son públicas, mejorarlas es cuestión de tiempo.

¿Quién está afectado?

Según PHP, todas las instalaciones de PHP que atienden peticiones web, incluyendo:

  • PHP-FPM
  • Apache con mod_php
  • php-cgi
  • FrankenPHP
  • LiteSpeed

El riesgo es mayor con mod_php, donde hay un exploit demostrado en el que una petición sin autenticar dispara el ataque antes de que se ejecute cualquier código de la aplicación. Es decir, no importa qué tan segura sea la aplicación.

Versiones corregidas publicadas por PHP:

Rama Versión corregida
8.5 8.5.12
8.4 8.4.27
8.3 8.3.36
8.2 8.2.35
8.1 y anteriores Sin soporte (ver abajo)

Cualquier versión anterior a estas dentro de cada rama es vulnerable.

PHP 8.1 y anteriores ya no reciben actualizaciones del proyecto PHP. Aun así, PHP publicó backports mínimos del código fuente para 7.4, 8.0 y 8.1, y algunos proveedores con soporte extendido (LTS) también los distribuyen.

PHP 8.2 llega a su fin de vida el 31 de diciembre de 2026, así que conviene planificar la migración a 8.3 o superior.

¿Qué hacer?

1. Actualizar

Es la única solución completa. Actualizar el código del sitio o sus plugins no alcanza: hay que actualizar PHP. Si el hosting administra PHP, hay que pedirle que aplique la actualización.

Las recomendaciones de CISA son:

  • Inventariar todas las instalaciones de PHP.
  • Actualizar de inmediato, empezando por los sistemas expuestos a Internet.
  • Si no es posible actualizar ahora, o se usa PHP 8.1 o anterior, sacar de línea los sistemas hasta poder corregirlos.
  • Monitorear workers de PHP que lancen shells o descarguen herramientas.

Como el ataque ocurre por debajo de la aplicación, sus logs pueden aportar poco contexto. Wiz recomienda prestar atención a:

  • Peticiones multipart inusuales seguidas de errores de límite de memoria o de respuestas HTTP 500 repetidas.
  • Cookies PHPSESSID inesperadamente grandes o malformadas en las respuestas.
  • Reinicios de workers de PHP sin explicación.

Un WAF puede dar algo de protección, pero no reemplaza la actualización.

OpenAI publicó un comprobador de versiones de PHP como habilidad de Codex, como parte de Patch the Planet, para ayudar a identificar instalaciones potencialmente vulnerables.

Para verificar la versión instalada:

php -v

Algunas distribuciones aplican el backport sin cambiar el número de versión, así que conviene revisar también el changelog del paquete de la distribución.

2. Mitigaciones temporales (no completas)

Solo para ganar tiempo mientras se actualiza. Después de cambiar la configuración hay que reiniciar todos los workers.

CVE-2026-109618

; Elimina el límite controlable, aunque una falla de asignación real
; todavía puede llegar al camino vulnerable
memory_limit = -1

; Bajo si no se necesitan subidas de archivos, 32M si se necesitan
post_max_size = 8M
; Siempre menor a post_max_size
upload_max_filesize = 4M
  • Preferentemente con php_admin_value en el pool de FPM o en la configuración de Apache.
  • Cuidado: como advierte Calif, memory_limit = -1 quita el tope de memoria de PHP, lo que aumenta el riesgo de agotar la memoria del servidor. Por eso conviene combinarlo con los límites de contenedor o cgroup del punto siguiente.
  • Limitar en el servidor web o proxy el tamaño de las peticiones, la cantidad de campos, partes multipart, cabeceras y profundidad de anidamiento.
  • Usar límites de memoria por contenedor o cgroup. PHP advierte no usar RLIMIT_AS ni RLIMIT_DATA.

CVE-2026-109619

; Bloquea los caminos confirmados por sesiones, no otras variantes
session.upload_progress.enabled = 0

; Solo PHP 8.5: bloquea el camino confirmado de backtraces fatales
fatal_error_backtraces = 0
  • Configurarlo en php.ini, en el pool de FPM o en el servidor, de forma que aplique antes de procesar la petición. No sirve ini_set() ni .user.ini de FPM.
  • Quitar configuraciones por petición inválidas o deprecadas que generen diagnósticos antes de la inicialización. Ocultar los diagnósticos no ayuda.
  • Corregir errores fatales y timeouts que un atacante pueda provocar.

Actualización 23/10/2026: PHP publicó un aviso informativo sobre otro problema, CVE-2026-115560, que afecta incluso a las versiones ya corregidas (como 8.5.12). Solo aplica a configuraciones poco comunes, en las que serialize_handler se cambia en tiempo de ejecución. Se corregirá en la próxima versión regular de PHP. El aviso recuerda que session.upload_progress.enabled = 0 ya figuraba como mitigación en GHSA-6wmf-4rv5-p3rp.

Caso Cloudflare

Cloudflare desplegó reglas de WAF el 21 de octubre de 2026 a las 3:02 UTC, unas horas antes de la divulgación pública. Están activas por defecto en todos los planes con WAF, incluido el gratuito:

Plan Ruleset Reglas
Free Cloudflare Managed Free Ruleset PHP Remote Code Execution Attack (CVE-2026-109618, CVE-2026-109619) #1 a #7
Pro, Business, Enterprise Cloudflare Managed Ruleset PHP Remote Code Execution Attack (CVE-2026-109618, CVE-2026-109619) #1 a #7
  • En los planes pagos conviene revisar que el Managed Ruleset esté desplegado en modo block.
  • Además, la regla Cloudflare Managed Response Firewall Default analiza la respuesta del servidor de origen y puede bloquearla antes de que llegue al atacante.
  • Los eventos se pueden revisar en Security > Security Analytics > Events, filtrando por las reglas.

Cloudflare informó que observó tráfico de ataque contra sus clientes, con atacantes modificando los payloads para evadir las reglas. Por eso el WAF ayuda a ganar tiempo, pero no reemplaza la actualización de PHP.

Caso AWS

AWS informó que sus propios servicios no están afectados, pero sí los clientes que corren PHP vulnerable en ellos:

Servicio Acción
Amazon Linux Paquetes actualizados disponibles, aplicarlos
Elastic Beanstalk Nuevas versiones de plataforma PHP 8.2 a 8.5; con actualizaciones gestionadas se aplican en la próxima ventana de mantenimiento
Lightsail Blueprints actualizados; las instancias existentes no se actualizan solas, hay que crear nuevas y migrar
Lambda No hay runtime PHP gestionado; actualizar imágenes o runtimes propios y redesplegar
CodeBuild Imágenes actualizadas desde el 30/10/2026; actualizar si se fijó una versión de imagen
EMR (EC2) Afectados clústeres con Ganglia (EMR 4.2.0 a 6.15.0); migrar a EMR 7.x o no instalar Ganglia
AWS WAF Grupo de reglas gestionadas de PHP actualizado; actualizar si se fijó una versión
WorkMail Corregido por AWS, sin acción del cliente

Fuentes

Fuente oficial:

Fuentes complementarias:

Más sobre conceptos relacionados en Amenaza vs Riesgo vs Vulnerabilidad.

Share: LinkedIn