PHP tardó 2 años en parchear una RCE: el fallo estructural que la PHP Foundation reconoció
El 21 de octubre la PHP Foundation publicó un comunicado que casi ningún proyecto de software libre quiere firmar: admitir en público que su propio proceso de decisión le costó casi dos años cerrar un agujero de ejecución remota de código. No fue un descuido de un desarrollador ni un exploit que nadie vio venir. Fue, con sus propias palabras, un fallo estructural.
Eso es raro y por eso vale la pena leerlo con calma. La mayoría de los proyectos salen del disclosure con un “ya está corregido” y un agradecimiento a los investigadores. PHP hizo algo distinto: publicó la autopsia completa, mientras reconoce que su equipo de seguridad pasó meses paralizado entre tres opciones porque cada una tenía un costo que alguien tenía que pagar.
Este post no es la guía de patching de PHP2Shell, que es otra historia y ya cubrimos aparte. Este es el ángulo que me interesa como sysadmin: qué significa para tu operación que el lenguaje que sostiene una parte enorme de la web se mantenga con mantenedores voluntarios y un presupuesto del tamaño de un proyecto interno. Esa es la variable que nadie controla y de la que dependemos todos.
TL;DR — Lo que vas a tener al final
- Tiempo de setup: 10 minutos para auditar qué partes de tu stack dependen de un proyecto con financiamiento frágil
- Requisitos: inventario de servicios en producción y la costumbre de leer el changelog de tu distribución
- Resultado: un criterio para saber si tu parche llega a tiempo por proceso o por suerte, y qué hacer en cada caso
- Troubleshooting: cuando el proyecto upstream todavía no corrige, tu mitigación es reducir exposición, no esperar
Qué se publicó y por qué importa
El release del 21 de octubre de 2026 sacó PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35, cerrando dos CVE críticos: CVE-2026-109618 (corrupción por agotamiento de memoria) y CVE-2026-109619 (estado global obsoleto, es decir un use-after-free). Ambos con CVSS v4.0 9.2 y ambos con ejecución remota de código sin autenticación. Sobre Apache con mod_php el ataque se dispara antes de que corra una sola línea de tu aplicación.
Esa parte ya la conoces si leíste el post de PHP2Shell. Lo que este comunicado agrega es el por qué tardaron, y esa es la parte interesante.
La línea de tiempo que revela el comunicado
- 2024: PHP hace su primera auditoría de seguridad externa en años, financiada por la Sovereign Tech Agency, organizada por OSTIF y ejecutada por Quarkslab. Mientras corregía un hallazgo de Quarkslab, un desarrollador del core contratado por la Fundación encuentra los primeros indicios del problema de memoria detrás de lo que sería CVE-2026-109618.
- Resto de 2024: se teoriza un posible exploit, pero no se encuentran vectores concretos. Otros problemas con impacto claro opacan la preocupación y el tema pierde prioridad.
- 2025 y 2026: el equipo desarrolla varios enfoques en competencia. Cada uno tiene un compromiso difícil: romper compatibilidad hacia atrás, abrir nuevas vías de denegación de servicio, o exigir cambios en muchas extensiones de terceros.
- Mayo de 2026: se crea el Ecosystem Security Team, financiado con un grant de Alpha-Omega. Volker Dusch, ex release manager de PHP 8.5, asume como AI Security Engineer in Residence.
- 30 de julio de 2026: la Fundación mete a PHP en el programa Patch the Planet de OpenAI. Calif empieza a auditar.
- 11 de agosto de 2026: el investigador Ryan A de Calif reporta ejecución remota incondicional con la misma causa raíz. OpenAI reporta lo mismo de forma independiente. Wiz aporta vectores adicionales el 24 de agosto y OpenAI más vectores el 18 de septiembre.
- Desde el 11 de agosto: corregir esto pasa a ser prioridad máxima. Con el dinero de la Fundación, varias personas trabajan a tiempo completo, incluidos fines de semana, para coordinar con todas las partes.
El dato clave es este: entre el hallazgo de 2024 y el reporte de ejecución remota confirmada en agosto de 2026 pasaron algo más de dos años. Y el comunicado no lo disfraza. Textual: “el retraso fue un fallo estructural, causado tanto por el proceso de decisión del equipo de seguridad como por su falta de recursos”.
Por qué paralizarse sobre el fix correcto es un problema real
Esta es la parte que a los que programamos nos cuesta internalizar, y vale la pena explicarla bien porque no es un caso raro: es el caso normal en cualquier base de código con años de historia.
El bug no estaba en una función suelta. Estaba en el manejo de agotamiento de memoria del motor, que usa longjmp() para abandonar el contexto de una petición cuando se acaba la memoria. Corregirlo bien no es agregar una condición: es cambiar la forma en que el motor aborta un contexto. La solución que PHP finalmente aplicó elimina el longjmp() para ese caso y lo reemplaza por un aborto suave y diferido en el siguiente punto seguro de la máquina virtual, más un aborto duro con _exit() si la memoria llega a un segundo umbral más alto.
Ese cambio toca el procedimiento de cierre de petición, y ese procedimiento lo invoca todo el mundo. Ahí está el problema: cada opción tenía un costo que alguien iba a pagar.
Las tres opciones y quién pagaba
| Enfoque | Qué arregla | Quién paga el costo |
|---|---|---|
Quitar longjmp() para el agotamiento de memoria |
La causa raíz: nunca queda estado inválido que el cierre de petición pueda leer | Cambios de comportamiento en extensiones de terceros y riesgo de regresión de rendimiento en rutas de alta concurrencia |
Endurecer solo los límites (contar cabeceras multipart, max_file_uploads, anidamiento) |
Reduce las rutas conocidas de llegar al agotamiento de memoria | No cierra la causa raíz y deja la puerta abierta para vectores nuevos |
| Aislar el estado inválido en el cierre de petición | Evita que el cierre procese basura | Deja el problema de fondo y agrega complejidad a una ruta crítica |
Cuando un equipo opera por consenso y cada opción tiene un perdedor, la decisión no se toma: se pospone. Y mientras se pospone aparece trabajo más urgente, el contexto se pierde, y meses después alguien tiene que reconstruir el análisis desde cero. Eso fue exactamente lo que pasó, y el propio PHP lo describe como un trabajo que se estancó y reinició repetidamente durante meses.
Lo que la Fundación cambió de verdad
La parte esperanzadora del comunicado no es el parche: es el reconocimiento de que el proceso estaba mal. Concretamente:
- Decidir por mayoría en vez de por consenso. El texto dice que están cambiando procesos para que las decisiones puedan tomarse por mayoría y se favorezcan los compromisos. El consenso exige unanimidad; la mayoría exige discusión. Es un cambio deliberado en el perfil de riesgo.
- Escalar al liderazgo antes. Confían en que un parche atascado en compromisos difíciles llegue ahora más rápido a la dirección de la Fundación, que puede ayudar a dirigir el proceso hacia una resolución a tiempo.
- Financiar personas, no promesas. El arreglo publicado el 21 de octubre solo fue posible porque la Fundación pudo pagar a varias personas a tiempo completo, fines de semana incluido. Sin eso, el equipo de seguridad voluntario no habría tenido la capacidad.
El efecto secundario interesante: la avalancha de reportes con IA
Algo que pasó casi de paso y merece atención: la proliferación de herramientas de seguridad con IA cambió el volumen de reportes que llega. En su publicación del 14 de octubre, Alexandre Daubois, core developer que se unió a la Fundación en julio, da los números: 15 CVEs publicados desde el 1 de julio y más de 100 reportes cerrados porque no eran vulnerabilidades, duplicaban algo que ya estaba en la cola, o directamente no se reproducían.
Eso significa que por cada vulnerabilidad real que llegaba, llegaban varias que no lo eran. La Fundación no se quedó quieta: se asoció con Alpha-Omega para escanear PHP con Claude Mythos, y OpenAI les abrió Daybreak Red, el nivel más alto de su programa, restringido a defensores aprobados y pensado para investigación de vulnerabilidades. Lo usan para validar reportes, seguir una prueba de concepto a través del motor y agrupar los reportes que golpean la misma causa raíz desde ángulos distintos.
El matiz importante, y aquí la Fundación es explícita: “los modelos no toman las decisiones”. Cada veredicto lo hace una persona que fija la severidad y escribe o revisa cada parche. La IA ordena la cola, no dictamina. Es exactamente el balance correcto, y contrasta con el caos de repositorios de “PoC” que vimos en el post anterior.
Qué significa esto para tu operación
Dejando la filosofía aparte: la lección no es que PHP sea inseguro. La lección es que la velocidad de parche de tu stack depende de decisiones que alguien más tomó con recursos que tú no controlas. Puedes hacer tres cosas concretas con esto.
1. Auditar de dónde vienen tus parches, no solo si los aplicas
Tu apt upgrade no te dice si tu distribución ya recibió el parche del upstream: te dice si tu distribución ya lo publicó. Son dos cosas distintas. El proyecto upstream puede publicar el arreglo hoy y tu distribución no enviar ese paquete hasta semanas después, o hasta el siguiente ciclo de su rama estable. Esa cadena tiene capas, y en la capa de PHP el tiempo de respuesta se mide en años cuando el arreglo requiere una decisión arquitectural.
# Ver de qué repositorio viene el paquete y cuándo se actualizó por última vez
apt-cache policy php8.3-cli
apt changelog php8.3-cli 2>/dev/null | head -20
# Ver el paquete y su fuente de origen
apt-cache show php8.3-cli | grep -E "^(Version|Source|Origin)"
El segundo bloque es el que importa: te dice de dónde sale realmente tu PHP. Si el origen es un repositorio de terceros y no tu distribución oficial, el tiempo de respuesta deja de ser una promesa y pasa a ser una negociación.
# RPM: ¿mi distribución ya publicó el aviso del CVE?
dnf updateinfo info --cves CVE-2026-109618
# Debian: el changelog del paquete indica cuándo entró el arreglo
apt-get changelog php8.3-cli 2>/dev/null | grep -iE -m3 "109618|109619"
Si ninguno de los dos devuelve nada, tu distribución todavía no tiene el arreglo, aunque el proyecto ya lo haya publicado. Esa es la señal de que el tiempo de patching no depende solo del upstream sino también de tu distribución, y es un punto que puedes monitorear en lugar de suponer.
Y hay un paso más que casi nadie hace: saber qué versión atiende las peticiones web, que no siempre es la que ves con php -v en la terminal.
# php -v muestra el CLI, que puede ser distinto al que sirve la web
php -r 'printf("CLI: %s | SAPI: %s%s", PHP_VERSION, php_sapi_name(), PHP_EOL);'
# Configuración real de los pools que atienden peticiones
grep -rE "^(listen|user|pm.|php_admin_value)" /etc/php/*/fpm/pool.d/*.conf
El escenario típico es compartido: varios servidores con FPM 8.2 detrás y un CLI 8.3 en la terminal, porque alguien instaló php-cli para una tarea puntual. El parche va a una rama y la web sigue en la otra, sin que nada falle visible.
2. Tratar “el upstream todavía no corrige” como escenario normal
No es una excepción, es el estado permanente de cualquier proyecto con errores difíciles. Para las dos vulnerabilidades, el comunicado incluye mitigaciones parciales que reducen la vía de ataque concreta sin resolver completamente el problema de fondo.
Para CVE-2026-109618 el camino es poner memory_limit=-1 de forma que la aplicación no pueda sobreescribirlo, bajar post_max_size y limitar el tamaño de la petición en el proxy. Para CVE-2026-109619 es desactivar session.upload_progress.enabled y, en PHP 8.5, fatal_error_backtraces=0.
Un detalle fino que la Fundación subraya y que conviene no pasar por alto: no uses RLIMIT_AS ni RLIMIT_DATA. Hacer eso provoca que mmap() falle al llegar al límite, lo que dispara justamente el camino vulnerable.
# Mitigación parcial si no puedes actualizar todavía (NO te deja seguro)
# En el pool de FPM: php_admin_value no se puede sobreescribir desde la app
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[session.upload_progress.enabled] = 0
sudo systemctl restart php8.3-fpm
sudo systemctl reload apache2
La verificación que corresponde después es simple y no es lo más obvio: el valor que manda no es el que escribiste, sino el que gana después de aplicar todos los niveles de configuración. Por eso conviene confirmarlo desde el propio FPM y no asumirlo.
# Confirmar el valor que realmente aplica el pool, no el que asume la app
php-fpm8.3 -tt 2>&dev/null | grep -E "memory_limit|post_max_size"
grep -rn "memory_limit" /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/pool.d/
3. Reducir tu propia superficie de dependencia
Si un proyecto sostiene una parte grande de tu infraestructura y su financiamiento es frágil, tu mitigación de largo plazo no es esperar: es reducir la dependencia crítica o tener un plan de salida. Un rsync a tu almacenamiento no necesita PHP. Un panel de control en PHP sí. Empieza por los secundarios, no por el servicio principal.
# Inventario: qué servicios dependen de un runtime costoso de mantener
ss -tlnp | grep -E "php|:80|:443" | head -20
# Imágenes base desactualizadas en Dockerfiles o compose
grep -rnE "php:[0-9]|php[0-9.]+-fpm" --include="Dockerfile*" --include="*.yml" .
# Confirmar qué responde en cada vhost de producción
for v in $(apachectl -S 2>/dev/null | awk '/name:/{print $2}'); do
echo "== $v"; curl -sk "https://$v" -o /dev/null -w "%{http_code}n"
done
El inventario de puertos y el grep de Dockerfiles son los que más hallazgos dan en la práctica. La línea de puertos te muestra qué está expuesto y por qué proceso, y el grep te encuentra imágenes base con años de antigüedad que nadie revisó porque “funcionan”. Ambos son rápidos y no tocan producción.
El ángulo incómodo: la industria externalizó su riesgo
Hay un detalle en el comunicado que va más allá de PHP. La Fundación termina pidiendo dinero de forma explícita: empresas que construyen sobre PHP, proveedores de hosting, gobiernos e instituciones públicas. El argumento es que las herramientas encuentran errores más rápido que nunca, para defensores y atacantes por igual, y que lo que las herramientas no pueden hacer es decidir cuál es el arreglo correcto para una decisión de diseño de larga data en el motor. Eso requiere ingenieros con conocimiento profundo de todo el código, y hoy hay muy pocos.
Es la misma tensión que se vio cuando Google pausó su programa de recompensas para software de código abierto, congelado desde el 1 de octubre de 2026 hasta el año que viene, o cuando un parche de Oracle sin aplicar tumbó al FBI. El patrón es siempre el mismo: externalizamos el mantenimiento crítico a personas voluntarias, asumimos que “el software es gratis”, y luego actuamos sorprendidos cuando el arreglo tarda dos años en salir. No es gratis. Solo parece.
Como sysadmin no puedes resolver eso. Pero sí puedes dejar de actuar como si existiera. La pregunta correcta no es “¿mi PHP está parcheado?” sino: ¿cuánto tardó en llegar el parche que evita que me caiga, y qué hago mientras no llega?
Resumen
La PHP Foundation hizo algo que casi ningún proyecto hace: publicar la autopsia de su propio fallo de proceso. El error detrás de CVE-2026-109618 se descubrió en 2024 y se corrigió el 21 de octubre de 2026, no por falta de conocimiento técnico, que sobraba, sino porque cada arreglo posible tenía un perdedor y un equipo pequeño operando por consenso no podía elegir rápido. Eso cambió: ahora deciden por mayoría, escalan al liderazgo antes y el dinero de la Fundación paga el tiempo que hace falta.
Para tu operación la lección es simple y no depende del lenguaje: tu parche depende de decisiones de terceros con recursos limitados. Audita de dónde viene cada parche, trata “el upstream todavía no corrige” como escenario normal con mitigaciones reales, y reduce la dependencia crítica de lo que puedas reducir. Nada de eso te salva de un exploit de día cero. Sí te quita la sorpresa.
Preguntas frecuentes
¿Por qué PHP tardó casi dos años si el error se descubrió en 2024?
Porque el arreglo no era un parche localizado sino un cambio arquitectural en la forma en que el motor abandona un contexto cuando se agota la memoria. El equipo desarrolló varios enfoques en competencia, cada uno con un compromiso difícil: romper compatibilidad, abrir nuevas vías de denegación de servicio, o forzar cambios en muchas extensiones de terceros. Con consenso como método de decisión, cada opción tenía perdedores y el trabajo se estancó y reinició durante meses.
¿El cambio a decisiones por mayoría es más arriesgado para los usuarios?
Es un cambio deliberado en el perfil de riesgo. El consenso exigía unanimidad, que en la práctica equivale a parálisis cuando los compromisos son inevitables. La mayoría permite decidir con concesiones explícitas. La Fundación asume ese riesgo porque la alternativa, que era no decidir, ya había costado dos años de exposición. El parche final sigue revisado por personas, no por modelos.
¿Qué hago si dependo de una rama de PHP que ya no recibe actualizaciones?
PHP 8.1 y anteriores ya no reciben actualizaciones del proyecto. Algunas distribuciones y proveedores de soporte a largo plazo ofrecen retrocesos de seguridad, y para 7.4, 8.0 y 8.1 existen backports mínimos de código fuente, pero no hay binarios oficiales. Tu camino real es el paquete de tu distribución o migrar a 8.2 o superior. PHP 8.2 llega a su fin de vida el 31 de diciembre de 2026.
¿Las herramientas de IA con las que trabaja PHP no hacen el trabajo de los ingenieros?
No. Se usan para validar reportes, seguir una prueba de concepto a través del motor y agrupar los que comparten causa raíz. Cada veredicto de severidad y cada parche lo escribe o revisa una persona. La IA ordena la cola de entrada, que llegó a superar los cien reportes en un trimestre, pero no dictamina nada. El propio texto de la Fundación lo dice: los modelos no toman las decisiones.
¿Por qué se rompió el embargo coordinado y qué significa para mi operación?
Los identificadores de los CVE aparecieron en foros el 20 de octubre a las 15:00 UTC, con una hora de publicación y una puntuación incorrectas. PHP adelantó su preaviso y movió la publicación un día antes. Después, una firma de seguridad publicó por error un video con su prueba de concepto, y otra construyó imágenes con el arreglo más de seis horas antes de la publicación coordinada. Ese es el costo real de un embargo roto: cualquiera podía estudiar el arreglo en binario antes de que el resto actualizara.
Lee también
- Despliegue Seguro y Rápido de Parches: Desafíos y Soluciones para Sysadmins Linux
- FBI removió al contratista de Accenture: cómo un parche de Oracle sin aplicar tumbó al FBI
- CVE-2026-31431 “Copy Fail”: auditoría 6 meses después para tu fleet Ubuntu, Debian o RHEL
- Gestión de Vulnerabilidades: Conoce a los Dueños de tus Activos en Linux
- Google pausó el programa de recompensas de vulnerabilidades para su software open source
