El informe php2shell de Calif Research analiza dos vulnerabilidades de PHP que permiten ejecución remota de código, CVE-2026-109618 y CVE-2026-109619. PHP las corrigió en su actualización de seguridad del 21 de octubre de 2026. La investigación fue realizada por Calif con la participación de OpenAI y Wiz. Ryan Austin, de Calif, figura como autor de la investigación y del informe.

El camino más antiguo identificado se remonta a PHP 5.4.0. La variante explotable depende de la versión de PHP y del SAPI. Según el informe, los caminos afectados funcionan con las configuraciones predeterminadas. Los investigadores confirmaron RCE previa a la autenticación contra WordPress 7.1.2 y Drupal 11.4.3. También probaron Joomla 6.1.4 y MediaWiki 1.46.2. Roundcube 1.7.4 y Magento 2.4.9 aparecen en los resultados. La lista incluye phpMyAdmin 5.2.3 y Nextcloud 35.0.1. El informe menciona además Laravel 13.11 y Symfony 8.1, junto con WHMCS sin número de versión. Algunas rutas permiten atacar scripts PHP arbitrarios.

El análisis conecta estos hallazgos con la historia de los fallos relacionados con el límite de memoria de PHP. Stefan Esser informó en 2004 de la primera vulnerabilidad remota de PHP basada en ese mecanismo. No publicó un exploit porque consideraba crítico el problema. CORE e Immunity desarrollaron después exploits no públicos. En IRC circulaban además afirmaciones falsas sobre exploits. Calif señala que una investigación anterior sobre use-after-free en PHP no encontró vectores explotables por defecto entre las clases restantes afectadas por problemas de serialización.

En 2020, jr@coredu.edu informó del bug #78876 de PHP. Las variables largas podían provocar un error de falta de memoria y dejar archivos temporales sin limpiar. La cronología del informe atribuye el aviso a jr@coredu.mp. Una auditoría de OSTIF y Quarkslab volvió a encontrar una condición relacionada en 2024, pero no demostró su explotación. Un parche de PHP asociado con #78876 también había mitigado el mismo patrón en HHVM. Su historial de Git contiene la etiqueta `security-audit-2024`, aunque el parche pertenece a 2020.

OpenAI invitó a Calif a participar en la iniciativa Patch the Planet el 30 de julio de 2026. Stefan Esser puso al equipo en contacto con Volker Dusch, del Ecosystem Security Team de la PHP Foundation. Calif notificó el primer fallo a PHP el 11 de agosto, entregó una prueba de concepto de RCE el 14 de agosto y recibió confirmación el 15 de agosto. El segundo fallo se notificó el 21 de agosto. Ryan Austin había publicado el hash de uno de los hallazgos el 5 de agosto. Las versiones corregidas llegaron el 21 de octubre.

La primera ruta de explotación comienza con presión de memoria controlada. Antes de procesar el cuerpo POST, PHP compara `Content-Length` con `post_max_size`. No aplica la misma comprobación a los bytes que realmente consume. Una petición multipart HTTP/1.1 con chunked o una petición multipart HTTP/2 sin longitud declarada puede entregar más datos de los que su comprobación inicial aparenta. Un filtro de descompresión de entrada crea una diferencia parecida entre los datos comprimidos y el flujo ya descomprimido.

El segundo disparador utiliza partes multipart con `filename=""`. PHP no las contabiliza en `max_file_uploads`, pero crea seis campos `$_FILES` para cada parte: `name`, `full_path`, `type`, `tmp_name`, `error` y `size`. Los nombres de campo muy anidados obligan a construir tablas hash separadas para cada campo. Cientos de registros pequeños pueden superar el `memory_limit` predeterminado de 128 MiB. El cuerpo completo permanece, sin embargo, por debajo del valor predeterminado de 8 MiB de `post_max_size`. Una asignación posterior provoca entonces un error fatal controlado.

El primer fallo está en `_zend_hash_str_add_or_update_i`. PHP incrementa `nNumUsed` y `nNumOfElements` antes de reservar y asignar la clave del bucket. Si esa reserva provoca un error fatal, el bucket ya es visible para la destrucción, aunque su clave, hash y valor todavía no estén inicializados. El registro de variables de la petición llega a este código tanto desde POST codificado en URL como desde datos multipart RFC1867, antes de ejecutar el script objetivo. Durante el cierre de mod_php, PHP destruye la tabla dañada e interpreta el bucket obsoleto como una estructura válida.

PHP-FPM toma otra decisión cuando falla el inicio de la petición. Termina la petición FastCGI, cierra el módulo de PHP y finaliza el worker con `FPM_EXIT_SOFTWARE`. Por tanto, el worker no conserva el estado de la petición defectuosa. La primera cadena afecta a Apache con mod_php. Para esta variante, el informe indica explotación contra scripts arbitrarios desde PHP 7.3.

Después, los investigadores generalizaron el patrón: un estado se publica antes de que termine una operación que puede fallar, y un error fatal impide completar la reversión. Un análisis asistido por LLM convirtió la idea en un patrón de búsqueda para `SKILL.md` y llevó a un segundo hallazgo en Session upload progress. El análisis multipart puede inicializar Session antes de activar normalmente los módulos. Si falla después de `FILE_START`, se omite el callback de limpieza. `PG(modules_activated)` permanece en false y las variables globales de Session conservan punteros al heap de la petición, aunque ese heap ya haya sido liberado. Una petición multipart posterior con un identificador de Session y `PHP_SESSION_UPLOAD_PROGRESS`, pero sin una parte de archivo, llega a la limpieza sin restablecer el estado antiguo. Así aparece una ruta use-after-free entre peticiones en mod_php. PHP-FPM termina el worker ante el fallo inicial y no mantiene ese estado.

Otra cadena alcanza PHP-FPM después de que el inicio de la petición haya terminado correctamente. El unserializer genérico guarda la información de referencias y destructores en las variables globales de la extensión Basic. Un error fatal durante una decodificación iniciada por la aplicación puede dejar `BG(unserialize).data` apuntando a memoria liberada de la petición, con un nivel de anidamiento distinto de cero. La siguiente petición inicia el procesamiento de Session upload progress antes de la inicialización normal de Basic y puede reutilizar esa memoria. Las referencias serializadas `R:` y `r:` pueden dirigirse entonces a entradas seleccionadas de la lista de referencias antigua. Un valor negativo de `used_slots` en un bloque de destructores puede seleccionar memoria anterior al array previsto. Las claves serializadas duplicadas llegan a `var_push_dtor_value()` y copian un zval válido en el hueco seleccionado. El informe describe una RCE dependiente de la aplicación tanto en mod_php como en PHP-FPM, mediante operaciones como `session_start()`, `session_decode()`, `unserialize()` y `maybe_unserialize()` de WordPress.

Calif afirma que no hizo falta un harness especial de fuzzing ni un modelo excepcionalmente potente. Los investigadores se basaron en el conocimiento de vulnerabilidades antiguas de PHP y en preguntas dirigidas a LLM. Los prompts generales no recuperan de forma fiable primitivas de explotación antiguas y poco conocidas.

La recomendación es instalar de inmediato la actualización de seguridad de PHP de octubre de 2026. Como medidas temporales, las instalaciones con PHP-FPM pueden desactivar `session.upload_progress.enabled` y establecer `memory_limit=-1`. En mod_php se propone igualmente `memory_limit=-1`. Estas medidas no sustituyen al parche. Eliminar el límite de memoria puede aumentar el riesgo de agotar los recursos del proceso.