Backend 7 min de lectura 1426 palabras

PHP2Shell: qué hacer con los servidores PHP mientras llega el parche

EN
PHP2Shell: qué hacer con los servidores PHP mientras llega el parche

Actualización: los parches ya están disponibles (PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35). Las versiones corregidas, los detalles de los dos fallos y las mitigaciones oficiales están en PHP2Shell: ya hay parche.

Nota: este artículo está escrito la mañana del 21 de octubre, antes de que salga el parche. Los detalles técnicos y las versiones corregidas se publicarán con la actualización, así que conviene consultar php.net para la información definitiva.

El lunes, el equipo de seguridad de PHP publicó un aviso poco habitual: una actualización de seguridad crítica programada para hoy, 21 de octubre a las 14:00 UTC (las 16:00 en la España peninsular), que corrige dos vulnerabilidades, CVE-2026-109618 y CVE-2026-109619, ambas con una puntuación CVSS v4.0 de 9,2. Se reservan el derecho a adelantarla si los detalles se hacen públicos o si aparecen ataques.

PHP no suele avisar con antelación, y aquí lo ha hecho porque la información ya se había filtrado. Hoy me ha tocado configurar un servidor completo con Docker y varios servicios PHP desactivando el upload progress, así que aprovecho para ordenar lo que se sabe y lo que se puede hacer mientras tanto.

Qué se sabe

Según cuenta Ars Technica, el aviso llegó después de que se filtraran detalles del fallo, incluido un vídeo de una herramienta llamada PHP2Shell ejecutando código en servidores con WordPress, Magento, Drupal, Joomla, Laravel, Symfony, MediaWiki o phpBB. La empresa de seguridad watchTowr ha confirmado que las vulnerabilidades son reales, y Sansec espera ataques masivos en los 15 minutos siguientes a la publicación del parche, que es lo que tardará alguien en comparar el código antes y después.

Lo que todavía no está confirmado:

  • Las versiones afectadas. La filtración habla de todas desde PHP 5 (Sansec dice desde la 5.4), pero PHP no lo dirá hasta publicar el parche. Lo que sí se sabe es que sólo las versiones con soporte, de la 8.2 en adelante, recibirán la corrección oficial.
  • El fallo exacto. Un análisis público apunta a un use-after-free en el código de progreso de subida de las sesiones de PHP, pero el equipo de PHP no lo ha confirmado.

Lo importante es que el fallo está en el propio PHP, antes de que se ejecute el código de la aplicación. No se puede arreglar desde WordPress, Laravel o lo que se use: hay que actualizar PHP.

Las mitigaciones, y sus límites

Hasta que salga el parche hay algunas medidas que reducen el riesgo. Mattias Geniar ha publicado una guía de mitigación muy completa, con una advertencia clara: ninguna está probada contra el exploit real.

La más extendida es desactivar el progreso de subida de las sesiones, session.upload_progress, la función que permite mostrar una barra de progreso mientras se sube un fichero. Si la filtración es correcta, el exploit usa ese camino. Casi ninguna aplicación lo usa, así que desactivarlo no rompe nada.

El problema es que eso sólo cubre una de las dos vulnerabilidades. Se habla de un segundo camino a través del procesamiento de peticiones multipart, y para ese no hay una mitigación conocida dentro de PHP. La única medida que podría cubrirlo es bloquear las peticiones multipart antes de que lleguen a PHP, con el coste de romper todas las subidas de ficheros.

MitigaciónUpload progressSegundo camino multipartEfecto secundario
session.upload_progress.enabled = 0CubiertoProbablemente noDejan de funcionar las barras de progreso nativas
Bloquear multipart en el servidor webCubiertoPuede que síSe rompen las subidas de ficheros que no se permitan explícitamente
Actualizar PHPCorregidoCorregidoNinguno

Y hay cosas que no ayudan, aunque parezca que sí: file_uploads = Off (PHP sigue procesando todo el cuerpo de la petición), reducir post_max_size, enable_post_data_reading = Off, comprobaciones dentro de la aplicación o un WAF si se puede llegar al servidor de origen directamente.

Desactivar upload progress

En un servidor tradicional basta con un fichero en el directorio conf.d de cada SAPI, por ejemplo /etc/php/8.4/fpm/conf.d/99-disable-upload-progress.ini:

session.upload_progress.enabled = 0
session.use_strict_mode = 1

session.use_strict_mode lo recomienda Sansec además del anterior. Es una buena práctica en general, aunque no tiene efecto si las sesiones se guardan en Redis o en la base de datos.

Con PHP-FPM, lo más seguro es fijarlo también en cada pool, con php_admin_flag para que no se pueda cambiar desde el código o desde un .htaccess:

; /etc/php/8.4/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
php_admin_flag[session.use_strict_mode] = on

Y después, recargar los workers con systemctl reload php8.4-fpm, repitiéndolo para cada versión de PHP que haya instalada.

Con Docker

En las imágenes oficiales de PHP, la configuración adicional se lee de $PHP_INI_DIR/conf.d, que es /usr/local/etc/php/conf.d. Lo más cómodo, sobre todo con varios servicios, es tener un único fichero .ini y montarlo en todos los contenedores:

services:
  app:
    image: php:8.4-fpm
    volumes:
      - ./php/99-php2shell.ini:/usr/local/etc/php/conf.d/99-php2shell.ini:ro

  worker:
    image: php:8.4-cli
    volumes:
      - ./php/99-php2shell.ini:/usr/local/etc/php/conf.d/99-php2shell.ini:ro

O, si las imágenes se construyen, añadirlo en el Dockerfile:

FROM php:8.4-fpm
RUN printf "session.upload_progress.enabled = 0\nsession.use_strict_mode = 1\n" \
    > "$PHP_INI_DIR/conf.d/99-php2shell.ini"

En los dos casos hay que recrear los contenedores para que lean la nueva configuración. Y conviene revisar todas las imágenes: es fácil acordarse del servicio web y olvidarse del panel de administración, el cron o esa imagen antigua que alguien construyó hace años con otra base.

Comprobar que está activo

El valor que importa es el que ve PHP-FPM al servir una petición, no el de la línea de comandos, que puede leer otro php.ini:

# El valor efectivo para FPM (debe decir Off)
php-fpm8.4 -i | grep session.upload_progress.enabled

# En un contenedor con la imagen oficial
docker compose exec app php-fpm -i | grep session.upload_progress.enabled

La forma más fiable es preguntarle a la propia aplicación con var_dump(ini_get('session.upload_progress.enabled'));, que debe devolver "0".

Bloquear multipart en el servidor web

Para el segundo camino, la medida que se propone es cortar las peticiones multipart antes de PHP. En nginx:

# En el contexto http {}
map $http_content_type $block_multipart {
    default       0;
    "~*multipart" 1;
}

# Dentro de cada server {} que pase peticiones a PHP
if ($block_multipart) {
    return 403;
}

Esto rompe todas las subidas de ficheros. La guía de Mattias incluye una variante que permite multipart sólo en las rutas de subida concretas, aunque esas rutas siguen expuestas hasta parchear, y ejemplos para Apache, Caddy y lighttpd. Para comprobarlo desde fuera, esta petición debe devolver un 403:

curl -s -o /dev/null -w '%{http_code}\n' -F a=b https://example.com/

Si el servidor está detrás de Cloudflare, han desplegado reglas de WAF en todos los planes, pero sólo sirven si el servidor de origen acepta tráfico únicamente desde las IP de Cloudflare.

Si hay que apagar algo

Para servicios que no son imprescindibles, la opción más segura hasta tener el parche es apagarlos. Pero ojo con cómo: Mattias comprobó que, si en Apache se quita sólo el manejador de PHP, los ficheros .php se sirven como texto plano, contraseñas de la base de datos incluidas. Lo correcto es parar el servidor web o devolver un 503 a todo.

Cuando salga el parche

  • Actualizar PHP en cuanto esté disponible. En Debian y Ubuntu, las versiones parcheadas por la distribución pueden mantener el mismo número de versión de PHP, así que hay que fijarse en la versión del paquete.
  • Reiniciar todo lo que tenga PHP en memoria: PHP-FPM, Apache con mod_php, Laravel Octane, FrankenPHP y los workers de colas. Un proceso de larga duración sigue ejecutando el código antiguo aunque el paquete esté actualizado.
  • En Docker, reconstruir las imágenes con la nueva versión y recrear los contenedores. Si se usan etiquetas como php:8.4-fpm, hay que asegurarse de descargar la imagen nueva y no usar la que ya hay en caché.
  • PHP 8.1 o anterior no tendrá parche oficial: o se migra, o se mantienen las mitigaciones y se depende de que el proveedor o la distribución publiquen una corrección.

Desactivar el upload progress se puede dejar así después de parchear: casi nadie lo usa, y es una superficie de ataque menos.

Lo que me llevo

Esta semana ya se ha visto cómo ocho certificados con claves de un byte obligaban a actualizar almacenes de certificados en medio mundo, y ahora toca PHP. En los dos casos, lo difícil no es aplicar el parche, sino saber dónde hay que aplicarlo: cada contenedor, cada versión de PHP, cada servicio olvidado.

Hoy lo que toca es inventariar, aplicar las mitigaciones sabiendo que son parciales y tener todo preparado para actualizar a las 14:00 UTC. Las mitigaciones ganan tiempo, pero la única solución es el parche.

Fuentes: anuncio del equipo de seguridad de PHP, Critical PHP RCE: CVE-2026-109618 and CVE-2026-109619 de Sansec, PHP CVE-2026-109618 and CVE-2026-109619: early mitigation steps de Mattias Geniar y Ars Technica.