Saltar al contenido
Ingeniería AI-native

Guía para parchear PHP: dos RCE sin autenticación corregidas en octubre de 2026

Dos fallos de PHP permiten ejecutar código sin autenticarse: qué versiones los corrigen, cómo revisar los workers y por qué el ini no basta.

Publicado Actualizado 8 min de lectura
seguridadPHPaplicación de parchesgestión de vulnerabilidadesoperaciones

Este artículo es educativo y no sustituye una auditoría formal de smart contracts, asesoría legal, asesoría financiera ni una revisión de seguridad específica del protocolo.

Bodegón cenital de paquetes tipo .deb y .rpm con pegatinas de versión, un USB, una nota UPDATE y una tablet comparando versiones de PHP.
Ilustración conceptual.

El 21 de octubre de 2026 el proyecto PHP publicó correcciones en sus cuatro ramas con soporte para dos fallos críticos, CVE-2026-109618 y CVE-2026-109619, que pueden permitir ejecutar código en remoto sin autenticación. Las versiones corregidas son PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35. Los anuncios de las versiones son directos: no hay mitigaciones completas y actualizar es la única corrección completa. El aviso de GitHub del segundo fallo lo dice con otras palabras: actualizar a una versión con el parche completo es la única mitigación fiable.

Para organizaciones con ramas antiguas de PHP o que no pueden actualizar el mismo día del parche, la guía recoge también las medidas temporales y lo que dejan abierto.

Esta guía cubre tres cosas: encontrar cada PHP que sirve peticiones web, confirmar la versión que da cada proceso en marcha después del reinicio y las medidas temporales para quien no pueda actualizar hoy.

Las medidas de ini que lista php.net reducen la exposición a rutas de ataque concretas, y el propio php.net dice que no arreglan la instalación. Son un puente de horas o días hasta que la versión corregida esté en marcha, no un sustituto. Y un servidor está parcheado cuando sus workers en marcha dan la versión corregida después de reiniciar, no cuando se ha instalado un paquete.

Qué se ha corregido

Los dos fallos, tal como los describe php.net:

  • CVE-2026-109618 (GHSA-w8hw-w9fx-j79w), ejecución remota de código por corrupción al quedarse sin memoria: la gestión de la falta de memoria puede interrumpir PHP cuando los datos internos de la petición solo están inicializados en parte, y la limpieza de la petición puede tratar después esos datos no válidos como válidos.
  • CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp), ejecución remota de código por estado global obsoleto: el estado interno de una petición puede sobrevivir después de liberar su memoria, y una petición posterior atendida por el mismo worker puede usarlo.

Los dos avisos de GitHub los califican de críticos, 9,2 en CVSS v4.0: vector de ataque de red, sin privilegios y sin interacción del usuario, pero con complejidad de ataque alta y requisitos de ataque presentes. Están afectadas todas las versiones anteriores a 8.2.35, 8.3.36, 8.4.27 y 8.5.12 en cada rama. php.net había anunciado la publicación con antelación el 20 de octubre, con esa calificación.

El ChangeLog de 8.4.27 resume la corrección en una línea: «Avoid state corruption via bailout». PHP 8.6.0 RC4, publicada el mismo día, también incluye las correcciones; php.net recuerda que es una versión de pruebas y no es para producción.

Quién está en riesgo

php.net lo dice así: «all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed», es decir, todas las instalaciones de PHP que sirven peticiones web. Considera que el riesgo es mayor con mod_php, donde un exploit demostrado permite que una petición sin autenticar dispare el ataque «before any application code runs», antes de que se ejecute código de la aplicación.

Ese detalle importa. Con mod_php, la calidad del código de la aplicación no cambia la exposición: un código limpio no protege a un intérprete vulnerable.

Los avisos añaden detalle por entorno:

  • Primer fallo: se validó la ejecución remota de código completa por HTTP contra mod_php con los límites de entrada y de memoria por defecto. El análisis del código fuente del aviso sitúa FrankenPHP cerca de mod_php, y el LSAPI de LiteSpeed cerca de FPM, donde los exploits que dependen de la aplicación son probables pero no se validaron.
  • Segundo fallo: la ruta de sesiones se validó contra mod_php y FPM.
  • En los dos: más workers, tráfico ajeno o el reciclado de workers bajan la probabilidad de cada intento, pero no eliminan la condición.

El segundo aviso trae una advertencia para la respuesta a incidentes: el estado obsoleto se puede publicar o consumir «without a predictable log entry», sin una entrada de log predecible. Unos logs limpios no demuestran que no haya pasado nada.

Paso 1: encontrar cada PHP que se ejecuta

Un servidor puede tener más de un PHP: el CLI, una o varias versiones de FPM, un php-cgi que quedó de una instalación antigua, un lsphp de LiteSpeed, un binario de FrankenPHP o un módulo de Apache. Cada uno se actualiza por su cuenta. En Linux, este comando lista los binarios de PHP y la versión que da cada uno:

bash
find / \( -path /proc -o -path /sys -o -path /var/lib/dpkg \) -prune -o -type f -perm -u+x \
  \( -name php -o -name 'php[0-9].[0-9]' -o -name 'php[0-9][0-9]' -o -name php-cgi -o -name 'php-cgi[0-9].[0-9]' \
     -o -name php-fpm -o -name 'php-fpm[0-9].[0-9]' -o -name lsphp \) -print 2>/dev/null |
  while read -r b; do echo "$b: $("$b" -v 2>/dev/null | head -1)"; done

En la máquina de pruebas, una Debian 13, encontró dos: /usr/bin/php8.4 (cli) y /usr/sbin/php-fpm8.4 (fpm-fcgi), los dos en 8.4.27. FrankenPHP y mod_php no aparecen. FrankenPHP es un binario único con su propio subcomando para la versión. mod_php es una biblioteca que carga Apache, así que se comprueba en la lista de módulos del servidor web, por ejemplo con «apachectl -M» o, en Debian, con «a2query -m».

Los contenedores llevan su propio PHP. El comando tiene que ejecutarse dentro de cada imagen, no solo en el host.

Paso 2: la versión del paquete no es la versión upstream

Según php.net, las distribuciones de Linux y los proveedores de LTS recibieron los parches por adelantado, así que la actualización suele llegar por el gestor de paquetes. La versión del paquete es de la distribución y no siempre sigue la numeración del proyecto PHP: un proveedor puede llevar la corrección en una compilación con un número de versión anterior. La referencia es el aviso de tu proveedor para estos dos CVE, no el número por sí solo. En Debian o Ubuntu:

bash
dpkg-query -W -f='${Package} ${Version}\n' 'php*-fpm' 'php*-cli' 'php*-cgi' 'libapache2-mod-php*' 2>/dev/null

El 25 de octubre, Debian 13 servía php8.4-fpm 8.4.27-1~deb13u1 en la máquina de pruebas. En sistemas basados en RPM, el equivalente es «rpm -q» con el nombre del paquete.

Paso 3: comprobar lo que se ejecuta, no lo que está instalado

Instalar el paquete sustituye el archivo en disco. Los procesos que ya están en marcha conservan el código antiguo en memoria hasta que se reinician. php.net pide reiniciar todos los workers después de aplicar sus medidas, y lo mismo vale para la actualización.

Este comando lista los procesos de PHP y del servidor web, cuándo arrancó cada uno y qué binario ejecuta. La última línea encuentra procesos que todavía tienen mapeado un binario o una biblioteca de PHP que después se borró o se sustituyó:

bash
for p in $(pgrep -f 'php-fpm|php-cgi|lsphp|frankenphp|apache2|httpd'); do
  echo "$p | started $(ps -o lstart= -p "$p") | $(sudo readlink "/proc/$p/exe")"
done
sudo grep -lE '(php|libphp)[^ ]* \(deleted\)' /proc/[0-9]*/maps 2>/dev/null

En la prueba se reinstaló el paquete con FPM en marcha y el reinicio automático desactivado. Esta fue la salida:

text
678767 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678768 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678769 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
/proc/678767/maps
/proc/678768/maps
/proc/678769/maps

«(deleted)» significa que el proceso sigue ejecutando el binario que existía antes de la actualización. Después de reiniciar FPM, el mismo comando mostró los procesos nuevos sin esa marca, y la última línea no imprimió nada. Con mod_php la comprobación es la misma sobre los procesos de Apache, porque la biblioteca libphp está mapeada dentro de ellos.

La comprobación más directa es preguntar al propio pool. Este fragmento envía una petición a FPM por su socket, sin pasar por el servidor web, e imprime la versión y los valores que el pool aplica de verdad. La ruta del socket depende de la distribución y de la versión; esta es la de Debian para 8.4:

bash
sudo tee /run/phpcheck.php >/dev/null <<'PHP'
<?php
echo PHP_VERSION, " ", PHP_SAPI, "\n";
foreach (['memory_limit', 'post_max_size', 'upload_max_filesize', 'session.upload_progress.enabled'] as $k) {
    echo $k, " = ", var_export(ini_get($k), true), "\n";
}
echo "ini_set memory_limit: ", var_export(ini_set('memory_limit', '128M'), true), "\n";
PHP
sudo SCRIPT_FILENAME=/run/phpcheck.php REQUEST_METHOD=GET \
  cgi-fcgi -bind -connect /run/php/php8.4-fpm.sock
sudo rm /run/phpcheck.php

El archivo de prueba se crea fuera de la raíz web y se borra al final, así que nunca es accesible por HTTP. Una página phpinfo o una página de versión no debe quedarse publicada.

El CLI no sirve como sustituto del pool. En la misma máquina, php en la línea de comandos dio memory_limit -1 y session.upload_progress.enabled 1, mientras que el pool de FPM sin cambios dio 128M y 1. Cada SAPI lee su propia configuración.

Paso 4: si no puedes actualizar hoy

php.net deja claro que estas medidas reducen la exposición a rutas de ataque concretas y no arreglan la instalación. Para el primer fallo:

  • memory_limit=-1, aplicado de forma que las aplicaciones no lo puedan cambiar, por ejemplo con php_admin_value en el pool de FPM o en la configuración de Apache. Hay que comprobar que ningún pool, directorio o aplicación vuelve a poner un límite finito. php.net advierte de que un fallo de asignación de memoria subyacente todavía puede llegar a la ruta vulnerable.
  • Un post_max_size bajo: 8M si la aplicación no acepta subidas de archivos, 32M si las acepta, con upload_max_filesize por debajo. php.net también propone límites en el servidor web o en el proxy para el tamaño de la petición decodificada, el número de campos, las partes multipart, las cabeceras y la profundidad de anidamiento, incluidas las peticiones sin un Content-Length utilizable.
  • Sin límite de memoria en PHP, el límite de disponibilidad tiene que ser un límite de contenedor o de cgroup, mejor con una política que mate solo el worker afectado. php.net desaconseja usar en su lugar RLIMIT_AS o RLIMIT_DATA, porque pueden provocar el fallo de asignación vulnerable.

Para el segundo fallo:

  • session.upload_progress.enabled=0 y reiniciar todos los workers, configurado en php.ini, en el pool de FPM o en la configuración del servidor que se aplica antes de analizar la petición. No con ini_set() ni con un archivo .user.ini de FPM. Bloquea las rutas de sesión confirmadas, no otras variantes.
  • En PHP 8.5, fatal_error_backtraces=0 y reiniciar todos los workers. Solo bloquea la ruta confirmada de los backtraces de errores fatales.
  • Quitar los ajustes no válidos u obsoletos aplicados por petición que emiten diagnósticos antes de la inicialización. Ocultar los diagnósticos con error_reporting, display_errors o @ no sirve.
  • Corregir los errores fatales y los tiempos de ejecución agotados que un atacante pueda provocar, también en callbacks, en el procesamiento de subidas o importaciones y en la limpieza.

En un pool de FPM, los ajustes quedan así. El valor de 4M de upload_max_filesize es un ejemplo; lo importante es que quede por debajo de post_max_size:

ini
; Temporary measures for CVE-2026-109618 and CVE-2026-109619.
; Remove memory_limit and post_max_size once the fixed version is running.
; Keep session.upload_progress.enabled off until the next PHP release (CVE-2026-115560).
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M
php_admin_flag[session.upload_progress.enabled] = off

Después se prueba la configuración y se reinicia:

bash
sudo php-fpm8.4 -t && sudo systemctl restart php8.4-fpm

En la prueba, con estas líneas al final del pool www, la prueba de configuración pasó, y el pool dio memory_limit -1, post_max_size 8M, upload_max_filesize 4M y session.upload_progress.enabled 0. Una llamada a ini_set para memory_limit desde el script devolvió false: la aplicación no puede cambiar php_admin_value. El reinicio se hizo a mano, porque la máquina de pruebas no tiene systemd.

Cada uno de estos cambios mueve una configuración de referencia. memory_limit=-1, en particular, quita un límite que muchas guías de bastionado ponen a propósito. Conviene registrar cada cambio con su motivo y una fecha de retirada, y quitarlo cuando la versión corregida esté en marcha.

CVE-2026-115560: un ajuste se queda

El 23 de octubre php.net publicó un aviso informativo. Recomienda mantener session.upload_progress.enabled=0 hasta la siguiente versión de correcciones ordinaria, por un vector adicional de ejecución remota de código que describe como improbable, CVE-2026-115560. Si el ajuste se aplicó el 21 de octubre, se queda como está y no hace falta nada más.

Según php.net, el problema necesita una combinación poco habitual: la aplicación pone session.serialize_handler en php_binary o php_serialize en su configuración, lo cambia a php en tiempo de ejecución con ini_set() antes de llamar a session_start(), y el progreso de subida de sesión sigue activado. Las aplicaciones que nunca cambian el serializador en tiempo de ejecución pueden dejar la función activada.

El aviso completo de CVE-2026-115560 todavía no se ha publicado: php.net dice que llegará con la próxima versión. Hasta entonces no hay una lista de versiones afectadas o corregidas que citar.

En la práctica, después de actualizar se pueden quitar las desviaciones de memory_limit y post_max_size, pero el interruptor del progreso de subida sigue apagado hasta esa versión.

PHP 8.1 y anteriores: una excepción, no un plan

PHP 8.1 y las versiones anteriores ya no reciben actualizaciones del proyecto PHP, y algunas distribuciones y proveedores de LTS ofrecen correcciones adaptadas. Por la gravedad, el equipo de seguridad de PHP hizo una excepción: adaptaciones mínimas del código fuente para tres ramas sin soporte, para ayudar a quien compila desde el código fuente o no tiene acceso a un proveedor de LTS. Esas ramas son 7.4, 8.0 y 8.1.

php.net vincula esa adaptación a la gravedad de estos dos fallos, y no cambia las fechas de fin de vida. 8.1 llegó al fin de vida el 31 de diciembre de 2025, 8.0 el 26 de noviembre de 2023 y 7.4 el 28 de noviembre de 2022. La rama 8.2 recibe correcciones de seguridad hasta el 31 de diciembre de 2026, y el anuncio de 8.2.35 pide planificar el paso a una versión con soporte. Si el inventario del paso 1 muestra 8.2 o anterior, el parche cierra este aviso y la siguiente tarea es la migración.

Qué revisar esta semana

  • Cada PHP de cada servidor y de cada imagen: CLI, FPM, php-cgi, lsphp, FrankenPHP y mod_php, con la versión que da cada uno.
  • Cuáles sirven peticiones web, empezando por mod_php.
  • Si la versión del paquete instalado coincide con el aviso de tu proveedor para CVE-2026-109618 y CVE-2026-109619.
  • Si algún proceso sigue teniendo mapeado un binario o una biblioteca de PHP borrados.
  • Si cada pool de FPM da la versión corregida cuando se le pregunta por su socket.
  • Qué medidas temporales se aplicaron, dónde y cuándo se retiran. session.upload_progress.enabled=0 se queda hasta la próxima versión.
  • Qué servidores ejecutan 8.2 o anterior, y la fecha prevista para su migración.

Un paquete en el repositorio o en el disco es un paso, no el resultado. Para CVE-2026-109618 y CVE-2026-109619, la verificación que cuenta es que cada proceso de PHP en marcha dé la versión corregida.

Si quieres comentar cómo aplicar estas comprobaciones a tus servidores o pipelines, puedes escribirnos desde la página de contacto; en el mensaje, menciona este artículo y el entorno que tienes en mente, sin credenciales, tokens ni material operativo confidencial.

Registro de fuentes

Fuentes consultadas el 25 de octubre de 2026; php.net se volvió a comprobar a las 10:42 (hora de Madrid) antes de redactar. Ejecutado en Debian 13 con php8.4-cli y php8.4-fpm 8.4.27-1~deb13u1, con la salida guardada: el inventario, la consulta de paquetes, la comprobación de procesos antes y después de sustituir el binario, la consulta a FPM con el pool por defecto y con las medidas temporales, y la prueba de configuración. No ejecutado: systemctl, porque la máquina de pruebas no tiene systemd, Apache con mod_php, FrankenPHP, LiteSpeed, sistemas basados en RPM ni Windows.

¿Necesitas aplicar esto a tu lanzamiento o workflow?

Solicita una revisión técnica y mapeamos el primer paso seguro.

Posts relacionados