PHP2Shell: cómo parchear PHP si tu proveedor no llega
Ver imagen del artículo

PHP2Shell me ha obligado a revisar varios proyectos en los que estoy involucrado, incluido este blog. Cuando el fallo está en el intérprete, la lista de trabajo deja de ser «actualiza el plugin» y pasa a ser «averigua qué PHP está ejecutando cada aplicación». El panel y la terminal pueden estar hablando de instalaciones distintas.
Escribo esta guía el 22 de octubre de 2026. Hay versiones corregidas oficiales de PHP desde el día 21; otra cosa es que tu alojamiento o tu distribución ya las tengan empaquetadas. Voy a explicar cómo comprobarlo, qué puedes hacer mientras esperas y cómo preparar un parche de fuentes sin convertir una urgencia en otra.
[AVISO] Afectado por la incidencia no significa comprometido. Esta entrada no acredita una intrusión en mis proyectos ni publica su inventario o su configuración. Las medidas temporales reducen vías concretas de ataque; no sustituyen un PHP corregido.
Qué falla y por qué no basta con actualizar WordPress
Son dos vulnerabilidades críticas del núcleo de PHP, ambas con CVSS 4.0 de 9,2. Pueden permitir ejecución remota de código sin autenticación, con los privilegios del proceso web. No equivale automáticamente a obtener root. La Fundación PHP explica la investigación y la publicación adelantada de los arreglos.
- CVE-2026-109618: un fallo al quedarse sin memoria puede dejar estructuras internas incompletas que PHP usa durante el cierre. En Apache con mod_php se demostró ejecución remota antes del código de la aplicación.
- CVE-2026-109619: un trabajador conserva referencias a memoria de una petición que ya terminó. Una petición posterior del mismo trabajador puede usar ese estado liberado. El aviso documenta vías demostradas en mod_php y FPM, con condiciones concretas.
Por eso un WordPress actualizado sigue necesitando revisar su intérprete. Un plugin que se ejecuta dentro de PHP tampoco puede garantizar la protección de lo que ocurre antes de arrancar la aplicación. PHP-FPM entra en el inventario: no lo doy por seguro porque el primer ejemplo publicado utilice Apache.
El aviso de PHP incluye las instalaciones que atienden peticiones web: FPM, Apache con mod_php, php-cgi, FrankenPHP y LiteSpeed. Las vías demostradas y sus condiciones cambian entre motores; usar otro no exime de actualizarlo.
Las versiones corregidas y la trampa del número
Estas son las primeras versiones corregidas publicadas por el equipo de PHP el 21 de octubre. Si tu proveedor mantiene sus propios paquetes, contrasta su aviso de seguridad y la revisión completa del paquete.
| Rama de PHP | Primera versión oficial corregida |
|---|---|
| 8.2 | 8.2.35 |
| 8.3 | 8.3.36 |
| 8.4 | 8.4.27 |
| 8.5 | 8.5.12 |
Un paquete puede incorporar el arreglo sin mostrar esos números. Ubuntu documenta para 24.04 el paquete 8.3.6-0ubuntu0.24.04.13. Su php -v sigue diciendo 8.3.6. La revisión .12 no lleva estos dos arreglos. El criterio es el aviso del distribuidor, no una comparación de números de proyectos distintos.
# Solo PHP 8.3 del archivo de Ubuntu 24.04, no Plesk ni otro repositorio.
# Ajusta la lista al modo de ejecución que realmente utilizas.
for paquete_php in php8.3-common php8.3-cli php8.3-fpm; do
if ! estado_php=$(dpkg-query -W -f='${db:Status-Status}' "$paquete_php" 2>/dev/null); then
printf '%s: no consta en dpkg\n' "$paquete_php"
continue
fi
if [ "$estado_php" != installed ]; then
printf '%s: NO instalado (%s)\n' "$paquete_php" "$estado_php"
continue
fi
version_php=$(dpkg-query -W -f='${Version}' "$paquete_php") || continue
if dpkg --compare-versions "$version_php" ge 8.3.6-0ubuntu0.24.04.13; then
printf '%s: revisión corregida (%s)\n' "$paquete_php" "$version_php"
else
printf '%s: anterior al arreglo (%s)\n' "$paquete_php" "$version_php"
fi
done
apt-cache policy php8.3-common php8.3-cli php8.3-fpm
La documentación de dpkg-query distingue el estado de la versión registrada: un paquete eliminado puede seguir apareciendo. Exijo installed antes de comparar. El ejemplo cubre esos tres paquetes de FPM; para mod_php o CGI comprueba también libapache2-mod-php8.3 o php8.3-cgi, además de las extensiones instaladas.
Antes de instalar, guarda una copia recuperable. La terminal puede usar un PHP distinto del panel. Este primer bloque solo actualiza los índices y simula; --error-on=any, documentado por APT, hace fallar también una descarga con error transitorio. El subshell evita dejar set -e activado en tu terminal.
# Este bloque NO instala paquetes. Se detiene si falla update o la simulación.
(
set -eu
sudo apt-get --error-on=any update
apt-get -s install --only-upgrade php8.3-common php8.3-cli php8.3-fpm
)
Detente aquí y revisa versiones, origen y dependencias. Si hubo errores o no aparece una revisión corregida, no ejecutes la instalación. Si todo corresponde a tu despliegue y tienes recuperación preparada, el segundo bloque instala. Adapta ambas listas a los componentes que utilizas; no añadas otro modo de ejecución solo para seguir el ejemplo.
# Ejecución separada, solo después de revisar la simulación y la copia de recuperación.
sudo apt-get install --only-upgrade php8.3-common php8.3-cli php8.3-fpm
Inventariar la web, el panel y los contenedores
La lista que necesito es por aplicación: proveedor del paquete, rama, modo de ejecución, servicio y responsable. Añadiría tareas programadas, colas y consolas de administración. Revisar solo el ejecutable que encuentra el PATH deja demasiadas posibilidades fuera.
- En alojamiento compartido, pide por escrito la revisión instalada, el aviso que cubre ambos CVE y la confirmación de que los trabajadores se han renovado. «El servidor está actualizado» no identifica un paquete.
- En Plesk, comprueba el manejador seleccionado por cada dominio y el motor del panel. Su aviso ya anuncia paquetes corregidos y actualizaciones de sw-engine/plesk-engine. Los PHP de los dominios y el del panel son actualizaciones distintas.
- En contenedores, identifica la imagen y su resumen criptográfico, reconstruye si procede y recrea el contenedor. Descargar una etiqueta nueva no sustituye el proceso que ya está atendiendo peticiones.
Si apt muestra un error de firma, la actualización no queda resuelta ignorándolo. Plesk enlaza desde ese aviso su procedimiento para NO_PUBKEY 5B6AB2160B50DB96. No utilizaría trusted=yes ni desactivaría la autenticación para conseguir que una emergencia deje de dar mensajes.
Qué he probado y qué demuestra esa prueba
En las pruebas documentadas del 22 de octubre, de 06:05 a 06:30 UTC, compilé PHP 8.3.35 y 8.3.36 en un directorio aislado, y ejecuté paquetes de Ubuntu desempaquetados sin instalarlos. Seleccioné los 280 ficheros de pruebas nuevos o modificados entre ambas versiones; se omitieron los que requerían componentes no disponibles.

La versión 8.3.35 falló 25 de 163 pruebas ejecutadas y 8.3.36 ninguna. El paquete .12 de Ubuntu falló 26 de 173 y el .13 una: debug_backtrace_sensitive_named_variadic.phpt. Conservo ese fallo en el resultado. No convierte el paquete corregido en vulnerable a PHP2Shell ni demuestra que toda la aplicación sea compatible.
- Probado: binarios CLI y CGI aislados y las pruebas indicadas, en Ubuntu 24.04 x86-64. No es una prueba de explotación.
- Revisado: avisos, configuración temporal, compilación para despliegue y procedimiento de sustitución. No he ejecutado aquí una migración de producción para escribir esta entrada.
- El método y sus límites están en cómo pruebo las guías.
Cuando tu proveedor todavía no tiene el paquete
Primero separo tres situaciones: falta el paquete del proveedor, existe un parche para la rama pero no una publicación completa, o nadie mantiene ya esa rama. Cada una tiene una salida distinta. Si no puedo poner un intérprete corregido delante del tráfico, restrinjo el acceso o sirvo mantenimiento estático antes de seguir investigando.
| Situación | Qué prepararía |
|---|---|
| Hay paquete firmado del proveedor | Actualizar todos los componentes afectados y renovar procesos. |
| No hay paquete, pero sí publicación oficial | Construir y empaquetar esa publicación en un entorno separado; probar la aplicación antes de desviar tráfico. |
| Rama antigua con adaptación del arreglo | Usar el trabajo publicado por PHP o un proveedor LTS que confirme ambos CVE, con pruebas de compatibilidad. |
| Sin parche verificable o sin capacidad de probarlo | Migrar a una rama mantenida, aislar el servicio o retirarlo temporalmente de Internet. |
No copiaría un binario de otro servidor sobre /usr/bin/php ni sobre los directorios del panel. Arquitectura, extensiones, bibliotecas y opciones de compilación deben corresponder. Un PHP que arranca sin la extensión de base de datos puede parecer una actualización correcta hasta que llega la primera petición real.
Compilar la publicación corregida sin pisar el sistema
Esta es una receta de laboratorio, no un PHP completo para WordPress. Parte de un equipo de compilación con las dependencias de desarrollo disponibles y un directorio nuevo. La documentación de compilación de PHP explica las opciones; las de tu despliegue hay que revisarlas por separado.
# Trabajo aislado. No se instala ni sustituye el PHP del sistema.
set -eu
mkdir php2shell-laboratorio
cd php2shell-laboratorio
curl -fSLO https://www.php.net/distributions/php-8.3.36.tar.xz
# SHA-256 de este archivo concreto, contrastado con php.net el 22/10/2026.
printf '%s %s\n' \
dd54693aa334c5c4d72e9aee96b082c2473750a18a6c1d105789c93a3b1692fd \
php-8.3.36.tar.xz | sha256sum --check -
tar -xf php-8.3.36.tar.xz
cd php-8.3.36
./configure --prefix="$PWD/instalacion" --disable-all \
--enable-cli --enable-cgi --disable-phpdbg --enable-session \
--enable-filter --enable-ctype --enable-tokenizer --without-pear
make -j2
sapi/cli/php -v
sapi/cli/php -m
La salida esperada del primer comando final identifica PHP 8.3.36. Conserva los registros de compilación y ejecuta las pruebas del árbol con ese binario. El resumen criptográfico comprueba la integridad frente al valor esperado; si se dispone de firma, valida también la firma con la clave del responsable verificada por un canal fiable.
Preparar el cambio de manejador
- Para producción, prepara un paquete o una instalación versionada aparte, con el mismo conjunto de extensiones necesario, FPM si lo usas, OPcache, bibliotecas y configuración revisadas. No reutilices extensiones de otra ABI.
- Prueba sesiones, acceso, formularios, subida de archivos, base de datos, correo y tareas. Valida la configuración del nuevo FPM antes de arrancarlo y comprueba el sitio contra ese servicio aislado.
- Cambia el manejador o el destino FastCGI mediante el mecanismo admitido por tu plataforma. Guarda el destino anterior y evita que una actualización del panel sobrescriba silenciosamente el arreglo.
Ramas antiguas: el parche existe, el soporte no vuelve
Antes de compilar, comprueba los paquetes del proveedor. El aviso de Plesk confirma que Linux ya tiene 7.4.33, 8.0.30 y 8.1.34 corregidos en sus compilaciones del 21 de octubre de 2026. Conservan el número: verifica paquete y compilación, y renueva los trabajadores.
Canonical publicó USN-8948-2 para Ubuntu 20.04, 18.04, 16.04 y 14.04 mediante Ubuntu Pro. Las dos últimas requieren además Legacy Support. Esa cobertura depende del paquete y del servicio contratado; no equivale a que PHP haya reabierto el soporte general de esas ramas.
# Fuente enlazada por PHP. Fija la revisión, sin compilar ni instalar PHP 8.1.
(
set -eu
git clone --single-branch --branch PHP-8.1-CVE-2026-109618-109619 \
https://github.com/php/php-src.git php81-revision
cd php81-revision
git checkout --detach 1d7dec178a094e6e75b229ce22754045f8c6070a
test "$(git rev-parse HEAD)" = 1d7dec178a094e6e75b229ce22754045f8c6070a
git log -1 --oneline
)
El ejemplo fija la revisión de 8.1 contrastada el 22 de octubre; no sigue silenciosamente una rama que pueda cambiar. No he compilado ni validado esta adaptación. Examina el cambio completo y reconstruye el paquete para tu plataforma. En fuentes modificadas por un distribuidor, comprueba primero el encaje, sin forzar conflictos.
# Plantilla: dentro de las fuentes exactas de TU paquete, con el parche revisado.
# Este nombre es orientativo; no identifica un parche universal descargable.
patch --dry-run -p1 < php2shell-rama-exacta.patch
Un parche que aplica sin conflictos solo ha pasado una comprobación de contexto. Faltan compilación, pruebas del arreglo, pruebas de la aplicación y una forma mantenible de desplegarlo. Deben quedar cubiertos ambos CVE y sus cambios de estado y memoria. No lo reduciría a desactivar una función y escribir «parcheado» en el inventario.
Mitigaciones temporales: alcance y coste
Las siguientes medidas proceden de los avisos técnicos. Son un puente mientras llega el binario corregido. Antes de aplicarlas guarda la configuración y comprueba el impacto con el responsable del servicio. En alojamiento compartido, pide que se impongan administrativamente; no basta con editar el código del sitio.
- Para el CVE-2026-109619, desactivar
session.upload_progress.enabledantes del análisis de la petición bloquea las vías de sesión demostradas. En 8.5,fatal_error_backtraces=0bloquea la vía de trazas fatales confirmada. Reinicia todos los trabajadores. Ninguna cubre todos los estados internos o extensiones. - Usa
php.ini, configuración del servidor o del grupo FPM. Para este caso no confíes enini_set()ni en.user.inide FPM: llegan demasiado tarde. Corregir errores fatales y diagnósticos de inicialización reduce exposición; ocultar sus mensajes no elimina su causa.
Ejemplo parcial para un grupo FPM, sujeto a tu versión y configuración. La documentación de FPM describe los valores administrativos. Valida y reinicia el servicio correcto; no apliques una directiva de 8.5 a ramas anteriores.
; Medida parcial. No sustituye el arreglo de ambos CVE.
php_admin_flag[session.upload_progress.enabled] = off
; Solo en PHP 8.5:
; php_admin_flag[fatal_error_backtraces] = off
El límite de memoria requiere una decisión aparte
Para el CVE-2026-109618, PHP propone imponer memory_limit=-1 administrativamente, por ejemplo con php_admin_value en FPM. Renueva todos los trabajadores y verifica que ningún grupo, directorio o aplicación repone un límite finito. Editar un php.ini sin comprobar el valor efectivo no demuestra que la medida esté aplicada.
Tampoco es protección completa: puede fallar una reserva del sistema. Además elimina el tope por petición y puede agotar la máquina. No lo aplicaría sin límites externos y presupuesto de memoria por trabajador.
- Acompaña esa medida de límites de tamaño real de las peticiones en el frontal, incluidos cuerpos sin un
Content-Lengthútil, y de límites de partes, cabeceras, variables y profundidad. Bloquea multipart si la aplicación no lo necesita. - Contenedores o cgroups pueden limitar el daño de disponibilidad, pero no garantizan protección completa. El aviso desaconseja sustituir el límite por
RLIMIT_ASoRLIMIT_DATA: pueden provocar precisamente el fallo de reserva vulnerable. - Reducir la reutilización con
pm.max_requests=1puede romper determinadas cadenas entre peticiones y penaliza mucho el rendimiento. No arregla el motor. Un WAF tampoco acredita que todas las variantes estén cubiertas.
[AVISO] Si no puedes actualizar ni sostener una contención verificable, retira el PHP vulnerable del acceso público. Una página de mantenimiento generada por el mismo PHP no elimina la exposición. El mantenimiento debe servirse antes de llegar al intérprete.
Comprobar el cambio, recuperar y cerrar la incidencia
Después del despliegue comprueba una petición real contra el nuevo servicio. Actualizar el archivo de disco no cambia automáticamente todos los procesos que ya lo tenían abierto. Una recarga suave puede dejar trabajadores antiguos terminando peticiones; confirma que han salido. Un opcache_reset() tampoco actualiza el intérprete.
# Inspección de lectura. Muestra rutas, así que conserva la salida en privado.
for proceso in /proc/[0-9]*; do
ruta_php=$(sudo readlink "$proceso/exe" 2>/dev/null) || continue
case "$ruta_php" in
*php*|*sw-engine*) printf '%s %s\n' "${proceso##*/}" "$ruta_php" ;;
esac
done
Busca procesos que sigan usando el ejecutable anterior o indiquen (deleted), pero no tomes su ausencia como prueba suficiente. En mod_php el intérprete es una biblioteca cargada por Apache y hay que revisar sus procesos y módulos. Cruza versión completa del paquete, manejador efectivo y renovación de trabajadores.
Vuelta atrás sin reabrir el intérprete vulnerable
- Valida acceso, sesiones, formularios, subida e importación, tareas, errores y consumo de memoria. Comprueba todas las aplicaciones y el origen, no solo una portada que quizá entregue la caché.
- Una comprobación temporal vía HTTP debe estar restringida a administración y revelar solo versión y modo de ejecución. Elimínala al terminar; no dejes un
phpinfo()público. - Si el cambio rompe la aplicación, vuelve al destino o paquete anterior conservado solo con el servicio aislado, o mantén una página estática. Reabrir un PHP vulnerable no es una recuperación segura.
- Retira las medidas temporales de una en una cuando confirmes el parche, recupera límites de memoria adecuados y vuelve a probar. Documenta qué revisión cubre los dos CVE y quién mantiene el siguiente cambio.
Si aparecen indicios de intrusión, la actualización no borra una puerta trasera ni revoca credenciales. Conserva evidencias, aísla, investiga el alcance y recupera desde una base fiable; rota los secretos afectados según lo que se determine. La ausencia de errores en los registros tampoco demuestra que nadie haya entrado.
Qué no he probado
- No he reproducido un ataque, ni validado una explotación contra FPM o mod_php.
- No he ensayado estos procedimientos en ARM, Windows, LiteSpeed o FrankenPHP, ni compilado las ramas antiguas enlazadas.
- Las pruebas aisladas no certifican todos los plugins, extensiones o aplicaciones de tus proyectos. La guía de despliegue y las mitigaciones están revisadas documentalmente.
En la entrada de las cuatro libcurl ya me encontré con el mismo problema de inventario. Tener un paquete actualizado no resuelve las copias que nadie ha contado. Aquí añado una casilla: comprobar qué proceso está atendiendo de verdad cada petición. Las demás comprobaciones están en las guías de Ubuntu y Linux.
Revisión técnica del 22 de octubre de 2026: corregidos los ejemplos de dpkg y APT para comprobar el estado instalado y separar la instalación de la simulación. Fijada la revisión de fuentes de 8.1 y ampliadas las precisiones sobre proveedores y mitigaciones. Las pruebas de producción siguen fuera del alcance declarado.
Imagen destacada: «Blue Open Goodies ElePHPant», de Atomic Taco from Seattle, WA, USA, CC BY-SA 2.0, vía Wikimedia Commons. Es una fotografía de contexto; el peluche no es mío.
$ tail -f comentarios.log
La conversación empieza aquí
Escribir un comentario ↓[esperando] 0 mensajes en el hilo
Nadie ha escrito todavía. Si lo has probado en tu máquina, cuenta qué te salió: otra versión u otro resultado también ayudan.