
Si tienes prisa: el 21 de octubre de 2026 PHP publicó las versiones 8.5.12, 8.4.27, 8.3.36 y 8.2.35, que corrigen dos fallos críticos (CVE-2026-109618 y CVE-2026-109619, CVSS 9.2 cada uno) a los que ya llaman "php2shell". Permiten que alguien sin usuario ni contraseña ejecute código en tu servidor, y en el peor caso lo hace antes de que tu aplicación llegue a cargar, así que ningún plugin de seguridad lo ve. Actualizar WordPress o los plugins no sirve: hay que actualizar PHP y reiniciar el servicio. Cloudflare ya bloquea el ataque incluso en el plan gratuito, pero solo para el tráfico que pasa por Cloudflare. Abajo están los comandos para comprobarlo en un VPS, en Docker y en hosting compartido.
Qué pasó y por qué tanto ruido
El 20 de octubre el equipo de seguridad de PHP hizo algo poco habitual: avisó con un día de antelación de que iba a publicar un parche el miércoles 21 a las 14:00 UTC, para que todo el mundo se preparara. El aviso decía que eran dos vulnerabilidades críticas y que se reservaban el derecho de adelantar la publicación si aparecían ataques reales.
El aviso corrió rápido entre proveedores de hosting y desarrolladores, muchos de los cuales escribieron a sus clientes para que tuvieran listo el procedimiento de actualización. Tras la publicación del parche, el CERT de las instituciones europeas publicó su aviso 2026-018 recomendando actualizar "lo antes posible" o aislar de internet lo que no se pudiera actualizar.
El 21 salió el parche con este aviso: "PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical vulnerabilities that can allow unauthenticated remote code execution". Es decir, ejecución remota de código sin autenticación, la peor categoría que existe para un servidor web.
Los dos fallos, sin jerga
No hace falta entender cómo funciona la memoria de PHP para protegerse, pero sí ayuda a entender por qué el remedio es el que es.
CVE-2026-109618: corrupción al quedarse sin memoria. Cuando PHP se queda sin memoria al empezar a procesar una petición, aborta en seco. Si eso pasa en el momento justo, deja datos internos a medio preparar, y al limpiar la petición los trata como si fueran válidos. Un atacante que manda una petición grande, pensada para provocar ese corte, puede aprovechar esa limpieza para ejecutar código. Según el aviso de GitHub, el ataque está demostrado en Apache con mod_php, y por el código fuente FPM, FrankenPHP y LiteSpeed también parecen vulnerables.
CVE-2026-109619: restos de una petición anterior. Un mismo proceso de PHP atiende muchas peticiones seguidas. Este fallo hace que algunos datos de una petición sobrevivan después de que su memoria se libere, y que la petición siguiente los use antes de que se reinicien. Según su aviso, está demostrado en mod_php y validado en PHP-FPM a través de la función de progreso de subida de sesiones (session.upload_progress), que viene activada por defecto.
Lo que hace grave a php2shell es esto: en mod_php, el ataque ocurre antes de que se ejecute una sola línea de tu aplicación. Wordfence, Solid Security o cualquier plugin de seguridad viven dentro de WordPress, así que nunca llegan a ver la petición. Lo mismo vale para Nextcloud, Laravel o cualquier otra cosa escrita en PHP.
A quién afecta
A cualquiera que sirva páginas con PHP, en cualquiera de sus formas:
| Cómo corre PHP | Riesgo según PHP |
|---|---|
| Apache con mod_php | El más alto. Hay exploit demostrado para los dos fallos |
| PHP-FPM (nginx, Caddy, Apache con proxy) | Afectado. Validado para el segundo fallo |
| FrankenPHP, LiteSpeed (LSAPI), php-cgi | Afectados según el aviso oficial |
Versiones:
| Rama | Corregida en | Nota |
|---|---|---|
| 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 parche oficial | PHP ya no las mantiene. Solo te salva tu distribución o tu proveedor |
Cloudflare, en su artículo del 21 de octubre, habla de "PHP 5.4 o posterior". No contradice a PHP: las versiones viejas también tienen el fallo, solo que nadie del proyecto PHP las va a corregir. Si tienes un PHP 7.4 olvidado en algún lado, no estás fuera de esto. Estás peor.
Y no es teórico. Cloudflare dice que ya vio tráfico de ataque contra sus clientes, con atacantes modificando el payload para intentar saltarse las reglas. CloudLinux habla de un exploit que ya circula públicamente.
Paso 1: averigua qué PHP sirve tu sitio de verdad
El error más común es escribir php -v, ver un número y quedarse tranquilo. Ese comando muestra la versión de línea de comandos, que no tiene por qué ser la misma que atiende tu web. En un servidor puede haber varias versiones instaladas a la vez.
En Debian o Ubuntu, primero mira qué versiones hay y cuáles están corriendo como servicio:
ls /etc/php/
systemctl list-units --type=service 'php*-fpm*'
Luego pregúntale a la versión de FPM que está activa (cambia 8.3 por la tuya):
php-fpm8.3 -v
Si usas Apache con mod_php, esto te dice si está cargado:
apache2ctl -M | grep -i php
Si no tienes acceso a la consola, en WordPress entra en Herramientas → Salud del sitio → Información → Servidor, donde aparece la versión de PHP que está ejecutando el sitio en ese momento.
Ojo con los números de las distribuciones
Si usas los paquetes oficiales de Ubuntu o Debian, el número no va a coincidir con la tabla de arriba, y eso no significa que estés expuesto. Las distribuciones aplican el parche sobre su versión original sin cambiar el número principal. Por ejemplo, Ubuntu 24.04 trae PHP 8.3.6, y la versión corregida es 8.3.6-0ubuntu0.24.04.13. Lo que manda es el sufijo del paquete, no el 8.3.6.
Versiones corregidas en los repositorios oficiales, comprobadas en USN-8948-1 y en el rastreador de seguridad de Debian:
| Sistema | Paquete | Versión corregida |
|---|---|---|
| Ubuntu 26.10 | php8.5 | 8.5.9-0ubuntu2.1 |
| Ubuntu 26.04 LTS | php8.5 | 8.5.4-0ubuntu1.5 |
| Ubuntu 24.04 LTS | php8.3 | 8.3.6-0ubuntu0.24.04.13 |
| Ubuntu 22.04 LTS | php8.1 | 8.1.2-1ubuntu2.28 |
| Debian 13 (trixie) | php8.4 | 8.4.27-1~deb13u1 (DSA-6574-1) |
| Debian 12 (bookworm) | php8.2 | 8.2.35-1~deb12u1 (DLA-4848-1) |
Si usas el repositorio de Ondřej Surý (ppa:ondrej/php o packages.sury.org), que es lo habitual cuando necesitas una versión más nueva que la de tu distribución, ahí sí verás el número oficial: 8.3.36, 8.4.27, etc.
Para ver el paquete exacto que tienes instalado:
apt list --installed 'php8.3-fpm'
Paso 2: actualiza y reinicia
En Debian o Ubuntu:
sudo apt update
sudo apt upgrade
Y aquí viene la parte que se olvida. Actualizar el paquete no cambia nada hasta que reinicies PHP. Los procesos de FPM o de Apache que ya estaban corriendo siguen usando el código viejo que tienen cargado en memoria. El segundo fallo vive justamente en esos procesos de larga duración.
sudo systemctl restart php8.3-fpm # si usas FPM (cambia la versión)
sudo systemctl restart apache2 # si usas mod_php
Para confirmar que el reinicio fue después de la actualización, compara estas dos horas:
grep 'upgrade php8.3-fpm' /var/log/dpkg.log | tail -1
systemctl show php8.3-fpm -p ActiveEnterTimestamp
Este es un ejemplo de un servidor bien actualizado: el paquete se instaló a las 20:22:14 y el servicio arrancó a las 20:22:36. Si en el tuyo la hora del servicio es anterior a la de la actualización, sigues corriendo PHP vulnerable aunque apt diga que todo está al día.
2026-10-21 20:22:14 upgrade php8.3-fpm:amd64 8.3.35-1+ubuntu24.04.1+deb.sury.org+1 8.3.36-1+0~20261021.88+ubuntu24.04~1.gbp250f7c
ActiveEnterTimestamp=Wed 2026-10-21 20:22:36 UTC
En RHEL, Rocky o Alma, el equivalente es sudo dnf upgrade 'php*' seguido del mismo reinicio de php-fpm o httpd.
Paso 3 (homelab): revisa tus contenedores
Si tienes un homelab, es muy posible que el PHP del sistema esté al día y que el problema esté dentro de un contenedor. Cada imagen de Docker trae su propio PHP, y apt upgrade en el sistema no lo toca. Nextcloud, WordPress, phpMyAdmin, FreshRSS, Matomo, Firefly III: si la aplicación está escrita en PHP, su contenedor lleva PHP dentro.
Para revisar todos los contenedores que tienes en marcha de una vez:
for c in $(docker ps -q); do
echo "$(docker inspect -f '{{.Name}}' $c): $(docker exec $c sh -c 'php -v 2>/dev/null | head -1')"
done
Los que no tienen PHP salen con la versión en blanco. Para los demás, revisa el número.
La buena noticia es que las imágenes oficiales se reconstruyeron el mismo 21 de octubre. Lo comprobé en Docker Hub: php:8.3.36-fpm, php:8.4.27-fpm, php:8.5.12-fpm y php:8.2.35-fpm ya existen, y wordpress:latest y nextcloud:latest se actualizaron esa misma tarde. Para traer la imagen nueva y recrear el contenedor:
docker compose pull
docker compose up -d
Dos detalles:
- Si fijaste una versión concreta en tu
compose.yaml(por ejemplophp:8.3.35-fpm),pullno va a traer nada nuevo. Tienes que cambiar la etiqueta a mano. - Las imágenes de terceros que no sean oficiales dependen de que su mantenedor las reconstruya. Después de actualizar, vuelve a correr el bucle de arriba y confirma la versión. No des por hecho que
latestya trae el parche.
Paso 4: si estás en hosting compartido
Aquí no puedes actualizar PHP tú mismo, así que lo que toca es preguntar. Abre un ticket o un chat y pregunta, con estas palabras si quieres: "¿Ya aplicaron los parches de PHP para CVE-2026-109618 y CVE-2026-109619 en el servidor donde está mi cuenta, y reiniciaron los procesos de PHP?" Si no te contestan con claridad o no tienen fecha, es una buena señal para empezar a pensar en otro proveedor.
Un detalle útil: la mayoría de los hostings compartidos usan cPanel con CloudLinux. Según CloudLinux, los paquetes corregidos de ea-php (los de cPanel) ya están publicados para todas las versiones, desde la 5.2 hasta la 8.5. Los de alt-php dependen de la versión de CloudLinux que use el proveedor. O sea, tu proveedor no tiene excusa técnica para no haber actualizado ya.
Como en las versiones con parche aplicado por el proveedor el número no siempre cambia, ver la versión desde el panel no basta para confirmarlo. La respuesta del soporte es lo que vale.
Si hoy no puedes actualizar
Puede pasar: una aplicación vieja que no arranca con la versión nueva, un servidor que no administras tú, una ventana de mantenimiento que todavía no llega. Los avisos oficiales de PHP proponen mitigaciones parciales. Ninguna sustituye al parche, y el propio PHP lo dice: actualizar es "the only complete mitigation".
Para el segundo fallo (CVE-2026-109619), desactiva el progreso de subida de sesiones. En php.ini:
session.upload_progress.enabled = 0
O en el pool de FPM (por ejemplo /etc/php/8.3/fpm/pool.d/www.conf), que tiene la ventaja de que la aplicación no lo puede volver a activar:
php_admin_flag[session.upload_progress.enabled] = off
Casi ninguna aplicación usa esa función. Si tienes PHP 8.5, el aviso también recomienda fatal_error_backtraces=0. En los dos casos hay que reiniciar FPM o Apache para que el cambio aplique.
Para el primer fallo (CVE-2026-109618), la mitigación principal que propone PHP es memory_limit=-1, es decir, quitar el límite de memoria para que nunca se dispare el error que provoca el fallo. Tiene un costo evidente: un script mal escrito o un ataque puede comerse toda la memoria del servidor. Si lo haces, pon un límite por fuera, por ejemplo con el límite de memoria del contenedor o de systemd. PHP también recomienda mantener post_max_size bajo y rechazar las peticiones grandes antes de que lleguen a PHP. En nginx, eso se hace con client_max_body_size.
Si usas FPM detrás de nginx, revisa que tu configuración no le pase a PHP los parámetros PHP_VALUE o PHP_ADMIN_VALUE. El aviso dice que esa es una de las condiciones que exponen a FPM al segundo fallo:
grep -rn -E 'PHP_VALUE|PHP_ADMIN_VALUE' /etc/nginx/
Si no devuelve nada, no los estás pasando.
Cloudflare: una capa más, con un límite claro
Cloudflare publicó reglas para php2shell el 21 de octubre a las 03:02 UTC, unas nueve horas antes de que se conocieran los detalles. Lo interesante:
- Llegan también al plan gratuito. Son siete reglas "Generic Rules – Anomalous Execution" en el Free Managed Ruleset, que según la documentación de Cloudflare está activado por defecto en las zonas gratuitas. En los planes de pago están en el Cloudflare Managed Ruleset, que sí hay que tener desplegado.
- Revisan también la respuesta. Además de las reglas que miran la petición, Cloudflare añadió una que examina lo que devuelve tu servidor y lo bloquea si parece que el ataque funcionó. Eso atrapa variantes del ataque que la regla de la petición no ve.
- La acción por defecto es bloquear.
Para comprobarlo, entra en tu zona, ve a Security → WAF → Managed rules (Seguridad → WAF → Reglas administradas si tienes el panel en español) y confirma que el conjunto de reglas está activo y que no tienes ninguna excepción que lo desactive.
El límite es este: el WAF solo protege el tráfico que pasa por Cloudflare. Si tu registro DNS tiene la nube gris (solo DNS) en vez de la naranja (proxy), Cloudflare no ve nada. Y si tu servidor tiene el puerto 80 o 443 abierto a internet, un atacante que conozca la IP real puede saltarse Cloudflare y llegar directo. Hay herramientas públicas que se dedican a encontrar esas IPs.
Para un homelab, Cloudflare Tunnel resuelve esto de raíz: tu servidor no abre ningún puerto y todo el tráfico entra por el túnel, así que no hay forma de llegar al origen sin pasar por el WAF. Si nunca lo has montado, lo explico paso a paso en mi guía de ActivePieces con Docker y Cloudflare Tunnels. Si prefieres los puertos abiertos, limita el acceso en el firewall a los rangos de IP de Cloudflare.
Aun así, Cloudflare lo dice claro en su artículo: el WAF es una defensa adicional y hay que parchear igual.
Después de parchear: revisa si alguien entró antes
El parche cierra la puerta, pero no echa a nadie que ya haya entrado. Si tu servidor estuvo expuesto entre el 21 de octubre y el momento en que actualizaste, vale la pena una revisión rápida. En WordPress:
# Archivos PHP dentro de uploads: ahí solo debería haber imágenes y documentos
find wp-content/uploads -name '*.php'
# Usuarios administradores: ¿los reconoces a todos?
wp user list --role=administrator
# Archivos PHP modificados en los últimos 3 días
find . -name '*.php' -mtime -3
Fuera de WordPress, revisa también las tareas de cron (crontab -l para cada usuario y /etc/cron.d/) y las conexiones salientes que no esperas. Si encuentras algo raro, no te limites a borrarlo: lo más seguro es restaurar desde un respaldo anterior al 21 de octubre y cambiar todas las contraseñas y claves. Un gestor de contraseñas como Passbolt autohospedado ayuda a no olvidar ninguna.
Lista rápida
- Averigua qué versión de PHP sirve tu sitio de verdad (FPM o Apache, no solo
php -v). - Actualiza hasta una versión corregida, o hasta el paquete corregido de tu distribución.
- Reinicia PHP-FPM o Apache, y confirma que el reinicio fue después de la actualización.
- Revisa los contenedores Docker uno por uno.
- En hosting compartido, pregunta por escrito al soporte.
- Si no puedes actualizar hoy, aplica las mitigaciones y ponle fecha al parche.
- Si usas Cloudflare, confirma que las reglas administradas están activas y que tu origen no es accesible directamente.
- Revisa si hay señales de que alguien entró antes del parche.
Si tienes un sitio en PHP 8.1 o anterior, este es un buen momento para planear la migración. Esta vez tu proveedor o tu distribución te cubrieron. La próxima, quizá no.
Fuentes
- PHP: aviso previo y aviso de seguridad, 20 y 21 de octubre de 2026
- GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- CERT-EU, aviso 2026-018
- Cloudflare: protección del WAF para las vulnerabilidades de PHP
- Ubuntu USN-8948-1
- Debian Security Tracker, CVE-2026-109618
- CloudLinux: estado de alt-php y ea-php