PHP2Shell: lo que debes saber y cómo prepararte para lo que viene Más información

CrowdStrike protege a los clientes frente a las amenazas distribuidas mediante PHP2Shell

Se divulgaron dos vulnerabilidades críticas de RCE por corrupción de memoria en el núcleo de PHP, registradas como CVE-2026-109618 y CVE-2026-109619.

21 de octubre de 2026  •  Amit Serper  •  Desde las primeras líneas

Se divulgaron dos vulnerabilidades críticas de ejecución remota de código (RCE) por corrupción de memoria, denominadas en conjunto PHP2Shell, en el núcleo de PHP, el motor subyacente que impulsa una gran parte de la web. La plataforma CrowdStrike Falcon® ofrece visibilidad y cobertura de detección listas para usar para las actividades posteriores a la explotación de ambas vulnerabilidades.

Identificadas como CVE-2026-109618 y CVE-2026-109619, ambas vulnerabilidades tienen una puntuación de 9,2 (crítica) en CVSS 4.0 y se clasifican como CWE-824: acceso a un puntero no inicializado. Ambas permiten que un atacante remoto no autenticado ejecute comandos arbitrarios del sistema en un servidor web afectado a través de HTTP.

Los dos problemas tienen una única causa raíz. Cuando PHP encuentra una condición de error fatal (como agotar el límite de memoria configurado en memory_limit, superar el tiempo máximo de ejecución o perder la conexión con el cliente), no devuelve un error a través de la pila de llamadas. Genera un error fatal y usa longjmp() para abandonar por completo el contexto de ejecución actual y abortar la solicitud. Cualquier función C que se encuentre en pleno proceso de construcción de una estructura de datos se descarta sin más: no se ejecutan los destructores, no se revierten los cambios en los registros de control ni queda constancia de que la estructura está incompleta. A continuación, PHP inicia una secuencia de limpieza que, en condiciones normales, supone que todas las estructuras se encuentran en un estado coherente.

Las dos CVE describen los puntos en los que esta suposición deja de ser válida:

  • CVE-2026-109618 (RCE por agotamiento de memoria) abarca situaciones en las que la memoria de la solicitud queda sin inicializar. Una estructura de datos se marca como si contuviera una entrada antes de que se asigne la memoria necesaria para almacenarla. Si esa asignación supera el límite de memoria, la entrada queda con datos residuales del heap. La propia rutina de limpieza de la solicitud fallida lee esos bytes y toma decisiones de flujo de control en función de sus valores.
  • CVE-2026-109619 (RCE por errores fatales) abarca los contextos que dejan inválido el estado global. Una variable global a nivel de módulo sigue apuntando al heap de la solicitud que el cierre ya liberó y sigue indicando que su operación está activa. Una solicitud posterior, procesada por el mismo proceso de trabajo, lee esa memoria liberada o toma decisiones de flujo de control en función de su contenido.

En ambos casos, el atacante controla el disparador y, lo que es fundamental, los datos que PHP termina interpretando incorrectamente. CrowdStrike ha verificado de principio a fin que una única solicitud POST anónima puede provocar que un proceso de trabajo de Apache mod_php inicie un proceso de shell y ejecute un comando especificado por el atacante, sin necesidad de autenticación ni de una sesión, y sin que intervenga código de aplicación vulnerable.

Lo que aumenta la gravedad de estas vulnerabilidades es su ubicación en la pila de ejecución. PHP analiza los datos entrantes de la solicitud antes de que se inicialicen las extensiones y de que se compile una sola línea de código de la aplicación, por lo que la explotación no depende de lo que aloje el servidor. Una aplicación que se mantiene de manera meticulosa está expuesta exactamente al mismo nivel de riesgo que una aplicación cuyo mantenimiento se ha descuidado. El ataque también es sorprendentemente sigiloso: el proceso de trabajo no falla, el servidor devuelve una respuesta HTTP 200 estándar y los registros de la aplicación no muestran ninguna actividad inusual.

En nuestras pruebas, desarrollamos un código de exploit que funcionó de manera constante en arquitecturas x86-64 y ARM, siguió siendo confiable entre reinicios de procesos de trabajo y, en hosts basados en ARM, que son cada vez más comunes en flotas de la nube, logró eludir la aleatorización del diseño del espacio de direcciones (ASLR) usando únicamente solicitudes HTTP.

Detalles técnicos

La causa raíz común: longjmp() sin realizar el proceso de desapilamiento

Ambas vulnerabilidades se encuentran en Zend, el motor que constituye el núcleo de PHP. Zend es la implementación en C que compila el código fuente de PHP en bytecode, lo ejecuta en una máquina virtual y administra todo lo que el lenguaje necesita durante la ejecución: variables, arreglos, objetos, conteo de referencias y asignación de memoria. Escrito originalmente por Zeev Suraski y Andi Gutmans (el nombre combina sus nombres de pila) e incorporado con PHP 4, no es un componente opcional ni un complemento de terceros: se incluye en todas las compilaciones de PHP. Por eso, su código fuente se encuentra en el directorio Zend/ de php-src. En la práctica, esto significa que un defecto en Zend es un defecto en el propio motor de PHP y afecta a todas las aplicaciones, los marcos y las extensiones que se ejecutan en el intérprete, independientemente de cómo esté escrito ese código.

Dos componentes fundamentales de Zend son centrales para los problemas que se describen aquí: HashTable, que sustenta todos los arreglos de PHP, incluido el superglobal $_POST, y el gestor de memoria de Zend (Zend MM), el asignador propio de PHP para cada solicitud, que se sitúa entre PHP y el sistema operativo. La ruta de gestión de errores fatales de PHP termina en zend_bailout(), que ejecuta un LONGJMP para salir de la pila de llamadas de C actual:

// Zend/zend.c
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno)
{
...
CG(unclean_shutdown) = 1;
...
LONGJMP(*EG(bailout), FAILURE);
}

No se ejecuta nada entre el error fatal y el bloque zend_catch que lo contiene. Cualquier función que haya modificado el estado y dependa de la ejecución de su propio código de limpieza deja ese estado sin restaurar. Esto solo es seguro si todas esas funciones están diseñadas para contemplar una terminación abrupta, pero varias de ellas no lo están.

CVE-2026-109618: memoria de solicitud sin inicializar, utilizada durante la misma solicitud

El caso más representativo se encuentra en _zend_hash_str_add_or_update_i(), en Zend/zend_hash.c, la función auxiliar que se utiliza para todas las inserciones con claves de cadena en el arreglo de PHP. En la ruta de inserción, incrementa ambos contadores de elementos, obtiene un puntero al nuevo bucket y solo entonces asigna memoria para una copia de la clave:

// Zend/zend_hash.c
add_to_hash:
   idx = ht->nNumUsed++;
   ht->nNumOfElements++;
   p = ht->arData + idx;
   p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);

Si zend_string_init() excede memory_limit, Zend MM genera un error fatal que termina en zend_bailout(). La ejecución nunca vuelve para completar el bucket. Ahora, la tabla informa una entrada más de las que realmente contiene, y el valor, el hash y el puntero de clave de esa entrada son los bytes que el asignador dejó por última vez en esa memoria; el arreglo de buckets se asigna con una llamada a emalloc() sin inicializar, y solo se restablece explícitamente la región del hash.

Durante la finalización de la solicitud, php_request_shutdown() destruye las superglobales y zend_array_destroy() recorre los buckets indicados por nNumUsed. La entrada residual se pasa a i_zval_ptr_dtor(), que interpreta los bytes restantes como un valor con recuento de referencias: desreferencia el puntero que contienen, reduce el recuento de referencias en esa dirección y, si este llega a cero, realiza una llamada a través de una tabla de punteros a funciones, utilizando como índice una etiqueta de tipo que también procede de los bytes residuales.

Para activar la condición, basta con un cuerpo POST codificado en URL, ya que PHP completa $_POST dentro de php_hash_environment(), que php_request_startup() llama antes de zend_activate_modules() y antes de que se compile cualquier script. Un campo con un nombre anidado como ZZZZ…[x]=1 hace que php_register_variable_ex() inserte el nombre externo como una nueva clave de arreglo, por lo que el atacante controla directamente la longitud de la clave que debe asignarse. Ajustar el tamaño de ese nombre para que supere memory_limit hace que el bailout ocurra precisamente al asignar la clave.

El control sobre los bytes sobrantes se debe al determinismo del asignador. Zend MM atiende las asignaciones pequeñas mediante listas libres por tamaño, en estricto orden de último en entrar, primero en salir, y nunca pone en cero los bloques recuperados. Por lo tanto, se puede dimensionar un campo anterior de la misma solicitud, cuyo valor PHP copia, decodifica y libera de inmediato, de modo que su contenido liberado ocupe con exactitud el bloque que posteriormente reclama el arreglo corrupto. La entrada residual se convierte en un valor completamente definido por el atacante, que utilizamos para crear un arreglo falsificado cuyo puntero al destructor apunta a system() de libc, con la cadena de comandos almacenada en el mismo bloque de memoria.

El único artefacto del lado del servidor de un intento exitoso es un error fatal genérico en el registro del servidor web:

PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 3670048 bytes) in Unknown on line 0

La expresión in Unknown on line 0 es la señal reveladora: confirma que no se estaba ejecutando ningún archivo PHP.

CVE-2026-109619: estado global no válido consumido por una solicitud posterior

La segunda CVE sigue el mismo mecanismo, pero el daño afecta al estado asociado al proceso, lo que desplaza la oportunidad de explotación a una solicitud posterior. En este caso, la operación interrumpida almacena temporalmente el estado en una variable global y depende de que se ejecute su propia función de limpieza para borrarlo. PHP almacena en caché los contextos activos de serialización y deserialización en variables globales del módulo y reutiliza el puntero almacenado en caché siempre que el nivel de anidamiento sea distinto de cero. Solo la llamada correspondiente a la función de destrucción reduce ese nivel y borra el puntero, y únicamente la rutina de inicialización de solicitudes de una extensión lo restablece. La gestión de sesiones funciona de manera similar: mantiene el identificador de sesión, el estado del controlador de sesiones, las variables de sesión y el estado del progreso de carga de archivos en variables globales que se borran mediante rutinas de cierre específicas.

Cuando un error fatal interrumpe una de estas operaciones, se omite la limpieza correspondiente. La variable global conserva su puntero y el contador de anidamiento sigue indicando que la operación está activa. Al cerrarse la solicitud, se libera todo el heap correspondiente a esa solicitud, pero la variable global sobrevive porque pertenece al proceso y no a la solicitud. PHP conserva un puntero a nivel de módulo que apunta a una región de memoria que ya ha liberado.

Hay dos factores que permiten acceder a esta vulnerabilidad y controlar su explotación:

  • Reutilización de procesos de trabajo: en implementaciones con procesos de trabajo persistentes, como Apache mod_php, el mismo proceso gestiona la siguiente solicitud. La variable global conserva su valor residual, el sistema interpreta que la operación sigue en curso y utiliza el puntero a memoria ya liberada para leer o escribir datos, o para determinar el flujo de ejecución. Para entonces, el contenido de esa región de memoria está influido por el atacante, ya que también controla la solicitud intermedia: puede elegir su tamaño y contenido, y el comportamiento de asignación de memoria de Zend MM es lo suficientemente determinista como para colocar los datos de forma fiable en esa región. PHP-FPM finaliza los procesos de trabajo cuando falla la inicialización de una solicitud, lo que impide su reutilización si la interrupción ocurre durante esa fase. Sin embargo, si la interrupción se produce dentro de un script que ya está en ejecución, el proceso de trabajo de FPM permanece activo y queda igualmente expuesto.
  • Orden de inicio: php_request_startup() analiza las variables de la solicitud antes de ejecutar las funciones de inicialización de solicitudes de las extensiones. Por lo tanto, el manejo del progreso de las cargas multipartes ejecuta código de sesión y serialización antes de que las extensiones responsables de esas variables globales hayan tenido oportunidad de restablecerlas, lo que permite que se use un puntero obsoleto en la primera fase de una solicitud.

Además del agotamiento de memoria, esta condición puede producirse cuando se supera el tiempo máximo de ejecución o se interrumpe la conexión con el cliente, ya que ambos casos siguen la misma ruta de zend_bailout(). La explotación es, por lo tanto, una operación de dos etapas: una solicitud envenena el proceso de trabajo y una segunda activa el uso de la memoria liberada.

Impacto de la vulnerabilidad

La gravedad de ambos problemas se debe a que aparecen al inicio del procesamiento de las solicitudes. Como la corrupción ocurre mientras PHP todavía está ensamblando las variables internas de la solicitud, antes de que se inicialicen las extensiones y se ejecute cualquier código de aplicación, cada endpoint de PHP accesible en un servidor afectado constituye una superficie de ataque activa, incluidos los formularios estáticos, las rutas de inicio de sesión y las comprobaciones de estado. No hay autenticación que eludir, ninguna sesión que robar ni ningún endpoint vulnerable que descubrir. La exposición no depende del CMS, el marco ni los complementos que se utilicen.

El ataque tampoco deja prácticamente ninguna huella digital. Los procesos de trabajo permanecen estables, las respuestas devuelven HTTP 200 y los registros de la aplicación no muestran anomalías, porque nunca se ejecutó código de la aplicación. El único rastro que queda en el servidor es un error fatal por agotamiento del límite de memoria en el registro de errores del servidor web, que suele descartarse como un evento sin importancia. Esto deja a los equipos de defensa sin datos de telemetría web útiles para identificar los endpoints afectados. 

En servidores web multiproceso como Apache prefork, cada proceso de trabajo se deriva de un único proceso principal, por lo que la disposición de memoria de la que depende un atacante se mantiene constante entre los reinicios de los procesos de trabajo. Esa estabilidad permite que un atacante perfeccione sus payloads sin costo: un intento fallido queda absorbido por la recreación de un proceso de trabajo, que las herramientas de monitoreo consideran rutinaria.

Las dos CVE presentan diferencias de alcance y momento de explotación que tienen importantes consecuencias operativas. CVE-2026-109618 se explota de forma independiente: una única solicitud corrompe sus propios datos y las consecuencias se manifiestan durante la fase de limpieza de esa misma solicitud, lo que hace que la explotación sea rápida, se complete en una sola operación y resulte difícil de reconstruir posteriormente. CVE-2026-109619, en cambio, abarca varias solicitudes y requiere una fase de preparación de la memoria y otra de activación dirigidas al mismo proceso de trabajo. Sin embargo, afecta a un conjunto más amplio de estados internos y puede activarse tanto por agotamiento de memoria como por tiempos de espera agotados e interrupciones de la conexión con el cliente. Las implementaciones con procesos de trabajo persistentes son las que presentan una exposición significativa a esta segunda clase de vulnerabilidades.

La explotación exitosa de cualquiera de las dos vulnerabilidades permite ejecutar comandos con los privilegios del usuario del servidor web, lo que constituye una base de apoyo estándar para implementar web shells, obtener credenciales de bases de datos y API a partir de archivos de configuración y desplazarse lateralmente hacia redes internas. Parchear es la única medida de remediación completa. Cuando no es posible parchear de inmediato, la supervisión del comportamiento de los endpoints constituye la principal línea de defensa, ya que la explotación es invisible a nivel HTTP, pero sus consecuencias sí pueden detectarse.

¿Me afecta?

Ambas vulnerabilidades están presentes en todas las versiones de PHP de las series 7.x y 8.x. El orden específico de la HashTable detrás de CVE-2026-109618 se introdujo mediante una optimización de rendimiento en octubre de 2017 y se incluyó por primera vez en PHP 7.3.0 (diciembre de 2018). Las rutas de serialización y de sesión detrás de CVE-2026-109619 se remontan aún más atrás: a PHP 7.1 y PHP 5.4, respectivamente. En otras palabras, la ventana de explotación ha existido en versiones publicadas de PHP durante más de siete años. Todas las ramas de PHP con soporte están afectadas.

La versión por sí sola no determina la exposición. El disparador de agotamiento de memoria debe poder activarse mientras PHP analiza los datos de la solicitud, lo que requiere que el cuerpo de la solicitud pueda crecer lo suficiente como para agotar el límite de memoria antes de que se ejecute cualquier script. En la práctica, esto significa que la configuración cumple aproximadamente con lo siguiente:

 memory_limit < 4 × post_max_size

Los valores predeterminados incluidos con PHP (post_max_size = 8M, memory_limit = 128M) no cumplen esta condición, por lo que una instalación estándar no puede explotarse mediante la ruta con codificación URL que demostramos. Sin embargo, muchas implementaciones en producción están configuradas para aceptar cargas grandes, y combinaciones comunes, como post_max_size = 64M con memory_limit = 128M o post_max_size = 100M con memory_limit = 256M, sí cumplen esa condición. Se debe tener en cuenta que post_max_size puede configurarse por directorio en .htaccess o en un host virtual, por lo que, en un servicio de hosting compartido, un solo sitio puede adoptar una configuración vulnerable sin que se realice ningún cambio en todo el servidor. CrowdStrike reprodujo la vulnerabilidad reduciendo memory_limit a 16M y aumentando post_max_size a 64M únicamente para mantener reducido el tamaño de la payload de prueba. La misma vulnerabilidad puede explotarse con cargas útiles más grandes cuando se utilizan los límites superiores mencionados anteriormente.

Para comprobar qué versión de PHP y qué configuración se están ejecutando en tu servidor, puedes ejecutar el comando php --version. Para ver los valores configurados en tu servidor para las opciones enumeradas anteriormente, ejecuta el siguiente comando:
php -i | grep -E "^(memory_limit|post_max_size|upload_max_filesize|Loaded Configuration File)”.

Cómo CrowdStrike Falcon protege a los clientes

CrowdStrike emplea un enfoque por capas para detectar la explotación y la actividad posterior a la explotación, mediante machine learning y detecciones basadas en el comportamiento, incluidos los indicadores de ataque (IOA). La plataforma Falcon ofrece visibilidad y cobertura de detección listas para usar para las actividades posteriores a la explotación de las vulnerabilidades de PHP. Los IOA existentes detectan y previenen actividades comunes posteriores a la explotación, como la implementación de web shells, la creación de procesos secundarios sospechosos por parte de PHP o de procesos del servidor web y la ejecución de comandos maliciosos.

CrowdStrike continúa investigando las técnicas de explotación asociadas con esta vulnerabilidad.

Figura 1. Cadena de explotación de vulnerabilidades de PHP

Cómo encontrar versiones vulnerables de PHP con Falcon® Exposure Management

CrowdStrike Falcon® Exposure Management ofrece visibilidad integral en los entornos afectados. Las capacidades de escaneo de red (Netscan) y gestión de la superficie de ataque externa (EASM) permiten detectar rápidamente los activos expuestos a Internet mediante el análisis de banners. Además, la visibilidad proporcionada por los sensores permite evaluar automáticamente los parches posteriores suministrados por los proveedores para las distribuciones de Linux compatibles y los sistemas operativos Windows y macOS.

Falcon Exposure Management detecta estas CVE en todas las versiones compatibles del sensor mediante su gestor de paquetes existente y comprobaciones específicas de aplicaciones. Estas comprobaciones no requieren una versión mínima del sensor.

CrowdStrike también agregó las siguientes detecciones basadas en sensores. Estas detectan instalaciones de PHP que las comprobaciones existentes podrían pasar por alto, como las instalaciones personalizadas o las realizadas fuera de un gestor de paquetes. Solo están disponibles en las versiones del sensor que se indican a continuación:

  • Windows (sensor Falcon 7.40 o posterior): comprobación de los ejecutables de PHP en todo el sistema, en la unidad predeterminada. Esto revela instalaciones adicionales de PHP que las comprobaciones existentes quizá no detecten, incluidas las instalaciones personalizadas.
  • Linux (sensor Falcon 7.31 o posterior): una revisión de los procesos en ejecución para identificar instalaciones de PHP realizadas fuera del gestor de paquetes esperado.

Los clientes que usan versiones anteriores del sensor siguen cubiertos por el gestor de paquetes y las comprobaciones específicas de aplicaciones. No dispondrán de las comprobaciones adicionales de todo el sistema y de los procesos en ejecución hasta que actualicen el sensor.

Figura 2. Identificación de versiones vulnerables de PHP en Falcon® Exposure Management

Identificación de amenazas con Next-Gen SIEM

Panel de Falcon Next-Gen SIEM: respuesta a amenazas emergentes

La cobertura de detección de CrowdStrike Falcon® Next-Gen SIEM para esta amenaza (la nueva sección “Vulnerabilidades de ejecución remota de código en PHP (CVE-2026-109618/109619)” del panel de CrowdStrike Falcon: respuesta a amenazas emergentes) ya está disponible en producción como parte de una versión de los paquetes de contenido de NG-SIEM.

El panel de respuesta a amenazas emergentes es una herramienta especializada diseñada para ofrecer a los clientes de CrowdStrike visibilidad inmediata de las campañas activas o las vulnerabilidades recién descubiertas que podrían afectar su entorno. Al aprovechar la amplia telemetría de los módulos de la plataforma Falcon y los datos de terceros incorporados en Falcon® Next-Gen SIEM, este panel ofrece consultas de detección y cacería listas para usar que permiten a los equipos de seguridad determinar rápidamente su exposición a amenazas e identificar los activos posiblemente afectados.

  • Consulta Next-Gen SIEM > Gestión de registros > Paneles

Figura 3. Identificación del panel de respuesta a amenazas emergentes en Falcon Next-Gen SIEM

Plantillas de reglas de correlación de Falcon Next-Gen SIEM

Hemos agregado una nueva plantilla de reglas de correlación (CRT) de Falcon Next-Gen SIEM para detectar y responder a las vulnerabilidades CVE-2026-109618 y CVE-2026-109619. Estas reglas brindan cobertura frente a los intentos de explotación relacionados con estas vulnerabilidades. 

Genérico - Web - Agotamiento de memoria de PHP durante el inicio de la solicitud (CVE-2026-109618 y CVE-2026-109619)

Esta detección identifica errores fatales de agotamiento de memoria de PHP que ocurren antes de que comience a ejecutarse un script. En concreto, se centra en un patrón característico que aparece en los registros de errores del servidor web: el mensaje “Allowed memory size of N bytes exhausted”, acompañado de “in Unknown on line 0”. El indicador “Unknown on line 0” permite distinguir los errores que se producen durante la fase de inicialización de los numerosos errores de memoria que ocurren durante la ejecución de scripts. Esto proporciona a los equipos de defensa una señal más confiable de que algo está provocando que el intérprete de PHP agote la memoria antes de que se ejecute cualquier código de la aplicación. Patrones como este pueden indicar intentos de explotación del motor de PHP. Los clientes pueden consultar la plantilla aquí ([US-1]  [US-2]  [US-3]  [EU-1]  [US-GOV-1]  [US-GOV-2]).

Conclusión y recomendaciones

CrowdStrike supervisa activamente la evolución de esta situación y proporcionará actualizaciones a medida que disponga de nueva información. En paralelo, seguimos desarrollando e implementando nuevas lógicas de detección basadas en el comportamiento en la plataforma Falcon para detectar y prevenir actividades maliciosas relacionadas con las vulnerabilidades de PHP divulgadas, CVE-2026-109618 y CVE-2026-109619.

Recomendamos a los clientes identificar todos los sistemas que ejecuten versiones vulnerables de PHP y aplicar las actualizaciones de seguridad más recientes de acuerdo con las recomendaciones oficiales de PHP o del proveedor correspondiente.