Volver al Blog

Dos horas de una web PHP a los hashes del dominio

Dos horas de una web PHP a los hashes del dominio

El 21 de octubre, CISA publicó una recomendación que no se escribe a la ligera: quien no pueda parchear de inmediato debería «retirar de la red los sistemas basados en PHP hasta que puedan parchearse o actualizarse». Apagar la web. Al menos un gobierno europeo lo hizo. Esta web que estás leyendo también corre sobre PHP, así que el aviso nos llegó a casa igual que a todo el mundo. Lo que sigue no discute si hay que parchear —hay que parchear, y punto—. Va de lo que pasó en las dos horas siguientes a una de esas intrusiones, porque en esas dos horas ya no estaba fallando PHP.

Las dos horas

La Unidad 42 de Palo Alto Networks cuenta un caso concreto: una entidad gubernamental europea, comprometida a través de estos fallos. Lo que hicieron los atacantes dentro de las dos primeras horas desde la explotación inicial fue hacerse con hashes de contraseñas y secretos de MFA, levantar un túnel de retransmisión NTLM hacia el Active Directory e intentar escalar a administrador de dominio. Dos horas desde «alguien ha entrado por la web» hasta «alguien está probando a ser dueño del directorio».

Hay un detalle del mismo informe que merece una lectura aparte, y no es decorativo: los operadores usaron plataformas de IA agéntica durante la post-explotación, con comandos que llevaban delimitadores del tipo ==SHELLUSER== para que la salida fuese fácil de leer por un modelo, en tareas cortas y espaciadas con cambios rápidos de rumbo tras cada fallo. Dicho sin dramatismo: la parte lenta de un ataque —probar, leer, decidir el siguiente paso— es justo la que mejor se automatiza. Si tu plan de respuesta asume que entre la entrada y el movimiento lateral hay una noche de margen, ese supuesto es el que acaba de quedarse viejo.

Y ahora la pregunta que da título a este post, que es la que nos interesa de verdad: ¿por qué un servidor web podía llegar al Active Directory? Esa parte no la abrió el fallo de PHP. Estaba abierta antes.

Qué son CVE-2026-109618 y CVE-2026-109619

Los dos avisos están publicados por el propio proyecto PHP, los dos con CVSS v4.0 de 9,2 y el mismo vector CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. Los bautizó PHP2Shell watchTowr y el nombre ha cuajado; los nombres no parchean nada, pero ayudan a buscar.

  • CVE-2026-109618 — ejecución de código por corrupción al quedarse sin memoria. PHP usa longjmp() cuando se agota la memoria. Si la reserva falla durante la inicialización de la petición, la salida anticipada deja el estado a medio montar, y el procedimiento de cierre procesa esa memoria corrupta. Las vías de ataque que describe el aviso son peticiones multipart sin longitud declarada que se saltan post_max_size, secciones con cabeceras ilimitadas que agotan el memory_limit y controles de fichero vacíos que esquivan max_file_uploads.
  • CVE-2026-109619 — ejecución de código por estado global rancio. Estado que pertenece a una petición sobrevive en el proceso trabajador y lo lee una petición posterior, antes de que la inicialización normal lo reponga. El aviso lo dice sin rodeos: «una disposición del heap estratégicamente preparada puede producir ejecución remota de código completa». Hace falta una operación que guarde ese estado y otra que lo consuma.

Un matiz honesto, de los que casi nunca se citan porque restan titular: ese vector lleva AC:H y AT:P, es decir, complejidad alta y condiciones de ataque que tienen que darse. No es un curl y dentro. Eso no cambia la conclusión —se parchea igual, y rápido—, pero sí cambia el tono con el que se habla del asunto en una reunión: no es magia instantánea, es trabajo de explotación con premio muy alto.

La cronología va al revés

Lo normal es: el fabricante parchea, luego se publican los detalles, luego aparece el exploit. Aquí el orden fue otro. El 20 de octubre los detalles se filtraron antes de la divulgación coordinada, con el parche todavía sin existir; watchTowr fue de los primeros en confirmar públicamente que la filtración era genuina. Ese mismo día el equipo de seguridad de PHP anunció una publicación de emergencia para el 21 de octubre a las 14:00 UTC. El 21 salieron las versiones corregidas, y ese mismo 21 la red de señuelos de watchTowr observó explotación de ambos fallos.

Conviene decir en voz alta una tensión entre fuentes, porque va a aparecer en cualquier conversación seria sobre esto. CISA, en su alerta del 21 actualizada el 23, afirma que no ha observado explotación exitosa en el momento de la publicación. Unit 42, en cambio, documenta el caso del gobierno europeo con post-explotación detallada, y watchTowr ve intentos en sus señuelos. No son necesariamente contradictorias —ver intentos en un señuelo no es lo mismo que confirmar una intrusión exitosa, y las ventanas de observación no coinciden—, pero tampoco las vamos a maquillar: nadie de fuera puede resolverlas, y el que escribe la política de parcheo no debería esperar a que alguien lo haga. A fecha de hoy, 24 de octubre, por cierto, ninguno de los dos CVE figura en el catálogo KEV de CISA. Si tu proceso de urgencia se dispara solo con el KEV, este es el día de descubrir que ese disparador llega tarde.

El parche existe para cuatro ramas

Las versiones corregidas son 8.5.12, 8.4.27, 8.3.36 y 8.2.35. Cuatro ramas. Ni una más. Y aquí es donde el incidente deja de ser una noticia de seguridad y pasa a ser una factura de deuda técnica, porque según los datos de W3Techs del 24 de octubre de 2026, de todos los sitios que usan PHP el 26,8% corre la rama 7, el 7,7% la rama 5 y un 0,1% todavía la 4. Son al menos 34,6 de cada 100 sitios con PHP en una rama que no tiene parche ni lo va a tener, y el grueso de ese 26,8% es 7.4: el 73,4% de los PHP 7 medidos. Decimos «al menos» porque la cuenta real es peor. Dentro del 65,4% que corre la rama 8, un 15,1% está en 8.1 y un 5,8% en 8.0, las dos sin soporte: son casi 14 puntos más. Sumado, cerca de la mitad del parque de PHP no tiene una versión corregida que instalar.

Rama Situación hoy (24-oct-2026) Qué toca hacer
8.5Soporte activo hasta el 31-dic-2027; seguridad hasta el 31-dic-2029Actualizar a 8.5.12
8.4Soporte activo hasta el 31-dic-2026; seguridad hasta el 31-dic-2028Actualizar a 8.4.27
8.3Solo correcciones de seguridad, hasta el 31-dic-2027Actualizar a 8.3.36
8.2Solo seguridad, y acaba el 31-dic-2026: dos mesesActualizar a 8.2.35 y planificar el salto ya
8.1Sin soporte desde el 31-dic-2025No hay parche. Saltar de rama, y es un proyecto, no un apt upgrade
8.0 / 7.x / 5.xSin soporte desde 2023, 2022 o antes (7.4 cerró el 28-nov-2022)No hay parche, y llevas años acumulando los anteriores

Esta es la forma que más nos encontramos y de la que ya escribimos en su día: tu política de parcheo no contempla que no haya parche. El procedimiento entero asume que existe un paquete que instalar. Cuando no existe, el documento se queda mudo y la decisión sube a dirección en caliente, un miércoles, con el reloj en marcha. Escribir esa rama del procedimiento un día tranquilo cuesta una hora.

Actualizar el paquete no es haber parcheado

El segundo fallo vive en el estado que el trabajador conserva entre peticiones; el primero, en la inicialización de una sola. Pero los dos comparten una consecuencia práctica que se olvida cada vez: mientras los trabajadores viejos sigan en memoria están ejecutando el código viejo, así que siguen siendo vulnerables aunque el paquete nuevo lleve media hora en el disco. El aviso de CVE-2026-109619 lo repite en las dos mitigaciones de configuración que propone —aplicar el ajuste y reiniciar los trabajadores—. Si gestionas una flota, la comprobación posterior no es «qué versión dice el paquete», es qué versión está ejecutando el proceso que atiende el puerto 443, y desde cuándo está vivo.

La exposición tampoco es igual en todas partes, y conviene leer los dos avisos por separado porque no dicen lo mismo. En el primero, con Apache y mod_php la cosa está confirmada: el cierre se invoca aunque el arranque haya fallado, y ahí está la ejecución de código; con PHP-FPM el hijo termina si falla en el arranque, pero un agotamiento de memoria posterior sigue siendo potencialmente explotable, y FrankenPHP es probablemente susceptible por analogía con mod_php. En el segundo, el caso confirmado vuelve a ser mod_php —por el rastreo de error fatal y por el progreso de subida de sesión—, mientras que FPM queda expuesto de forma condicional: cuando el frontal le pasa parámetros PHP_VALUE o PHP_ADMIN_VALUE que generan un diagnóstico antes de inicializar la petición. De FrankenPHP dice que no encontró un consumidor equivalente en su configuración por defecto, y añade, con una honestidad que agradecemos, que eso no lo hace inmune; LSAPI figura como candidato condicional. Qué servidor usas deja de ser una preferencia de administrador y pasa a ser un dato de la evaluación de riesgo, igual que la exposición real la decide tu php.ini cuando hablamos de WordPress.

Las mitigaciones que no mitigan

Aquí hay una perla que merece un minuto. La mitigación temporal que propone el aviso de CVE-2026-109618 es poner memory_limit=-1: quitar el límite de memoria. Justo el ajuste que llevamos quince años recomendando apretar. El motivo es que ese límite predecible por petición es lo que hace cómoda la explotación, y sin él el atacante pierde su reloj. Y no basta: el aviso lo dice con esas palabras —«esto no es una mitigación completa», porque si falla mmap() u otra reserva de bajo nivel, PHP puede volver a usar longjmp() con memoria de petición incompleta al alcance—. Actualizar es, literalmente, «la única mitigación completa». El mismo aviso pide además un post_max_size conservador en el frontal, bajar max_input_nesting_level y acotar variables, partes, cabeceras y subidas. Y aquí viene lo que remata la lección: dice que NO se usen RLIMIT_AS ni RLIMIT_DATA, porque hacen fallar mmap() al llegar al límite y disparan justo el camino vulnerable. Dos controles que cualquier auditoría puntuaría como buenos, jugando a favor del atacante. Un endurecimiento no es bueno en abstracto; es bueno contra un ataque concreto, y a veces juega en contra.

Para el segundo fallo el aviso ofrece fatal_error_backtraces=0 en PHP 8.5 y session.upload_progress.enabled=0, ambos con reinicio de trabajadores, y advierte de que cada uno cubre solo el caso demostrado. Y luego está la frase que debería cerrar el debate en cualquier comité: «no hay una mitigación de configuración genérica completa», porque distintas rutas de aplicación o de extensión pueden producir o consumir estado rancio de forma impredecible. Traducido: si alguien propone aguantar con ajustes hasta la ventana de mantenimiento de dentro de tres semanas, el propio fabricante ya ha contestado que no.

Dónde corre PHP sin que lo sepas

Cortex Xpanse, de Palo Alto, cifraba en más de 22,6 millones las instancias expuestas potencialmente vulnerables en el momento de publicar su informe. Ese número no sale de los servidores web que tú y yo tenemos apuntados en una hoja: sale de todo lo demás. Un panel de administración de hosting. Un sistema de tickets heredado que nadie toca desde que se fue quien lo montó. El interfaz web de una cabina, de un SAI, de una impresora departamental. Un contenedor con una imagen base de 2021 que se reconstruye solo cuando alguien cambia el código, y el código no se toca desde hace dos años. Ese es el inventario que decide tu exposición real, y casi nunca coincide con el inventario oficial.

Por qué una web llega al directorio

Volvamos a las dos horas, que es lo que de verdad hay que arreglar. Para que un servidor web comprometido saque hashes del dominio y monte una retransmisión NTLM, tienen que darse cosas que no son vulnerabilidades: son decisiones. En lo que nos encontramos, casi siempre son estas tres. Una, el frontal está unido al dominio —normalmente para que la gente entre con sus credenciales de siempre, que es una razón buena con una consecuencia mala—. Dos, el frontal comparte segmento con servidores internos, así que alcanza el 445 y el 389 del controlador sin que nadie se lo haya autorizado explícitamente. Tres, hay credenciales guardadas en texto en un fichero de configuración que el proceso web puede leer, porque leerlas es literalmente su trabajo.

Ninguna de las tres aparece en un escáner de vulnerabilidades. Las tres son el motivo por el que la hora dos fue mucho peor que la hora cero. Y las tres se arreglan con trabajo de arquitectura aburrido: una cuenta de servicio sin privilegios en el directorio, un segmento propio para el frontal con reglas de salida explícitas hacia los puertos que de verdad necesita, y los secretos fuera del disco del servidor web. No es glamuroso y no sale en ninguna alerta roja. Es, exactamente, la diferencia entre un fallo y una avería.

Cuándo NO apagar la web

El consejo de CISA es correcto y está bien dirigido, y conviene leerlo entero antes de repetirlo en una reunión, porque tiene dos destinatarios: quien no puede parchear de inmediato y, textualmente, quien siga en PHP 8.1 o anterior, sin soporte. Ese segundo grupo es el 20,9% de la rama 8 que acabamos de contar, y para ellos no es un consejo temporal: mientras no cambien de rama, no hay parche que esperar. Lo que no es la frase es una recomendación general de apagar por si acaso. Desconectar tiene su propia factura: una tienda parada no vende nada, un portal de clientes caído genera llamadas que también cuestan dinero y, si el apagón dura, aparecen atajos —«pásame el pedido por correo»— que abren agujeros peores que el que querías cerrar. Nuestra forma de decidirlo es mirar dos cosas antes del botón rojo: si puedes parchear en menos de una hora, parchea y no apagues nada; si no puedes, apaga lo que esté expuesto a Internet y no haga falta para facturar hoy, que casi nunca es todo. Entre «todo encendido» y «todo apagado» hay un inventario con prioridades, y ese inventario se escribe antes, no a las nueve de la noche de un miércoles.

La comprobación de veinte minutos

Siete pasos, en este orden. Los cuatro primeros son de hoy; los tres últimos son los que evitan la hora dos del próximo fallo, que llegará con otro nombre.

  1. Pregunta a la versión que atiende peticiones, no al paquete. Una página con phpversion() detrás de autenticación, o php-fpm -v en el servidor, dicen la verdad; php -v en la línea de comandos a veces es otro binario. Si no empieza por 8.5.12, 8.4.27, 8.3.36 u 8.2.35, no has terminado.
  2. Reinicia los trabajadores y comprueba que han muerto de verdad. systemctl restart php*-fpm y luego mira la antigüedad de los procesos: si siguen vivos desde antes del parche, siguen siendo vulnerables. Con mod_php, el que tiene que reiniciar es Apache.
  3. Barre tus rangos antes de fiarte del inventario. Una pasada buscando cabeceras X-Powered-By, cookies PHPSESSID y rutas .php en todo lo que escucha, incluidas las redes de gestión. Lo que aparezca y no estuviera en la hoja es, precisamente, lo que nadie va a parchear.
  4. Si tu rama no tiene parche, decide hoy y escríbelo. Con 8.1 o anterior no hay actualización menor que te salve: o saltas de rama —y eso se prueba, porque rompe cosas—, o aíslas ese sistema de Internet mientras tanto. Lo que no vale es dejarlo en el aire y llamarlo «pendiente de evaluar».
  5. Mira con qué cuenta corre tu servidor web en el dominio. Si es una cuenta de dominio, revisa qué puede hacer: pertenencia a grupos, permisos de lectura en el directorio, si puede autenticarse contra otros servidores. Lo que no necesite, fuera.
  6. Comprueba qué alcanza tu frontal hacia dentro. Desde el propio servidor web, intenta abrir el 445 y el 389 de un controlador de dominio. Si conectan, acabas de reproducir el primer paso del movimiento lateral con tus propias manos y un día tranquilo. Las reglas de salida de ese segmento son el arreglo.
  7. Decide quién mira el servidor web a las tres de la mañana. Dos horas de margen no las cubre un informe semanal. O hay alguien de guardia con capacidad de aislar la máquina, o el plan consiste en enterarse el jueves. Las dos opciones son legítimas; solo una de ellas suele estar escrita.
Lo que pasó ¿Lo arregla el parche de PHP?
Ejecución de código en el proceso webSí — si además reinicias los trabajadores
El proceso web podía leer credenciales del fichero de configuraciónNo. Eso es gestión de secretos
El frontal alcanzaba el controlador de dominioNo. Eso es segmentación
Nadie se enteró durante dos horasNo. Eso es detección y respuesta
Tu rama de PHP no tiene versión corregidaNo existe. Eso es ciclo de vida

Dónde encaja esto en lo que hacemos

Nosotros mantenemos infraestructura de otros, y esta web corre en PHP: el miércoles 21 fue un día de trabajo como el de cualquiera que lea esto. Lo que ponemos en ciberseguridad no es un producto que tape agujeros de PHP —eso no existe—, es la parte aburrida: saber qué versión corre cada cosa, tener forma de parchear una flota el mismo día sin romper nada, y que el frontal web no sea un vecino con llave del directorio. La capa de infraestructura y cloud es donde se decide lo segundo, y se decide el día del diseño, no el día del incidente.

Si quieres que hagamos juntos el barrido del paso 3 y la prueba del paso 6 en tu casa —qué PHP tienes de verdad y qué alcanza tu frontal hacia dentro—, hablamos. Es media mañana. Si sale que estaba todo parcheado y bien segmentado, te lo decimos y tan contentos.

Fuentes. Las identificaciones, la puntuación CVSS v4.0 de 9,2, el vector, las versiones afectadas y corregidas (8.2.35, 8.3.36, 8.4.27 y 8.5.12), la descripción técnica de ambos fallos, la susceptibilidad por SAPI (mod_php confirmado; PHP-FPM condicional con ejecución confirmada por el ciclo de vida de sesión; FrankenPHP probable; LSAPI candidato condicional), las mitigaciones temporales y sus límites (memory_limit=-1 y la advertencia de que «esto no es una mitigación completa»; actualizar como «la única mitigación completa»; post_max_size conservador, max_input_nesting_level y la advertencia de NO usar RLIMIT_AS ni RLIMIT_DATA; fatal_error_backtraces=0 y session.upload_progress.enabled=0 con reinicio de trabajadores) y la frase sobre que no hay una mitigación de configuración genérica completa proceden de los avisos publicados por el proyecto PHP: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) y GHSA-6wmf-4rv5-p3rp (CVE-2026-109619). La recomendación de actualizar o retirar de la red los sistemas basados en PHP —dirigida textualmente a quien no pueda actualizar de inmediato «o que siga usando PHP 8.1 o versiones anteriores sin soporte»—, la afirmación de no haber observado explotación exitosa en el momento de la publicación y la comprobación de que ninguno de los dos CVE figura en el catálogo KEV (versión 2026.10.23) están en la alerta de CISA del 21 de octubre de 2026, actualizada el 23. El caso de la entidad gubernamental europea (hashes de contraseñas y secretos de MFA, túnel de retransmisión NTLM hacia Active Directory e intento de escalada a administrador de dominio dentro de las dos primeras horas; uso de plataformas de IA agéntica y delimitadores del tipo ==SHELLUSER==), la cifra de más de 22,6 millones de instancias expuestas medida por la telemetría de Cortex Xpanse —telemetría propia de un fabricante, como la de W3Techs es una muestra— y el hecho de que al menos un gobierno europeo retirase su web de forma preventiva están en el informe de la Unidad 42 de Palo Alto Networks. La cronología de la filtración del 20 de octubre antes de la divulgación coordinada, el anuncio de la publicación de emergencia para el 21 a las 14:00 UTC y la observación de explotación en su red de señuelos están en el análisis de watchTowr. Las fechas de soporte y de fin de vida de cada rama proceden de php.net/supported-versions y php.net/eol. Los porcentajes de uso por rama (65,4% la 8, 26,8% la 7, 7,7% la 5, 0,1% la 4; 73,4% de 7.4 dentro de la rama 7; y 15,1% de 8.1 y 5,8% de 8.0 dentro de la rama 8), medidos el 24 de octubre de 2026 sobre los sitios que usan PHP, son de W3Techs; como toda medición de ese tipo, depende de su muestra y conviene leerla como orden de magnitud. Las traducciones de las citas son nuestras. Son criterio nuestro, y no afirmaciones de ninguna de esas fuentes: la lectura de las dos horas como problema de arquitectura y no de PHP, las tres causas habituales de que un frontal alcance el directorio, el matiz sobre la complejidad alta del vector, la forma de decidir si apagar o no, y la comprobación de veinte minutos. everyWAN presta servicios gestionados de infraestructura y seguridad, y esta web corre sobre PHP: el conflicto de interés queda declarado. Foto de portada: armario de comunicaciones mural con conmutador, router y módem, dominio público (CC0), banco de medios de WordPress Photos.

Etiquetas: Ciberseguridad PHP Vulnerabilidades Parcheo Identidad

Suscríbete a nuestra newsletter

Para recibir historias del mundo IT, novedades de everyWAN y ofertas exclusivas para suscriptores, date de alta a nuestra lista de correo

everyWAN
everyWAN