El 20 de octubre de 2026 se hicieron públicos dos fallos graves en el núcleo de PHP. El nombre común es PHP2Shell y los identificadores son CVE-2026-109618 y CVE-2026-109619. La puntuación CVSS es 9,2. Permiten ejecutar código en el servidor sin autenticarse, y ya se han observado ataques.

En este artículo explico qué son y qué versiones de PHP afectan. Cuento cómo protegí los servidores que gestiono mientras no había parche, y cómo llegué a la actualización definitiva. También encontrarás el script que usé y los comandos para lanzarlo.
Qué es PHP2Shell
El problema está en cómo PHP lee las peticiones de tipo multipart/form-data. Es el formato que usa el navegador para enviar archivos. PHP lo interpreta antes de que arranque cualquier script.
Por eso no hace falta un módulo de subida. Puede bastar una sola petición a cualquier archivo PHP del sitio.
Los dos fallos, en resumen:
- CVE-2026-109618. Si la memoria se agota mientras PHP lee la petición, algunos datos se corrompen. La corrupción aflora en la limpieza final de la petición y puede llevar a ejecutar código.
- CVE-2026-109619. Parte del estado de una petición se queda en memoria. Una petición posterior, en el mismo proceso, lo reutiliza.
Estos detalles técnicos vienen del análisis del NCSC neerlandés y de otras fuentes públicas. No he intentado explotar los fallos en mis servidores.
Qué versiones de PHP están afectadas
Las versiones corregidas salieron el 21 de octubre de 2026: PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35. Toda versión anterior a estas está afectada.
Las ramas 8.1 y anteriores no tienen versión corregida oficial, porque ya no tienen soporte. PHP solo ha publicado unos backports mínimos del código fuente para 7.4, 8.0 y 8.1. Para ramas aún más antiguas no consta nada.
El riesgo alcanza a cualquier forma de ejecutar PHP. La más expuesta es mod_php. También cuentan PHP-FPM, php-cgi, FrankenPHP y LiteSpeed. Y afecta a toda aplicación que se ejecuta sobre PHP: WordPress, Joomla, Drupal, Nextcloud, phpMyAdmin, Roundcube y el panel ISPConfig.
En Debian hay un detalle que conviene conocer. Debian aplica las correcciones como backports y no cambia el número de versión de PHP. Un servidor puede mostrar un número bajo y estar ya corregido. Por eso el script no compara versiones. Comprueba si el changelog del paquete instalado cita las dos CVE.
La mitigación, a la espera del parche
Cuando los fallos se hicieron públicos, los parches aún no estaban disponibles para las distribuciones. No quería dejar los servidores al descubierto, así que reduje el riesgo con un script.
La mitigación no es un parche. Desactivar session.upload_progress cierra solo una de las vías conocidas de la CVE-2026-109619 y no hace nada contra la CVE-2026-109618. Servía para reducir el margen de ataque hasta la actualización, no para sustituirla.
Qué hace el script
El script está pensado para Debian 12 y 13. Funciona con Apache (mod_php, FastCGI, suPHP, PHP-FPM) y con Nginx (PHP-FPM). Reconoce los servidores gestionados con ISPConfig, tanto Apache como Nginx.
Las opciones:
- Sin opciones, solo comprueba. Para cada paquete PHP verifica que el changelog cite las dos CVE. Señala los procesos que aún usan un binario viejo, lee los ajustes activos y los puertos web en escucha.
--updateactualiza antes el índice de paquetes.--logsbusca indicios sospechosos: archivos PHP recientes en las carpetas de subida o caché, patrones típicos de webshells, referencias a php2shell en los logs. Es una heurística y no prueba que haya compromiso.--hardenaplica el endurecimiento descrito más abajo.--dry-run, junto con--harden, muestra lo que haría sin modificar nada.--skip-disable-functions, junto con--harden, no tocadisable_functions.--rollbackquita lo aplicado por--harden.
Qué cambia con –harden
Para cada versión de PHP presente en /etc/php, el script crea el archivo mods-available/cve-hardening.ini. Lo activa con phpenmod para los SAPI fpm, apache2 y cgi. Así la protección vale para todos los sitios de una vez.
Los php.ini originales quedan intactos. La CLI no se toca, de modo que composer, artisan y wp-cli siguen funcionando.
El archivo establece tres cosas:
session.upload_progress.enabled = Offexpose_php = Offdisable_functionscon exec, passthru, shell_exec, system, proc_open, popen y pcntl_exec, añadidas a las que ya hubiera.
Después el script prueba la configuración y recarga PHP-FPM y Apache solo si la prueba pasa. Antes de escribir hace una copia de seguridad en /var/backups/php-cve-hardening/.
Los límites que hay que conocer
disable_functionspuede romper algunas aplicaciones, por ejemplo WordPress con ciertos plugins o Magento. Si pasa, usa--rollbacko vuelve a lanzar con--skip-disable-functions.- Las directivas
php_admin_valuede los pools de PHP-FPM tienen prioridad sobre el archivo global. El script las señala pero no las modifica. - Con ISPConfig, los archivos de
pool.dlos regenera el panel. Los overrides se corrigen desde el panel, en Sites, Website, Options, Custom php.ini settings, no a mano. - Los
php.inide cada sitio (modo FastCGI o suPHP de ISPConfig) y las versiones de PHP en/optno se gestionan. Hay que revisarlos aparte.
Cómo lanzar el script
El script está aquí: https://cisilino.com/mscripts/php20261.sh
Antes de ejecutar cualquier script descargado de internet, léelo. Puedes descargarlo y abrirlo:
wget -qO php20261.sh https://cisilino.com/mscripts/php20261.sh
less php20261.sh
Si quieres lanzarlo directamente en remoto, hacen falta privilegios de root. Estos son los comandos, en el orden que recomiendo.
1. Solo comprobación, con actualización del índice de paquetes:
wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --update
2. Simulación del endurecimiento, sin cambios:
wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --harden --dry-run
3. Aplicación:
wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --harden
4. Eliminación, si algo se rompe:
wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --rollback
Después de --harden y de --rollback no hace falta reiniciar nada a mano: el script recarga solo PHP-FPM y Apache, tras probar la configuración. El reinicio sí hace falta después de actualizar el paquete PHP, porque los procesos en ejecución conservan en memoria el binario viejo:
sudo systemctl restart 'php*-fpm' apache2
Las comillas simples alrededor de php*-fpm son necesarias. Sin ellas, es la shell quien expande el asterisco, no systemctl. Si el servidor usa solo Nginx, quita apache2.
El resultado del script es también un código de salida: 0 si todo está bien, 1 si hay avisos, 2 si el sistema es vulnerable o hace falta alguna acción.
En servidores con ISPConfig: ispconfig-multipart-guard
Quien usa ISPConfig tiene un arma más. El proyecto ha publicado el script ispconfig-multipart-guard, descrito en el aviso oficial de ISPConfig.
El principio es simple. Si el problema está en las peticiones multipart, el servidor web las detiene antes de que lleguen a PHP:
- bloquea las peticiones
Content-Type: multipart/...para todos los sitios, para algunos sitios elegidos o para el panel de control; - con Nginx responde 415 a las peticiones PHP de los sitios bloqueados y los archivos estáticos no cambian. Con Apache responde 403, también para los archivos estáticos;
- por defecto bloquea solo el panel de ISPConfig. Los sitios hay que bloquearlos con comandos como
block alloblock esempio.it; - crea un cron que cada 5 minutos restablece sus hooks, si ISPConfig reescribe la configuración;
- tiene una lista de IP autorizadas que aún pueden subir archivos.
Funciona con Debian y Ubuntu, con ISPConfig 3.2 o 3.3 (y también sin ISPConfig), con Nginx o con Apache 2.4 y posteriores.
El precio es que en los sitios bloqueados las subidas no funcionan. Eso incluye los medios de los CMS, los adjuntos de los formularios y los del webmail, y las importaciones de phpMyAdmin. Las páginas normales y los formularios urlencoded siguen funcionando. Esta también es una medida provisional y no una corrección.
Este script y el mío no son alternativos. El mío actúa sobre la configuración de PHP, el de ISPConfig sobre el servidor web. No los he comparado en entornos reales y no puedo decirte cómo se comportan juntos en cada configuración.
La actualización definitiva, en dos pasos
Las mitigaciones debían durar poco. Actualicé los servidores en cuanto estuvieron disponibles las actualizaciones, en dos pasos:
- Compilación directa. Mientras la distribución de cada servidor publicaba su paquete, compilé PHP desde las fuentes en las versiones corregidas.
- Paquete oficial. Cuando el paquete oficial de la distribución se publicó para ese servidor, sustituí la compilación por el paquete.
El segundo paso cuenta. Un PHP compilado a mano no se actualiza solo con el resto del sistema. El paquete de la distribución sí, y el script puede comprobarlo leyendo el changelog.
Qué hacer ahora
- Actualiza PHP a la 8.5.12, 8.4.27, 8.3.36 u 8.2.35. En Debian también puedes usar el paquete de la distribución cuando el changelog cite las dos CVE.
- Reinicia PHP-FPM y Apache, y vuelve a lanzar el script sin opciones. Ya no debe señalar problemas.
- Si usas PHP 8.1 o anterior, planifica el paso a una rama con soporte. Para esas ramas no hay versión corregida oficial.
- Mantén desactivado
session.upload_progresshasta la próxima publicación de correcciones, si tu aplicación no lo usa. - Revisa los logs y los archivos recientes, con
--logso a mano, buscando archivos PHP aparecidos en las carpetas de subida. - No olvides PHP dentro de contenedores, imágenes y appliances de terceros. Hay que actualizarlos aparte.
- Si tu sitio usa WordPress, el plugin Cisilino Site Defense Network no resuelve PHP2Shell. Protege, eso sí, el login y mantiene un registro de actividad, útil para entender qué ocurre en el sitio.
Fuentes
- ISPConfig: PHP2Shell, vulnerabilidades críticas de PHP (CVE-2026-109618, CVE-2026-109619)
- NCSC-NL: wiki sobre PHP2Shell
ISPConfig, PHP, Debian y los demás nombres citados son marcas de sus respectivos propietarios. Se indican solo para identificar los productos.

Deja una respuesta