From 2ae2e9a785fe8a640befbc948dc4a19ff8d0e802 Mon Sep 17 00:00:00 2001 From: JoshRob Date: Mon, 9 Nov 2026 20:32:57 -0600 Subject: [PATCH] 1.10.0: publish the portability matrix and the disclosure timeline --- COVERAGE.md | 2 ++ PORTABILITY.es.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++ PORTABILITY.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++ README.es.md | 4 ++-- README.md | 8 ++++---- TIMELINE.es.md | 42 ++++++++++++++++++++++++++++++++++++++++ TIMELINE.md | 41 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 PORTABILITY.es.md create mode 100644 PORTABILITY.md create mode 100644 TIMELINE.es.md create mode 100644 TIMELINE.md diff --git a/COVERAGE.md b/COVERAGE.md index cc4632e..1167b44 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -107,6 +107,8 @@ leak). Raw: `raw/fixed-branches-verify.txt`. per OOM and `mod_php`-only, and on `mod_php` the leak and the stale-`BG(unserialize)` primitive are mutually exclusive. - Kernel/allocator portability is not solved; public PoC results vary by host. + `PORTABILITY.md` records the measured matrix behind that sentence: `KeepAlive On`, + proxy buffering and an unsaturated worker pool each break the chain on their own. - A/C/E prerequisites are application-specific; the matrix records the lab conditions, not a universal exploitability claim. - The Nextcloud 33.0.x test deployment was used for configuration validation, not as the RCE diff --git a/PORTABILITY.es.md b/PORTABILITY.es.md new file mode 100644 index 0000000..8e0c749 --- /dev/null +++ b/PORTABILITY.es.md @@ -0,0 +1,48 @@ +# Portabilidad: por qué la misma PoC da números distintos + +Cada celda se midió en el mismo laboratorio — loopback, `php:8.4.26-apache`, tres intentos por +celda salvo que se indique — y responde una pregunta: qué condiciones **sí** condicionan la +cadena y cuáles solo lo parecen. + +| Configuración | Resultado | Abortos | +|---|---|---| +| `display_errors=On`, `memory_limit=8M`, 1 worker | **8/8** | 32 (8 corridas) | +| `display_errors=Off`, `memory_limit=8M`, 1 worker | 0/3 | 12 | +| `On`, `512M`, 1 worker, campo de 12 MB | 0/3 | 0 | +| `On`, `512M`, 1 worker, campo de 480 MB | **1/1 PROOF** | 4 | +| `On`, `8M`, 4 workers, exploit estándar sin saturar | 0/3 | 0 | +| `On`, `8M`, 4 workers, saturación (retener N-1) | **6/6 PROOF** | 24 | +| `On`, `8M`, 8 workers, saturación (retener N-1) | **4/4 PROOF** | 16 | +| `On`, `8M`, 16 workers, saturación (retener N-1) | **3/3 PROOF** | 12 | +| `On`, `8M`, 4 workers, proxy nginx, buffering apagado, trickle, 64k | **3/3 PROOF** | 12 | +| `On`, `8M`, 4 workers, proxy nginx, **buffering encendido** | 0/1 | n/d | +| `On`, `8M`, 4 workers, **`KeepAlive On`** | 0/5 | 20 | +| `Off`, `8M`, 1 worker, build debug (primitivo A, oráculo bin-128) | **4/4 OOM-in-cookie** (NB=235, K=4) | n/d | +| `On`, `8M`, 1 worker, imagen oficial, ASLR encendido | **3/3 PROOF RCE** | ~12 | +| `Off`, `8M`, 1 worker, imagen oficial, warmup A+B, ASLR on/off | 15/15 cookie de 4000 B dentro de su ventana (12 on / 3 off); repetición controlada **0/3** — claim de estabilidad retirado | n/d | +| `Off`, `8M`, 1 worker, imagen oficial, ASLR **apagado**, direcciones fijas | **3/3 PROOF RCE** | n/d | +| `Off`, `8M`, 1 worker, imagen oficial, ASLR **encendido** | **0/3** — bloqueado por el leak de `libphp` | n/d | +| `On`, `8M`, 1 worker, kernel pve 7.0, ASLR encendido | 0/3 | n/d | + +## Lo que dice la matriz + +1. **`memory_limit` no es el candado: lo es `post_max_size`.** Con 512M/480 MB la cadena corre + igual, porque el cuerpo cabe; con los defaults 128M/8M el A de 3.7 MB no agota la memoria y + el primitivo no arranca. La fila 512M/480 MB es una configuración no-default (del tipo que + corre un Nextcloud) y es con `display_errors=On`. +2. **`display_errors=Off` bloquea exactamente una cosa: el leak de `libphp`.** El primitivo A y + el `chunk_base` sobreviven; lo que desaparece es el puntero que convierte una escritura en + heap en un destino de código. +3. **En prefork, saturar workers es obligatorio.** Sin retener a los otros hijos, el OOM cae en + un hijo que no es el que llevará la siguiente petición y la cadena muere en 0/3. Reteniendo + `N-1` sale 6/6, 4/4 y 3/3 con 4, 8 y 16 workers. +4. **Un proxy tiene que dejar de bufferear.** Con buffering apagado y entrega por goteo la + cadena sobrevive; con buffering encendido no, porque la conexión nunca queda en el estado que + el OOM necesita. +5. **`KeepAlive On` la rompe**: 0/5. Es la mitigación más barata de la matriz y no está en PHP. +6. **Una corrida suelta no prueba nada.** La fila 15/15 fue el claim retirado de la cookie: + dentro de su ventana reproducía, en repetición controlada dio 0/3. Cada número marcado + `PROOF` viene de una celda repetida hasta estabilizarse. + +Esta es la respuesta a "por qué la PoC pública funciona en mi máquina y en la tuya no": la +diferencia suele ser el pool de workers, el buffering del proxy o `KeepAlive`, no el build de PHP. diff --git a/PORTABILITY.md b/PORTABILITY.md new file mode 100644 index 0000000..02d0439 --- /dev/null +++ b/PORTABILITY.md @@ -0,0 +1,49 @@ +# Portability: why the same PoC gives different numbers + +Every cell below was measured in the same lab — loopback, `php:8.4.26-apache`, three attempts +per cell unless stated — and answers one question: which conditions actually gate the chain, +and which ones only look like gates. + +| Configuration | Result | Aborts | +|---|---|---| +| `display_errors=On`, `memory_limit=8M`, 1 worker | **8/8** | 32 (8 runs) | +| `display_errors=Off`, `memory_limit=8M`, 1 worker | 0/3 | 12 | +| `On`, `512M`, 1 worker, 12 MB field | 0/3 | 0 | +| `On`, `512M`, 1 worker, 480 MB field | **1/1 PROOF** | 4 | +| `On`, `8M`, 4 workers, standard exploit, no saturation | 0/3 | 0 | +| `On`, `8M`, 4 workers, saturation (hold N-1) | **6/6 PROOF** | 24 | +| `On`, `8M`, 8 workers, saturation (hold N-1) | **4/4 PROOF** | 16 | +| `On`, `8M`, 16 workers, saturation (hold N-1) | **3/3 PROOF** | 12 | +| `On`, `8M`, 4 workers, nginx proxy, buffering off, trickle, 64k | **3/3 PROOF** | 12 | +| `On`, `8M`, 4 workers, nginx proxy, **buffering on** | 0/1 | n/a | +| `On`, `8M`, 4 workers, **`KeepAlive On`** | 0/5 | 20 | +| `Off`, `8M`, 1 worker, debug build (primitive A, bin-128 oracle) | **4/4 OOM-in-cookie** (NB=235, K=4) | n/a | +| `On`, `8M`, 1 worker, official image, ASLR on | **3/3 PROOF RCE** | ~12 | +| `Off`, `8M`, 1 worker, official image, A+B warmup, ASLR on/off | 15/15 cookie at 4000 B inside its window (12 on / 3 off); controlled repetition **0/3** — stability claim withdrawn | n/a | +| `Off`, `8M`, 1 worker, official image, ASLR **off**, fixed addresses | **3/3 PROOF RCE** | n/a | +| `Off`, `8M`, 1 worker, official image, ASLR **on** | **0/3** — blocked by the `libphp` leak | n/a | +| `On`, `8M`, 1 worker, kernel pve 7.0, ASLR on | 0/3 | n/a | + +## What the matrix says + +1. **`memory_limit` is not the gate; `post_max_size` is.** At 512M/480 MB the chain runs the + same, because the body fits; with the default 128M/8M the 3.7 MB A never exhausts memory and + the primitive never starts. The 512M/480 MB row is a non-default configuration (the kind a + Nextcloud deployment runs), and it is `display_errors=On`. +2. **`display_errors=Off` blocks exactly one thing: the `libphp` leak.** Primitive A and the + `chunk_base` survive; what disappears is the pointer that turns a heap write into a code + target. +3. **On prefork, worker saturation is mandatory.** Without holding the other children, the OOM + lands on a child that is not the one that will carry the next request, and the chain dies at + 0/3. Holding `N-1` gives 6/6, 4/4 and 3/3 at 4, 8 and 16 workers. +4. **A proxy has to stop buffering.** With nginx buffering off and trickle delivery the chain + survives; with buffering on it does not, because the connection never sits in the state the + OOM needs. +5. **`KeepAlive On` breaks it**: 0/5. This is the cheapest mitigation on the matrix and it is + not in PHP at all. +6. **A single measured run proves nothing.** The 15/15 row was the withdrawn cookie claim: in + its window it reproduced, in controlled repetition it gave 0/3. Every number here that says + `PROOF` came from a cell where the counts were repeated until they were stable. + +This is the answer to "why does the public PoC work on my machine and not on yours": the +difference is usually the worker pool, the proxy buffering, or `KeepAlive`, not the PHP build. diff --git a/README.es.md b/README.es.md index c3a5ec1..d3dc570 100644 --- a/README.es.md +++ b/README.es.md @@ -3,7 +3,7 @@ # php2shell — Verificación Independiente y Auditoría de Límites de Explotación ### CVE-2026-109618 · CVE-2026-109619 · CVE-2026-115560 -[![Release](https://img.shields.io/badge/Release-v1.9.2_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.9.2) +[![Release](https://img.shields.io/badge/Release-v1.10.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.10.0) [![Target](https://img.shields.io/badge/Objetivos_PHP-8.2_·_8.3_·_8.4_·_8.5-334155?style=flat-square)](#) [![CVSS](https://img.shields.io/badge/CVSS_v4.0-9.2_CRÍTICO-b91c1c?style=flat-square)](#) [![ASLR](https://img.shields.io/badge/ASLR-Ventana_Cookie_1.88_MB-d97706?style=flat-square)](#) @@ -22,7 +22,7 @@ Contacto: Telegram [@JocesLab](https://t.me/JocesLab) · correo [joshrob.sec@pro - **Estado**: Publicación v1.9.2, 2026-11-09 (verificación del estado de parches contra php.net y php-src, más el puntero de v1.0.0 a la última; calibración en imágenes oficiales de distribución, pruebas de aislamiento de stdout en FastCGI, desambiguación de arquitecturas SAPI, alcance de ASLR medido por tamaño de lectura, transcripciones por corrida de la cadena 9bie y arnés de medición publicado). - **Alcance**: Ramas de PHP 8.4.x y adyacentes (8.2, 8.3, 8.5), imágenes oficiales en contenedores, código público de prueba de concepto (PoC) y reproducción independiente en laboratorio. - **Alcance aplicativo**: Se utilizó un entorno de prueba de Nextcloud 33.0.x (PHP 8.4.21, Apache `mod_php` prefork) para validar la exposición de configuración y nivel de parche. La prueba de RCE se obtuvo sobre la imagen oficial `php:8.4.26-apache`. El fallo reside en el inicio de la petición de PHP, no en el código de Nextcloud. -- **Artefactos**: Versiones formales en PDF (`REPORT.pdf` y `RESUMEN.pdf`), instrumentación de laboratorio reproducible (`tooling/labkit/` y `tooling/docker-compose.yml`), herramienta de diagnóstico no destructiva (`tooling/checker/`), calculadoras offline de alcanzabilidad y reglas de detección. +- **Artefactos**: `PORTABILITY.es.md` (la matriz medida de qué condiciona la cadena), `TIMELINE.es.md` (cronología de la divulgación con fuentes), versiones formales en PDF (`REPORT.pdf` y `RESUMEN.pdf`), instrumentación de laboratorio reproducible (`tooling/labkit/` y `tooling/docker-compose.yml`), herramienta de diagnóstico no destructiva (`tooling/checker/`), calculadoras offline de alcanzabilidad y reglas de detección. - **Asistencia de IA**: Se utilizaron múltiples modelos de IA y subagentes especializados bajo dirección humana para revisión, orquestación de laboratorio y comprobación documental; cada hallazgo fue contrastado contra comandos, salidas reales o fuentes públicas. Ver `METHODOLOGY.md`. --- diff --git a/README.md b/README.md index d7fdf4e..c043590 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # php2shell — Independent Verification & Exploitation Boundary Audit ### CVE-2026-109618 · CVE-2026-109619 · CVE-2026-115560 -[![Release](https://img.shields.io/badge/Release-v1.9.2_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.9.2) +[![Release](https://img.shields.io/badge/Release-v1.10.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.10.0) [![Target](https://img.shields.io/badge/PHP_Targets-8.2_·_8.3_·_8.4_·_8.5-334155?style=flat-square)](#) [![CVSS](https://img.shields.io/badge/CVSS_v4.0-9.2_CRITICAL-b91c1c?style=flat-square)](#) [![ASLR](https://img.shields.io/badge/ASLR-Cookie_Window_1.88_MB-d97706?style=flat-square)](#) @@ -19,10 +19,10 @@ Contact: Telegram [@JocesLab](https://t.me/JocesLab) · email [joshrob.sec@proto --- -- **Status**: public release v1.9.2, 2026-11-09 (patch-state check against php.net and php-src, plus the v1.0.0→latest pointer; official stock distribution image calibration, FastCGI stdout isolation proofs, SAPI architecture disambiguation, measured ASLR scope per read size, per-run 9bie transcripts, and the published measurement harness). +- **Status**: public release v1.10.0, 2026-11-09 (portability matrix and disclosure timeline published, patch-state check against php.net and php-src, plus the v1.0.0→latest pointer; official stock distribution image calibration, FastCGI stdout isolation proofs, SAPI architecture disambiguation, measured ASLR scope per read size, per-run 9bie transcripts, and the published measurement harness). - **Scope**: PHP 8.4.x and adjacent branches (8.2, 8.3, 8.5), official PHP container images, public proof-of-concept code, and independent lab reproduction. - **Application scope**: a Nextcloud 33.0.x test deployment (PHP 8.4.21, Apache `mod_php` prefork) was used to validate the exposed configuration and patch level. The RCE proof was obtained on the official `php:8.4.26-apache` image. The bug is in PHP request startup, not Nextcloud code. -- **Artifacts**: formal typography PDF releases (`REPORT.pdf` and `RESUMEN.pdf`), reproducible lab instrumentation (`tooling/labkit/` and `tooling/docker-compose.yml`), non-destructive diagnostic tooling (`tooling/checker/`), offline reachability calculators, and detection rules. +- **Artifacts**: `PORTABILITY.md` (the measured matrix of what gates the chain), `TIMELINE.md` (disclosure timeline with sources), formal typography PDF releases (`REPORT.pdf` and `RESUMEN.pdf`), reproducible lab instrumentation (`tooling/labkit/` and `tooling/docker-compose.yml`), non-destructive diagnostic tooling (`tooling/checker/`), offline reachability calculators, and detection rules. - **AI-assisted**: multiple AI models and specialized subagents were used under human direction for review, lab orchestration and documentation checks; every finding was validated against commands, outputs or public sources. See `METHODOLOGY.md`. ## Executive summary @@ -75,7 +75,7 @@ Standard out-of-the-box PHP configurations use `memory_limit = 128M` and `post_m ### 2. The Session HashTable is out of the cookie window, but the channel leaks heap Using non-intrusive GDB worker probing (`php_session_reset_id` & `php_session_track_init`), we tracked `PS(id)` and session `HashTable` allocations across requests. Due to the LIFO release order of superglobals, the `HashTable` is consistently positioned **before** the `PS(id)` pointer or separated across different 2 MB allocator chunks (delta > 4.5 MB), so the forward cookie window cannot reach session data. Testing with custom destructor objects (`pDestructor`) confirmed that userland destructor ordering cannot reorder the pre-request allocations done during `rfc1867_post_handler`. -The forged cookie does over-read past its own filler: at the 4000/5000-byte read size the measured values carry 525-841 bytes outside the session alphabet, including **47 pointer-like values** inside the id's 2 MB chunk (Zend MM bin-64 free-list links), disclosing the chunk base. The distance from that base to libphp text varied between `0x289bc00` and `0x2a7dc00` (1.88 MB) across 20 ASLR-on runs, with the low 12 bits constant (`delta % 0x1000 == 0xc00`), so **at this read size** the leak narrows libphp to a ~481-page window without pinning it. That is a property of the 4000-byte read, not of the channel: with a forged `zend_string` of `len=0xC8000` and `display_errors=On` the same cookie returns ~800 KB and the chain pins libphp. `9bie/php2shell-bypass-aslr-exp` reports 10/10 on the stock profile; under `display_errors=On` we replicated the chain 8/8 with ASLR on and 5/5 with ASLR off, while under `display_errors=Off` with ASLR on it stops at stage 1 in 0/3 runs in the same lab profile (`raw/9bie-display-off-3run.txt`); the author's 128M/150-worker Off profile was not reproduced here (`POCS.md`, 9bie entry; raw sweeps `raw/9bie-aslr-on-8run.txt` and `raw/9bie-aslr-off-5run.txt`, stage-2 base resolution in `raw/rce-on-aslr-on-8426.txt`). The session-table read remains unreachable and the channel stays `mod_php`-only and single-shot per OOM. See `validation/`. +The forged cookie does over-read past its own filler: at the 4000/5000-byte read size the measured values carry 525-841 bytes outside the session alphabet, including **47 pointer-like values** inside the id's 2 MB chunk (Zend MM bin-64 free-list links), disclosing the chunk base. The distance from that base to libphp text varied between `0x289bc00` and `0x2a7dc00` (1.88 MB) across 20 ASLR-on runs, with the low 12 bits constant (`delta % 0x1000 == 0xc00`), so **at this read size** the leak narrows libphp to a ~481-page window without pinning it. That is a property of the 4000-byte read, not of the channel: with a forged `zend_string` of `len=0xC8000` and `display_errors=On` the same cookie returns ~800 KB and the chain pins libphp. `9bie/php2shell-bypass-aslr-exp` reports 10/10 on the stock profile; under `display_errors=On` we replicated the chain 8/8 with ASLR on and 5/5 with ASLR off, while under `display_errors=Off` with ASLR on it stops at stage 1 in 0/3 runs in the same lab profile (`raw/9bie-display-off-3run.txt`); the author's 128M/150-worker Off profile was not reproduced here (`POCS.md`, 9bie entry; the full matrix is in `PORTABILITY.md`; raw sweeps `raw/9bie-aslr-on-8run.txt` and `raw/9bie-aslr-off-5run.txt`, stage-2 base resolution in `raw/rce-on-aslr-on-8426.txt`). The session-table read remains unreachable and the channel stays `mod_php`-only and single-shot per OOM. See `validation/`. ### 3. The leak channel is `mod_php`-only On FPM, payload A makes the worker exit (pool logs `child ... exited with code 70`, nginx returns 502); the follow-up request runs in a new process with a fresh 32-byte id and never reuses the stale one. diff --git a/TIMELINE.es.md b/TIMELINE.es.md new file mode 100644 index 0000000..44835a2 --- /dev/null +++ b/TIMELINE.es.md @@ -0,0 +1,42 @@ +# Cronología de la divulgación (UTC) + +Compilada de fuentes públicas, con nuestros eventos de laboratorio marcados como tales. El +objetivo es que el orden sea verificable: varias afirmaciones en circulación solo tienen sentido +contra esta línea de tiempo. + +| Fecha/hora | Evento | Fuente | +|---|---|---| +| 2026-08-11 | Calif reporta el primer bug; OpenAI el mismo día por la misma causa raíz | PHP Foundation | +| 2026-08-24 | Wiz reporta sus hallazgos | Wiz | +| 2026-09-21 | PHP reconoce los reportes y empieza el fix coordinado | Wiz | +| 2026-10-20 15:00 | Filtración de los IDs de CVE con hora y CVSS 10.0 incorrectos | PHP Foundation | +| 2026-10-20 19:30 | PHP adelanta el pre-aviso y mueve el release del 22 al 21 | php.net archive | +| 2026-10-21 03:02 | Cloudflare publica reglas WAF preventivas | Cloudflare | +| 2026-10-21 07:41 | Chainguard publica imágenes con el fix, antes del release | PHP Foundation | +| 2026-10-21 07:43 | Cloudflare reporta los primeros ataques | Sansec / Cloudflare | +| 2026-10-21 11:35 | Hadrian reporta explotación "a escala" vía upload progress | Brandefense | +| 2026-10-21 14:00 | Release coordinado: 8.2.35, 8.3.36, 8.4.27, 8.5.12 | php.net | +| 2026-10-21 14:17 | NVD registra ambas CVE | NVD | +| 2026-10-21 19:36 | **Nuestro Nextcloud se remedia a 8.4.27** | este proyecto | +| 2026-10-21 20:30 | Huntress: sin explotación en su base de socios | Brandefense | +| 2026-10-21 21:03 | KEV de CISA sigue sin listar ninguna de las dos | CISA | +| 2026-10-22 | **Lab:** RCE 3/3 en `php:8.4.26-apache`, `display_errors=On`, ASLR encendido | este proyecto | +| 2026-10-22 | **Lab:** RCE 3/3 con `display_errors=Off` y ASLR apagado | este proyecto | +| 2026-10-22 | Amazon Threat Intelligence: primeros intentos el 17-oct, cuatro días antes del parche, desde redes de anonimización | BleepingComputer | +| 2026-10-22 | **Lab:** la cookie forjada filtra heap y acota `libphp` a una ventana de 1.88 MB; la HashTable de sesión queda fuera de alcance | este proyecto | +| 2026-10-22 | **Lab, FPM:** cadena E reproducida (UAF cross-request de `BG(unserialize)`, 502 determinista); 8.4.27 la cierra | este proyecto | +| 2026-10-22 | **Lab, FPM:** escritura de 16 B y RIP en `zend_error_cb = &system` confirmados; faltaba el argumento | este proyecto | +| 2026-10-23 | **Lab, FPM:** RCE completa — `sapi_module.ub_write = &system` ejecuta `id` como www-data, con ASLR apagado y sesión plantada de 24 B | este proyecto | +| 2026-10-23 | PHP publica advisory informativo de un tercer issue, **CVE-2026-115560**; sin fix en las versiones de emergencia, mitigación `session.upload_progress.enabled=0` | php.net / oss-sec | +| 2026-10-24 | **Lab:** el leak de cookie no existe en FPM (A mata al worker, exit 70); en `mod_php` el leak y la primitiva de `BG(unserialize)` colgante son mutuamente excluyentes — la ruta Off + ASLR on queda cerrada por ambas vías | este proyecto | +| 2026-10-24 | **Lab:** bypass de `post_max_size` con chunked confirmado (claim de chain D de ikow): chunked 30 MB parsea completo, chunked 70 MB hace OOM en request startup con 128M/8M | este proyecto | +| 2026-11-09 | La última estable sigue siendo 8.4.27 / 8.5.12; 8.4.28 y 8.5.13 son solo RC1; CVE-2026-115560 sigue sin fix estable | php.net, php-src | + +## Notas + +- El release estaba previsto para el 22-oct y se adelantó por la filtración. +- Chainguard publicó el fix en imágenes seis horas antes del release: hubo diff binario + disponible mientras los demás esperaban. Eso explica intentos de explotación sin una PoC + pública validada. +- CVSS: 9.2 v4.0 (AC:H, AT:P) del vendor, 9.8 v3.1 (AC:L) de NVD. No es contradicción — v3.1 no + puede expresar los "attack requirements". diff --git a/TIMELINE.md b/TIMELINE.md new file mode 100644 index 0000000..ee7ccea --- /dev/null +++ b/TIMELINE.md @@ -0,0 +1,41 @@ +# Disclosure timeline (UTC) + +Compiled from public sources, with our own lab events marked as such. The purpose is to make +the order of events checkable: several claims in circulation only make sense against it. + +| Date/time | Event | Source | +|---|---|---| +| 2026-08-11 | Calif reports the first bug; OpenAI reports the same root cause the same day | PHP Foundation | +| 2026-08-24 | Wiz reports its findings | Wiz | +| 2026-09-21 | PHP acknowledges the reports and starts the coordinated fix | Wiz | +| 2026-10-20 15:00 | CVE ids leak with the wrong time and a CVSS 10.0 | PHP Foundation | +| 2026-10-20 19:30 | PHP moves the pre-notice up and the release from the 22nd to the 21st | php.net archive | +| 2026-10-21 03:02 | Cloudflare publishes preventive WAF rules | Cloudflare | +| 2026-10-21 07:41 | Chainguard ships images with the fix, before the release | PHP Foundation | +| 2026-10-21 07:43 | Cloudflare reports the first attacks | Sansec / Cloudflare | +| 2026-10-21 11:35 | Hadrian reports exploitation "at scale" through upload progress | Brandefense | +| 2026-10-21 14:00 | Coordinated release: 8.2.35, 8.3.36, 8.4.27, 8.5.12 | php.net | +| 2026-10-21 14:17 | NVD registers both CVEs | NVD | +| 2026-10-21 19:36 | **Our Nextcloud is remediated to 8.4.27** | this project | +| 2026-10-21 20:30 | Huntress: no exploitation in its partner base | Brandefense | +| 2026-10-21 21:03 | CISA KEV still does not list either CVE | CISA | +| 2026-10-22 | **Lab:** RCE 3/3 on `php:8.4.26-apache`, `display_errors=On`, ASLR on | this project | +| 2026-10-22 | **Lab:** RCE 3/3 with `display_errors=Off` and ASLR off | this project | +| 2026-10-22 | Amazon Threat Intelligence: first exploitation attempts on 17 Oct, four days before the patch, from anonymising networks | BleepingComputer | +| 2026-10-22 | **Lab:** the forged cookie leaks heap and narrows `libphp` to a 1.88 MB window; the session HashTable stays out of reach | this project | +| 2026-10-22 | **Lab, FPM:** chain E reproduced (cross-request `BG(unserialize)` UAF, deterministic 502); 8.4.27 closes it | this project | +| 2026-10-22 | **Lab, FPM:** 16-byte write and RIP at `zend_error_cb = &system` confirmed; the argument was still missing | this project | +| 2026-10-23 | **Lab, FPM:** full RCE — `sapi_module.ub_write = &system` runs `id` as www-data, with ASLR off and a planted 24-byte session | this project | +| 2026-10-23 | PHP publishes an informational advisory for a third issue, **CVE-2026-115560**; no fix in the emergency releases, mitigation `session.upload_progress.enabled=0` | php.net / oss-sec | +| 2026-10-24 | **Lab:** the cookie leak does not exist on FPM (A kills the worker, exit 70); on `mod_php` the leak and the dangling `BG(unserialize)` primitive are mutually exclusive — the Off + ASLR-on route is closed on both paths | this project | +| 2026-10-24 | **Lab:** `post_max_size` bypass with chunked confirmed (ikow's chain D claim): 30 MB chunked parses whole, 70 MB chunked OOMs during request startup at 128M/8M | this project | +| 2026-11-09 | Latest stable is still 8.4.27 / 8.5.12; 8.4.28 and 8.5.13 are RC1 only; CVE-2026-115560 still has no stable fix | php.net, php-src | + +## Notes + +- The release was planned for 22 Oct and moved up because of the leak. +- Chainguard shipped the fix in images six hours before the release, so a binary diff was + available while everyone else waited. That explains exploitation attempts without a validated + public PoC. +- CVSS: 9.2 v4.0 (AC:H, AT:P) from the vendor, 9.8 v3.1 (AC:L) from NVD. Not a contradiction — + v3.1 cannot express "attack requirements".