Mi composer.lock conoce el hash exacto del commit de un helper de cadenas del tamaño de left-pad que añadí hace tres años. No tiene ni idea de qué build de PHP lo va a ejecutar. El 21 de octubre ese hueco dejó de ser una rareza filosófica: el proyecto PHP publicó 8.5.12, 8.4.27, 8.3.36 y 8.2.35 para cerrar CVE-2026-109618 y CVE-2026-109619, ambos con un 9.2 en CVSS v4.0 y ambos explotables sin iniciar sesión. Creo que la lección para quienes escribimos las aplicaciones es tajante. El intérprete es una dependencia, y deberíamos versionarlo, desplegarlo y revertirlo con la misma disciplina que dedicamos a nuestro directorio vendor.

Fíjate en quién quedó expuesto. El aviso menciona PHP-FPM, Apache con mod_php, php-cgi, FrankenPHP y LiteSpeed, y señala mod_php como el peor caso porque una petición hostil llega al código vulnerable antes incluso de que se haya cargado tu front controller. Tu middleware de Laravel nunca llega a ejecutarse. Tu firewall de Symfony tampoco. El status.php de 40 líneas que un proveedor de monitorización te pidió dejar en la raíz web en 2019 es un punto de entrada tan bueno como tu flujo de pago. No voy a repasar los detalles internos aquí, los avisos en GitHub lo explican mejor que yo, pero en resumen: un fallo está en cómo el motor aborta cuando se queda sin memoria a mitad de una petición, y el otro en restos que un worker reciclado debería haber borrado entre dos visitantes.

Ese segundo escuece un poco, porque toca justo lo que le contamos a todo recién llegado a PHP: cada petición empieza de cero. Esa promesa siempre se refirió a nuestras variables de userland, y ahí sigue siendo cierta. Por debajo, un hijo de FPM es un proceso que puede atender diez mil peticiones antes de que pm.max_requests lo retire, y el motor es lo único que se interpone entre el visitante 4812 y lo que haya dejado atrás el visitante 4811. Ajustamos ese parámetro por las fugas de memoria de alguna librería de imágenes. Pocos lo vimos alguna vez como un control de seguridad, y la verdad es que sigue sin serlo; solo el binario parcheado lo es.

Ahora el contraargumento, y es sólido. La mayoría ejecutamos PHP desde Debian, Ubuntu, RHEL o el panel de control de un hosting precisamente para que otro se encargue de esto. Los equipos de seguridad de las distribuciones aplican backports de los parches en paquetes cuya cadena de versión parece congelada en el tiempo, y esa es la única razón por la que las máquinas con PHP 8.1 o anterior tienen alguna salida ahora que upstream ha dejado de parchearlas. Un equipo de plataforma que gestiona el runtime de toda la flota puede parchear doscientos servicios en una tarde, mientras que doscientos equipos de aplicación actualizando cada uno su Dockerfile tardarían dos semanas y un hilo de Slack que nadie lee. Centralizar el runtime es una ganancia real de seguridad, y no quiero restarle importancia.

Donde sigo quedándome en el otro bando es en el reinicio. Actualizar un paquete en disco no hace nada por un master de FPM que lleva en marcha desde agosto; el código viejo sigue mapeado en memoria hasta que alguien recarga el pool o reinicia Apache. En el modelo centralizado, ese paso suele caer en la grieta entre «ops instaló la actualización» y «el despliegue es cosa de los devs», y el panel muestra orgulloso la nueva versión del paquete mientras los procesos en ejecución dicen otra cosa. Cuando el runtime viaja dentro de tu imagen, construida desde tu repo, un parche es simplemente un despliegue: contenedor nuevo, el viejo drenado, todos los workers recién arrancados por diseño. Además tienes un diff que revisar y un botón para revertir si 8.4.27 tropieza con alguna extensión que habías olvidado que cargas.

El calendario hace que el argumento sea más difícil de esquivar. El equipo de inteligencia de amenazas de Amazon dice que su honeypot MadPot detectó un intento de exploit de PHP2Shell el 17 de octubre, cuatro días antes de la publicación, y que vio más tráfico de pruebas de concepto en torno a la divulgación desde infraestructura dispersa. Amazon no pudo decir si ese primer intento habría funcionado contra un servidor real. Aun así, si tu vía para actualizar el runtime necesita un ticket, un comité de cambios y una ventana de mantenimiento, cuatro días es más o menos lo que se tarda en encontrar hueco para la reunión. Los investigadores a quienes se atribuye haber empujado estos fallos hacia un arreglo urgente, Calif, OpenAI y Wiz, no serán los últimos en encontrar algo así con herramientas que no paran de acelerarse.

Mientras no puedas desplegar, merece la pena aplicar las medidas provisionales del propio proyecto, siempre que nadie las confunda con la solución. Desactiva session.upload_progress.enabled y, en 8.5, apaga fatal_error_backtraces; luego reinicia de verdad los workers para que el cambio en el ini surta efecto. Mantén pequeños los cuerpos de las peticiones en el proxy. Y resiste la tentación ingeniosa de limitar la memoria del proceso con RLIMIT_AS o RLIMIT_DATA, porque el aviso advierte expresamente que eso puede llevar al motor directo al camino de fallo que el parche elimina. Cloudflare y AWS han puesto reglas de WAF por delante, lo que te da tiempo para el tráfico que pasa por ellos y nada para una IP de origen a la que alguien pueda llegar directamente.

Así que esto es lo que me gustaría saber de ti, sobre todo si gestionas más de un puñado de servicios. ¿La versión de PHP vive en tu repositorio, fijada en un Dockerfile o en una restricción de plataforma de composer que un pull request puede cambiar, o pertenece a un equipo de plataforma o de hosting que la parchea por debajo sin que intervengas? Y si es lo segundo, cuando salió la versión del 21 de octubre, ¿cuánto tardaron los procesos que realmente atienden tu tráfico en ejecutar el código nuevo, medido en horas y no en lo que dice la salida del gestor de paquetes?