Saltar al contenido
Luis ZambranaDevOps · Seguridad · Forense

PHP2Shell: las dos fallas críticas de PHP, cómo protegerte y cómo actualizar sin romper nada

PHP corrigió de urgencia dos fallas críticas que permiten ejecutar código en el servidor sin login, antes de que corra tu aplicación. Qué hacen, cómo saber si te afectan, cómo protegerte y cómo migrar el PHP viejo sin romper nada.

PHP2Shell: versiones de PHP parcheadas y sin parche

El 21 de octubre de 2026 el equipo de PHP sacó de urgencia cuatro versiones nuevas: 8.5.12, 8.4.27, 8.3.36 y 8.2.35. Corrigen dos fallas críticas, CVE-2026-109618 y CVE-2026-109619, que se conocen como PHP2Shell por el nombre de la herramienta que las encadena. Las dos tienen un puntaje de 9.2 sobre 10 (CVSS v4.0), y con razón: permiten que alguien sin usuario ni contraseña ejecute código en tu servidor con solo mandarle pedidos HTTP armados.

Lo más incómodo es que la falla no está en tu sitio, ni en WordPress, ni en un plugin. Está en PHP mismo, y se dispara antes de que se ejecute una sola línea de tu código. Podés tener la aplicación más prolija del mundo y estar expuesto igual.

Te cuento qué hace, cómo saber si te afecta, cómo protegerte y, sobre todo, cómo encarar el problema de fondo: los servidores con PHP viejo que nadie se anima a actualizar.

⚠️ Si administrás servidores con PHP, el resumen es corto: actualizá hoy. El resto de la entrada es para entender por qué y para hacerlo sin romper nada.

Qué pasó y por qué es tan grave

La divulgación estaba coordinada para el 22 de octubre, pero los detalles se filtraron el 20. PHP adelantó los parches al 21 a las 14:00 UTC. Para entonces ya había movimiento:

  • Cloudflare vio tráfico de ataque a las 07:43 UTC del 21, unas seis horas antes de que salieran los parches.
  • watchTowr informó intentos de explotación con sus honeypots ese mismo día.
  • Ese mismo 21, Calif y Wiz publicaron el análisis técnico, y Wiz mostró la ejecución remota sin login contra WordPress, Drupal y Joomla.

Con el parche y los análisis publicados, cualquiera puede comparar el código viejo con el nuevo y armar su propio ataque. Por eso se espera explotación masiva en los próximos días.

Qué hacen las dos fallas

Sin entrar en el detalle de la memoria, la idea es esta:

  • CVE-2026-109618, corrupción por falta de memoria. Cuando PHP se queda sin memoria (supera el memory_limit) mientras arma los datos del pedido, corta todo de golpe y esos datos quedan a medio inicializar. Después, al limpiar, PHP los trata como si fueran válidos. Un atacante puede provocar esa situación a propósito y aprovecharla para ejecutar código.
  • CVE-2026-109619, estado viejo entre pedidos. Parte del estado de un pedido sobrevive después de que se liberó su memoria. Si el mismo proceso atiende el pedido siguiente, puede usar ese estado viejo antes de reiniciarlo. Eso abre la puerta a filtrar memoria del servidor (contraseñas, tokens, claves) y a ejecutar código.

Todos los ataques conocidos usan pedidos multipart/form-data, el formato de los formularios con archivos adjuntos. PHP procesa esos pedidos antes de pasarle el control a tu aplicación. Por eso no importa si tu sitio tiene formularios de carga o no: basta con que PHP reciba el pedido. Según Sansec, una de las rutas filtra memoria aunque la aplicación nunca llame a session_start().

¿Me afecta?

Según PHP, están en riesgo todas las instalaciones que atienden pedidos web: PHP-FPM, Apache con mod_php, php-cgi, FrankenPHP y LiteSpeed. El riesgo es mayor con mod_php, donde ya se demostró el ataque completo. El código vulnerable viene de PHP 5.4, de 2012.

Tu versión Situación
8.5, 8.4, 8.3 o 8.2 Actualizá a 8.5.12, 8.4.27, 8.3.36 u 8.2.35.
8.1, 8.0 o 7.4 PHP ya no las mantiene. Hay parches de tu distribución (Debian, Ubuntu, RHEL y otras) o parches mínimos de código fuente para compilar.
7.3 o anterior, y todo PHP 5 Sin parche. Hay que migrar o sacar el sitio de internet.

Ojo con dos trampas al revisar la versión:

  1. php -v no siempre dice la verdad sobre tu sitio. Muestra la versión de la línea de comandos, que puede ser otra distinta de la que usa la web (FPM o mod_php). Lo seguro es preguntárselo al PHP que atiende la web.
  2. Las distribuciones no cambian el número de versión. Un PHP 8.1 de Ubuntu 22.04 parcheado sigue diciendo 8.1.2. Lo que cambia es la versión del paquete. Hay que buscar el CVE en el registro de cambios del paquete, no mirar el número de PHP.

Para ver la versión que usa la web, un archivo así alcanza. Ponele un nombre difícil de adivinar y borralo apenas lo leas:

<?php
// chequeo-7f3a9c.php: borrar después de usar
header('Content-Type: text/plain');
echo PHP_VERSION, ' (', PHP_SAPI, ")\n";
echo 'upload_progress: ', ini_get('session.upload_progress.enabled') ? 'activado' : 'desactivado', "\n";

En Debian o Ubuntu, para ver si el paquete ya trae la corrección:

apt changelog php8.1-fpm 2>/dev/null | grep -m3 -E 'CVE-2026-10961[89]'

En RHEL, AlmaLinux o Rocky:

rpm -q --changelog php-fpm | grep -m3 -E 'CVE-2026-10961[89]'

Si no aparece nada, ese paquete todavía no está parcheado. Un detalle: el repositorio de Remi (rpms.remirepo.net), muy usado en servidores Red Hat, no tenía los paquetes el día del parche. PHP recomienda pasar a los paquetes de la distribución o compilar.

Mi caso: luiszambrana.ar

Este blog corre en PHP, así que lo primero que hice fue revisarlo. Lo hice con un script temporal como el de arriba, protegido con un token y borrado enseguida:

  • El hosting ya tenía PHP 8.3.36, la versión corregida, con PHP-FPM.
  • session.upload_progress.enabled estaba desactivado, una de las mitigaciones recomendadas.
  • El sitio está detrás de Cloudflare, que desde las 03:02 UTC del 21 bloquea estos ataques con reglas que se activaron solas en todos los planes, incluido el gratuito.

Igual, lo que me deja tranquilo es el parche, no Cloudflare. Un WAF ayuda, pero si alguien conoce la IP real del servidor puede atacarlo directo y saltearse el proxy. Además, Cloudflare vio que los atacantes iban modificando los ataques para esquivar las reglas.

Cómo protegerte

1. Actualizar: la única solución completa

Todo lo demás reduce el riesgo, pero no lo elimina. Según el caso:

  • Hosting compartido: fijate en el panel qué versión de PHP usa cada dominio. Si tenés una versión vieja elegida a mano, es el momento de subirla. Si el proveedor no publicó nada, preguntale.
  • Servidor propio con paquetes de la distribución: apt update && apt upgrade (o dnf upgrade) y reiniciá PHP-FPM o Apache. Sin reinicio, los procesos viejos siguen corriendo con el código vulnerable.
  • Docker: volvé a bajar la imagen (docker pull php:8.3-fpm), reconstruí y recreá los contenedores. Una imagen vieja en caché sigue vulnerable aunque la etiqueta sea la misma.
# Debian/Ubuntu con PHP-FPM 8.3
sudo apt update && sudo apt install --only-upgrade 'php8.3*'
sudo systemctl restart php8.3-fpm

2. Si no podés actualizar ya: mitigaciones temporales

PHP publicó estas medidas para ganar tiempo, con una advertencia clara: reducen la exposición a ciertos ataques, pero no hacen segura la instalación.

; Contra CVE-2026-109619
session.upload_progress.enabled = 0

; Contra CVE-2026-109618
memory_limit = -1
post_max_size = 8M        ; 32M si el sitio necesita subir archivos
; upload_max_filesize siempre por debajo de post_max_size

Detalles importantes:

  • Van en php.ini, en el pool de FPM (php_admin_value / php_admin_flag) o en la configuración de Apache. No sirven ini_set() ni .user.ini. Después hay que reiniciar todos los procesos.
  • memory_limit = -1 le saca el techo de memoria a PHP: un solo pedido podría comerse toda la RAM del servidor. Para compensarlo, poné un límite desde afuera, con los límites de memoria del contenedor o de cgroups. No uses RLIMIT_AS ni RLIMIT_DATA, porque provocan justo la falla que se quiere evitar.
  • En PHP 8.5, además: fatal_error_backtraces = 0.

Otras capas que suman:

  • Bloquear multipart/form-data en el servidor web si el sitio no recibe archivos. En Apache, con mod_rewrite:

    RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
    RewriteRule ^ - [F]

    Rompe cualquier carga de archivos, y a veces algún formulario de pago. Probalo.

  • Poner un WAF adelante (Cloudflare, por ejemplo) y, si podés, que el servidor acepte conexiones solo desde el WAF.
  • Sacar de internet lo que no se pueda parchear: un PHP 5 o 7 viejo puede quedar detrás de una VPN mientras se migra. El NCSC británico directamente recomienda apagar lo que no se pueda corregir enseguida.

3. Revisar si ya te atacaron

Que hoy estés parcheado no quiere decir que no te hayan atacado antes. Hubo ataques desde el 21, y Amazon dice haber visto un intento el 17. Algunas cosas para mirar:

# Procesos de PHP que lanzaron una shell u otros programas (no debería haber ninguno)
ps -eo pid,ppid,user,cmd --forest | grep -A3 -E 'php-fpm|php-cgi|apache2|httpd' | grep -E 'sh -c|bash|curl|wget|nc |perl|python'

# Archivos PHP nuevos o modificados en los últimos 3 días dentro del sitio
find /var/www -name '*.php' -mtime -3 -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

# Tareas programadas raras del usuario de la web
sudo crontab -l -u www-data

Fijate también en los registros:

  • POST con multipart/form-data a direcciones que no esperan archivos.
  • Errores fatal o de memoria agotada (Allowed memory size … exhausted) en el log de PHP-FPM o de Apache.
  • Comandos de reconocimiento corriendo con el usuario del servidor web: whoami, id, uname -a.

Si encontrás algo, aislá el servidor y guardá los logs antes de tocar nada. Recién después limpiás, y cambiás todas las contraseñas y claves que estaban en ese servidor (la falla puede filtrar memoria).

El problema de fondo: el PHP viejo

Esta vulnerabilidad deja a la vista algo que todos los que administramos servidores conocemos: hay muchísimos sitios con PHP 7.x, o incluso 5.x, que nadie actualiza porque funcionan y da miedo tocarlos. Ese miedo tiene fundamento: entre versiones de PHP cambian funciones, desaparecen otras y se endurecen los tipos. Pero hoy esos sitios no tienen parche oficial, y PHP 8.2 deja de recibir actualizaciones de seguridad el 31 de diciembre de 2026.

Algunos cambios que suelen romper código viejo:

Desde Qué cambia Ejemplo típico que se rompe
7.0 Se eliminan las funciones mysql_* mysql_connect() → hay que pasar a mysqli o PDO
8.0 Se eliminan each() y create_function() Bucles while (list($k, $v) = each($a))
8.0 Comparación entre texto y número 0 == "abc" antes daba true, ahora da false
8.0 Las funciones internas lanzan TypeError strlen([]) antes era un warning, ahora corta la ejecución
8.0 @ ya no silencia los errores fatales Código que "andaba" escondiendo errores
8.1 Pasar null a funciones internas queda obsoleto trim(null), strlen(null) llenan el log de avisos
8.2 Las propiedades dinámicas quedan obsoletas $obj->campoNuevo = 1 en una clase que no lo declara
8.2 utf8_encode() y utf8_decode() quedan obsoletas Conversión de textos de bases viejas en Latin-1
8.4 Los parámetros que admiten null sin declararlo quedan obsoletos function f(Foo $x = null) → ?Foo $x = null

Lo que hoy es un aviso de obsolescencia en la versión siguiente suele ser un error. Ignorarlos es dejar el problema para después.

Cómo actualizar sin romper nada

Es lo que hago cada vez que tengo que subir de versión un sistema viejo:

  1. Inventario. Anotá qué servidores y sitios tienen PHP, con qué versión y con qué forma de ejecución. No te olvides de lo que viene adentro de otros programas: paneles de control, sistemas de monitoreo, el phpMyAdmin que alguien dejó instalado.

  2. Todo en control de versiones. Antes de tocar nada, el código va a git, con su composer.lock si usa Composer. Si algo sale mal, volvés atrás con un comando y no con un backup de hace tres meses. Las copias sueltas (index.php.viejo, funciones.php.anda) no son control de versiones.

  3. Un entorno de prueba con la versión nueva. Con Docker es muy simple levantar el mismo código con otro PHP sin tocar producción:

    docker run --rm -p 8081:80 -v "$PWD":/var/www/html php:8.4-apache
  4. Revisión automática del código. Antes de probar a mano, que las herramientas busquen lo obvio:

    # Errores de sintaxis con la versión nueva
    docker run --rm -v "$PWD":/app -w /app php:8.4-cli \
     sh -c 'find . -name "*.php" -not -path "./vendor/*" -print0 | xargs -0 -n1 php -l | grep -v "No syntax errors"'
    • PHPCompatibility (un estándar para PHP_CodeSniffer) marca las funciones eliminadas o cambiadas para la versión que le indiques con --runtime-set testVersion 8.4.
    • Rector corrige muchos de esos cambios solo. Usalo primero con --dry-run para ver qué propone.
    • PHPStan encuentra errores de tipos que con PHP 8 pasan a ser fatales.
  5. Todos los avisos a la vista. En el entorno de prueba usá error_reporting = E_ALL y log_errors = On, y recorré el sitio entero: altas, bajas, búsquedas, exportaciones, el formulario que se usa una vez por mes. Cada aviso de obsolescencia es un error futuro.

  6. Actualizar las dependencias. Muchas librerías viejas no andan con PHP 8. composer outdated te dice cuáles tienen versión nueva.

  7. Plan de vuelta atrás. Antes de pasar a producción, sabé cómo volver: el selector de versión del hosting, la imagen anterior de Docker o el paquete anterior. Y backup de la base antes del cambio.

  8. Pasar a producción y vigilar. Mirá el log de errores las primeras horas. Si el sitio tiene mucho tráfico, conviene hacerlo en un horario tranquilo.

💡 Si un sistema es tan viejo que actualizarlo implica reescribirlo, al menos sacalo de internet: detrás de una VPN, con acceso por IP, o en una red interna. Un sistema sin parche y expuesto es una invitación.

Lista rápida

  • Saber qué versión de PHP usa cada sitio (la de la web, no la de php -v).
  • Actualizar a 8.5.12, 8.4.27, 8.3.36, 8.2.35, o al paquete parcheado de la distribución.
  • Reiniciar PHP-FPM o Apache, y recrear los contenedores.
  • Si no se puede actualizar ya: session.upload_progress.enabled = 0, las otras mitigaciones y un WAF adelante.
  • Revisar procesos, archivos nuevos y logs por si ya hubo un ataque.
  • Armar el plan para migrar lo que siga en PHP 8.1 o anterior, empezando por git y un entorno de prueba.

Fuentes

Comentarios

Todavía no hay comentarios. ¡Sé el primero!

Seguí leyendo

Docker

Y no se olviden de Roundcube: PHP2Shell no perdona

Después de PHP2Shell, el webmail es el servicio que todos usan y nadie mira. Un caso supuesto: Roundcube 1.6.7 con PHP 8.1 a 1.6.20 con PHP 8.4.27, 15 segundos de corte, las trampas que aparecieron y cómo los encabezados pasaron de C a A.

12 min