todos los sistemas operativostodo operativo

apenaloza.cl · bitácora

SCL (UTC−3) · v2026.10

~/blog/php2shell-cve-2026-109618-cve-2026-109619-mitigacion.md

PHP2Shell: qué son CVE-2026-109618 y CVE-2026-109619 y cómo proteger tus servidores PHP

Dos fallas críticas en PHP permiten ejecutar código remoto sin autenticación, antes de que corra tu aplicación. Qué versiones afectan, quién está expuesto, qué cubre el WAF de Cloudflare, cómo actualizar y qué mitigar mientras tanto.

autor
Alejandro Peñaloza
Publicado
Lectura
13 min
Portada de PHP2Shell: elefante de PHP hecho de líneas violetas, partido por una grieta roja con un prompt de terminal verde, junto a los CVE-2026-109618 y CVE-2026-109619
En este artículo
  1. Resumen rápido
  2. Qué es PHP2Shell
  3. Quién está expuesto
  4. ¿Se está explotando?
  5. Qué hacer ahora: actualizar
  6. Si tu sitio está detrás de Cloudflare
  7. Mitigaciones temporales (si no puedes actualizar hoy)
  8. Detección: qué mirar en tus servidores
  9. Checklist
  10. Referencias

Actualizado el 21 de octubre de 2026 (tarde): PHP publicó backports oficiales para 7.4, 8.0 y 8.1 (más abajo explico qué significa), amplió las mitigaciones (límites de memoria por contenedor, errores fatales provocables, peticiones sin Content-Length) y aclaró que memory_limit=-1 no cierra la falla por completo. También sumé el estado de explotación según Check Point y Patchstack.

El 21 de octubre de 2026, a las 14:00 UTC, el equipo de PHP publicó versiones de emergencia para corregir dos vulnerabilidades críticas que, juntas, la comunidad bautizó como PHP2Shell. Los detalles se filtraron antes de la divulgación coordinada, ya circulan pruebas de concepto públicas y Cloudflare reporta tráfico de ataque contra sus clientes. Si tienes cualquier servidor PHP atendiendo tráfico web, esto es para hoy.

En este artículo resumo qué se sabe con fuentes oficiales, a quién afecta, cómo actualizar, qué cubre el WAF de Cloudflare y qué hacer si no puedes actualizar de inmediato.

Resumen rápido

CVE-2026-109618 CVE-2026-109619
Nombre oficial RCE via OOM corruption RCE via stale global state
Severidad Crítica, CVSS v4.0 9.2 Crítica, CVSS v4.0 9.2
Impacto Ejecución remota de código sin autenticación Ejecución remota de código sin autenticación
Aviso de PHP GHSA-w8hw-w9fx-j79w GHSA-6wmf-4rv5-p3rp
NVD CVE-2026-109618 CVE-2026-109619

Las publicaciones filtradas hablaban de CVSS 10.0; la puntuación oficial es 9.2, igual de crítica en la práctica.

Versiones corregidas: PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35 (anuncio oficial).

PHP 8.1, 8.0 y 7.4: no hay versión nueva, pero el equipo de seguridad de PHP publicó parches mínimos de código fuente (backports). Más abajo explico qué significa eso en la práctica.

PHP 7.3 y anteriores: el aviso de PHP no las menciona. Algunos reportes dicen que la falla afecta desde PHP 5, sin confirmación oficial: asume que son vulnerables y que no hay parche.

Qué es PHP2Shell

No es un bug de un framework ni de una librería: está en el propio motor de PHP, en cómo maneja la memoria y el estado de cada petición. Por eso afecta a Laravel, Symfony, WordPress o PHP puro por igual.

CVE-2026-109618: corrupción tras quedarse sin memoria

Según el aviso de PHP, cuando PHP se queda sin memoria (OOM) puede interrumpirse mientras los datos internos de la petición están a medio inicializar. Después, la limpieza de la petición trata esos datos inválidos como válidos. El análisis de peticiones multipart/form-data aparece como uno de los disparadores.

CVE-2026-109619: estado global que sobrevive a la petición

Parte del estado interno que pertenece a una petición puede sobrevivir después de que se libera su memoria. Una petición posterior atendida por el mismo proceso (worker) puede usar ese estado obsoleto antes de que la inicialización normal lo reinicie. Las rutas confirmadas pasan por session.upload_progress y, en PHP 8.5, por los backtraces de errores fatales.

Las dos se corrigieron en el núcleo con un mismo cambio, descrito en el changelog como "Avoid state corruption via bailout".

Quién está expuesto

PHP indica que el riesgo aplica a todas las instalaciones que atienden peticiones web:

  • PHP-FPM (detrás de Nginx, Caddy, etc.)
  • Apache con mod_php
  • php-cgi
  • FrankenPHP
  • LiteSpeed

PHP considera el riesgo más alto en mod_php, donde existe un exploit demostrado en que una petición sin autenticar dispara el ataque antes de que se ejecute cualquier línea de tu aplicación. Es decir: no importa lo bien escrito que esté tu código, ni tu middleware de autenticación.

Los scripts de línea de comandos (php CLI, colas, cron) no atienden peticiones web y no aparecen en los avisos como vector, pero el binario es el mismo: al actualizar, actualiza todo.

¿Se está explotando?

Las fuentes siguen sin coincidir, así que lo dejo tal cual:

  • Cloudflare dice haber observado tráfico de ataque contra sus clientes, con atacantes mutando el payload para intentar saltarse las reglas (blog de Cloudflare).
  • watchTowr reporta explotación observada desde el 21 de octubre en su red de honeypots (FAQ).
  • Patchstack también reportó explotación activa, aunque sin publicar detalles.
  • Check Point no tiene reportes de explotación en entornos reales por ahora (blog de Check Point).
  • NCSC-NL no tiene indicios de explotación activa, pero confirma que hay pruebas de concepto públicas (repositorio).
  • CISA KEV: al momento de esta actualización, ninguna de las dos aparece en el catálogo.

Con un exploit público y una falla que no requiere autenticación, conviene asumir que los escaneos masivos ya empezaron.

Qué hacer ahora: actualizar

Es la única solución completa. El propio anuncio de cada versión lo dice así: no hay mitigaciones completas, actualizar es la única opción segura. Primero revisa qué versión corre de verdad (la CLI y FPM pueden ser distintas):

php -v
php-fpm -v

Según la distro el binario puede llamarse php-fpm8.3 o similar. En un contenedor, esto te dice lo que atiende el tráfico web:

docker compose exec app php -r 'echo PHP_VERSION, PHP_EOL;'

Tienes que estar en 8.5.12, 8.4.27, 8.3.36 u 8.2.35 o superior dentro de tu rama.

Debian y Ubuntu

sudo apt update
sudo apt install --only-upgrade 'php8.3*'
sudo systemctl restart php8.3-fpm

Si usas mod_php, reinicia también apache2. Las distribuciones y los proveedores de soporte extendido recibieron los parches por adelantado, así que sus paquetes deberían estar disponibles. Si usas paquetes de la distribución (y no del PPA de Ondřej Surý o similar), el número de versión puede no coincidir con el de php.net porque las distros aplican el parche sobre su versión (backport). En ese caso verifica en el aviso de seguridad de tu distro que el paquete instalado incluye CVE-2026-109618 y CVE-2026-109619.

RHEL, Rocky, Alma y Fedora con remirepo

Si dependes de rpms.remirepo.net, ojo: es un repositorio mantenido por voluntarios y no tendrá la actualización el mismo 21 de octubre. PHP recomienda pasarse a los paquetes de la distribución o compilar desde el código fuente para poder parchar de inmediato.

Docker

Si tu imagen parte de php:8.x-fpm o similar, no basta con reiniciar: hay que reconstruir con la imagen base nueva y desplegar. Fija el parche mínimo seguro en vez de una etiqueta flotante:

FROM php:8.3.36-fpm
docker compose build --pull --no-cache
docker compose up -d
docker compose exec app php -v

--pull obliga a bajar la imagen base nueva; sin eso Docker puede reutilizar la que tienes en caché.

Hosting compartido o administrado

Revisa el panel o el estado del proveedor: muchos ya están actualizando, pero confirma la versión con phpinfo() en una ruta temporal (y bórrala después).

PHP 8.1 o anterior: qué significan los "backports"

El aviso de PHP dice dos cosas que a primera vista parecen contradecirse:

PHP 8.1 and older no longer receive updates from the PHP project. Some distributions and LTS vendors provide backported fixes, so contact your vendor for details, or upgrade to a supported version.

Given the severity of these vulnerabilities, the PHP security team provided minimal source-backports for PHP 7.4, PHP 8.0, and PHP 8.1 to help people building from source or without access to an LTS vendor.

No se contradicen; hablan de cosas distintas.

La primera frase dice que PHP 8.1 y anteriores están fuera de soporte: el proyecto PHP no va a publicar versiones nuevas de esas ramas. No habrá un "PHP 8.1.x" nuevo en php.net, ni binarios para Windows, ni imágenes Docker oficiales actualizadas. Para esas versiones te deriva a tu distribución o proveedor de soporte extendido (Debian, Ubuntu Pro, Red Hat, TuxCare, etc.), que pueden mantener versiones antiguas y sacar paquetes parchados, o a actualizar a una versión con soporte.

La segunda frase es una excepción por la gravedad del caso. Tampoco es una versión nueva: es solo el parche de código fuente, el conjunto de líneas que hay que cambiar en PHP para corregir estas dos fallas.

  • "Minimal" significa que corrige solo PHP2Shell. Esas versiones siguen arrastrando todas las demás vulnerabilidades descubiertas desde que perdieron soporte.
  • "For people building from source" significa que sirve si compilas PHP tú mismo: bajas el código de PHP 8.1, le aplicas el parche y compilas.
  • "Without access to an LTS vendor" significa que está pensado para quien no tiene un proveedor que le arme el paquete.

En la práctica:

Si usas PHP 7.4, 8.0 u 8.1… Qué hacer
Instalado con apt o yum de tu distro Revisa si tu distro sacó un paquete parchado. Si no, el parche de PHP no te sirve directo.
Imagen Docker oficial php:8.1 No se va a actualizar. Migra de versión o compila tu propia imagen aplicando el parche.
Hosting compartido Depende del proveedor: pídele que actualice o que te cambie de versión.
Lo compilas tú Aplica el backport y recompila.

Las opciones, en orden de preferencia:

  1. Migrar a una rama soportada: es la solución de fondo.
  2. Usar el paquete parchado de tu distribución o de un proveedor de soporte extendido, si existe.
  3. Si compilas PHP tú mismo en 7.4, 8.0 u 8.1, aplicar los backports mínimos del equipo de seguridad de PHP.
  4. Aplicar las mitigaciones de abajo y aislar el servidor de Internet. CERT-EU recomienda sacar de la red pública las aplicaciones expuestas que no se puedan parchar (aviso 2026-018).

Y si migras, apunta a 8.3 o superior: PHP 8.2 también deja de tener soporte el 31 de diciembre de 2026.

Si tu sitio está detrás de Cloudflare

Cloudflare participó en la divulgación coordinada junto a la PHP Foundation, OpenAI y Wiz, y desplegó reglas de WAF unas 9 horas antes del anuncio público. Desde el 21 de octubre a las 03:02 UTC, los clientes con WAF, incluido el plan gratuito, quedaron protegidos por defecto (anuncio de Cloudflare).

Plan Dónde están las reglas Qué hacer
Free Cloudflare Managed Free Ruleset Nada, vienen activas
Pro, Business, Enterprise Cloudflare Managed Ruleset Confirmar que el despliegue está en modo Block
Todos Cloudflare Managed Response Firewall, regla Default block for High confidence Signatures Nada, viene activa

Las reglas se llaman Generic Rules - Anomalous Execution (1 a 7). La novedad es que usan detección de transacción completa: el WAF analiza la petición y la respuesta del mismo intercambio HTTP, y si coinciden puede bloquear la respuesta de tu servidor antes de que llegue al atacante. Según Cloudflare, eso permite reglas más certeras y con menos falsos positivos que mirando solo la petición.

Para revisar si te atacaron: Security > Security Analytics > Events, filtrando por los IDs de esas reglas (están en el anuncio).

Dos advertencias importantes:

  • El WAF no reemplaza el parche. Cloudflare lo dice explícitamente: la falla está en el runtime de PHP y el WAF es una capa adicional. Los atacantes ya están mutando payloads.
  • Solo protege el tráfico que pasa por Cloudflare. Si tu servidor de origen acepta conexiones directas, un atacante que conozca su IP se salta el WAF. Restringe el origen a las IPs de Cloudflare, o usa Cloudflare Tunnel o Authenticated Origin Pulls.

Mitigaciones temporales (si no puedes actualizar hoy)

El propio aviso de PHP es claro: estas medidas reducen la exposición a rutas de ataque conocidas, pero no dejan la instalación segura. Gina Peter Banyard, del equipo de PHP, insistió en lo mismo: desactivar session.upload_progress es una mitigación parcial que no impide la explotación. Son para ganar horas, no para quedarse.

Aplica todo en php.ini, en el pool de FPM o en la configuración del servidor, nunca con ini_set() ni con .user.ini: tienen que regir antes de que PHP lea la petición. Y reinicia todos los workers después.

Contra CVE-2026-109619

session.upload_progress.enabled = 0

Viene activado por defecto y casi ninguna aplicación lo usa (sirve para barras de progreso de subidas hechas con sesiones). Bloquea las rutas confirmadas, no todas las variantes.

En PHP 8.5 agrega además:

fatal_error_backtraces = 0

El aviso actualizado suma dos medidas más:

  • Quita configuraciones inválidas o deprecadas que se aplican por petición y que emiten avisos (diagnósticos) antes de que la petición termine de inicializarse. Ocultar esos avisos con error_reporting, display_errors o @ no sirve: hay que eliminar la configuración que los provoca.
  • Corrige los errores fatales y los timeouts que un atacante pueda provocar, incluidos los que ocurren en callbacks, en el procesamiento de subidas o importaciones, y en la limpieza.

Contra CVE-2026-109618

PHP recomienda quitar el límite de memoria por script para evitar la condición de OOM controlable que dispara la falla, fijándolo donde la aplicación no lo pueda sobrescribir. En el pool de FPM (por ejemplo /etc/php/8.3/fpm/pool.d/www.conf):

php_admin_value[memory_limit] = -1

En Apache con mod_php:

php_admin_value memory_limit -1

Revisa que ningún otro pool, directorio o archivo de la aplicación vuelva a poner un límite finito. Y ten presente lo que agrega el aviso: esto elimina el fallo provocado por el límite de memoria de PHP, pero un fallo de asignación de memoria del sistema aún puede llegar a la ruta vulnerable.

Sin límite, además, un script descontrolado puede consumir toda la RAM del servidor. Para eso PHP recomienda usar un límite de memoria del contenedor o cgroup como frontera de disponibilidad, idealmente con una política que mate solo al worker afectado. Con Docker Compose, por ejemplo:

services:
  app:
    mem_limit: 1g

Combínalo con límites de tamaño de petición:

post_max_size = 8M
upload_max_filesize = 6M

Usa 8M si no aceptas subidas de archivos y 32M si las aceptas, siempre con upload_max_filesize por debajo de post_max_size.

PHP también sugiere limitar en el servidor web o proxy el tamaño de la petición ya decodificada, la cantidad de campos y partes multipart, los headers y la profundidad de anidación. Esos límites también deben cubrir las peticiones que llegan sin un Content-Length utilizable (por ejemplo, con transferencia por partes). En Nginx, client_max_body_size se aplica también a esas peticiones:

client_max_body_size 8m;
large_client_header_buffers 4 8k;

Y no uses RLIMIT_AS ni RLIMIT_DATA (por ejemplo vía ulimit o systemd) para contener la memoria: pueden provocar justo el fallo de asignación vulnerable.

Verifica que la mitigación quedó aplicada

La CLI y FPM leen archivos distintos, así que comprueba desde el contexto web. Una ruta temporal con:

<?php
var_dump(
    ini_get('session.upload_progress.enabled'),
    ini_get('memory_limit'),
    ini_get('post_max_size'),
);

Bórrala apenas confirmes.

Detección: qué mirar en tus servidores

No hay firmas ni indicadores de compromiso públicos confiables todavía, así que la detección es por comportamiento. Lo sospechoso es que un proceso de PHP lance otras cosas:

  • Procesos padre php-fpm, php-cgi, httpd o apache2 que inician sh, bash, python, perl, curl, wget o nc.
  • Líneas de comando con /dev/tcp/, fsockopen o mkfifo (típicas de reverse shells).
  • Comandos de reconocimiento ejecutados por el usuario del servidor web: whoami, id, uname -a.

Para ver hijos de PHP o Apache que no deberían existir:

ps -eo pid,ppid,user,cmd --forest | grep -E -A3 'php-fpm|apache2|httpd' | grep -E 'sh|bash|curl|wget|nc|python|perl'

Y archivos PHP nuevos en el directorio web en los últimos 3 días:

find /var/www -name '*.php' -mtime -3 -ls

Si encuentras algo: aísla el servidor, guarda los logs del servidor web y de PHP antes de tocar nada, y busca web shells, archivos nuevos en la raíz web, tareas de cron y usuarios nuevos. En MITRE ATT&CK esto corresponde a T1190 (explotación de aplicación expuesta) y T1505.003 (web shell). Actualizar PHP en un servidor ya comprometido no saca al atacante: si hubo compromiso, reconstruye desde una imagen limpia y rota credenciales.

Checklist

  • Inventariar todos los servidores, contenedores y hostings con PHP (incluidos los olvidados: staging, demos, WordPress de marketing).
  • Confirmar la versión que atiende tráfico web, no solo la de la CLI.
  • Actualizar a 8.5.12, 8.4.27, 8.3.36 u 8.2.35 (o al paquete parchado de la distro).
  • Si usas remirepo, pasar a paquetes de la distro o compilar desde el código fuente.
  • Reconstruir imágenes Docker con --pull y fijar el parche mínimo en el Dockerfile.
  • Reiniciar FPM, Apache y todos los workers.
  • Si usas Cloudflare: reglas en modo Block y origen cerrado a conexiones directas.
  • Donde no se pueda actualizar hoy: session.upload_progress.enabled = 0, límites de memoria del contenedor y de petición, revisar errores fatales provocables, y aislar de Internet.
  • Revisar procesos hijos de PHP y archivos nuevos en la raíz web.
  • Planificar la salida de PHP 8.2 (fin de soporte el 31 de diciembre de 2026) y de 8.1 o anterior.

Referencias

Artículo escrito el 21 de octubre de 2026 y actualizado ese mismo día con la información disponible. La situación cambia rápido: revisa los avisos oficiales antes de tomar decisiones.

compartir:linkedinx

Comentarios

Aún no hay comentarios. ¿Quieres dejar el primero?

Deja un comentario

Los comentarios se revisan antes de publicarse. No pido correo ni guardo tu IP.

$ ls ~/blog | grep relacionados

Sigue leyendo