From 7415b0c7378a8d8f480e7070791149e361d68fcb Mon Sep 17 00:00:00 2001 From: JoshRob Date: Mon, 9 Nov 2026 21:29:12 -0600 Subject: [PATCH] 1.11.0: the Off + ASLR-on row was measured against the previous chain --- COVERAGE.es.md | 6 +-- COVERAGE.md | 15 +++--- PORTABILITY.es.md | 4 ++ PORTABILITY.md | 5 ++ README.es.md | 4 +- README.md | 6 +-- raw/9bie-pure-off-aslr-on-10run.txt | 77 +++++++++++++++++++++++++++++ 7 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 raw/9bie-pure-off-aslr-on-10run.txt diff --git a/COVERAGE.es.md b/COVERAGE.es.md index bb08c6c..5730ed5 100644 --- a/COVERAGE.es.md +++ b/COVERAGE.es.md @@ -32,7 +32,7 @@ Definiciones de estado: | Aborto OOM / Corrupción de heap | Afectado por versión; no explotado | fail | pass | pass (0/3) | Control 8.4.27: 0/5 sin abortos; ramas corregidas bloqueadas en etapa 1 | | RCE, `display_errors=On`, ASLR On | No probado | fail (3/3) | pass (0/5) | pass (0/3) | mod_php prefork; re-ejecución de control 3/3 en el mismo arnés | | RCE, `display_errors=Off`, ASLR Off | No probado | fail (3/3) | pass | No probado aquí | Build con offsets calibrados; ramas fijas no probadas en este perfil | -| RCE, `display_errors=Off`, ASLR On | No probado | No alcanzado: cadena de cookie 0/3 controlado; cadena 9bie también 0/3, se detiene en la etapa 1 (su 8/8 requiere `display_errors=On`) | pass | No probado | Reclamación 12/12 retractada; la cookie de 4000 B depende del estado y la lectura de 0xC8000 necesita salida de errores para la etapa 1 | +| RCE, `display_errors=Off`, ASLR On | No probado | **alcanzado 10/10** con la cadena `pure/` del upstream en stock 128M/150 workers (10 bases `libphp` distintas, ejecución real de comando), 0/5 en 8.4.27; nuestra cadena de cookie 0/3 y la cadena 9bie anterior 0/3 | pass | No probado | Reclamación 12/12 retractada; la cookie de 4000 B depende del estado y la lectura de 0xC8000 necesita salida de errores para la etapa 1 | | FPM decode OOM / `BG(unserialize)` colgante | No probado | Crash UAF determinista (502) comprobado en watchpoint; con struct reclamado por multipart, escritura controlada de 16 B en `sapi_module.ub_write = &system` -> RCE en lab | No probado | No probado | CVE-2026-109619; mecanismo por defecto = enlace de free-list sobre `used_slots`; variante controlada = fake `var_dtor_entries` (ASLR off, sesión de 24 B); 8.4.27 estable | | Divulgación de memoria vía cookie | No probado | Parcial (cookie de 4000 B revela punteros de heap; HashTable inalcanzable; libphp acotado a ~1.88 MB con esa lectura, fijado con `zend_string` de 0xC8000 -- cadena 9bie, 8/8 con ASLR On) | pass | pass (0/3) | Dependiente de geometría; ramas fijadas: fuga no reproducible | | CVE-2026-115560 (Informativo, 2026-10-23) | No probado | No probado | No corregido | No probado | Condición estrecha (config `php_binary` + `ini_set()` a `php` en runtime); mitigación `session.upload_progress.enabled=0` | @@ -71,7 +71,7 @@ Ramas fijadas 8.3.36 y 8.2.35 (mismo arnés, 3 corridas c/u): 0/3, bloqueadas en | PoC / Herramienta | Reclamación | Estado Independiente | |---|---|---| -| 9bie | Bypass de ASLR / RCE | pass en laboratorio con `display_errors=On` (8/8 ASLR On, 8 bases distintas; 5/5 Off, 1 base); 0/3 con `display_errors=Off` + ASLR On en el mismo perfil de laboratorio; `raw/9bie-aslr-on-8run.txt`, `raw/9bie-aslr-off-5run.txt`, `raw/9bie-display-off-3run.txt`; el autor reporta 10/10 con el perfil stock de 150 workers; dependiente de la memoria del host | +| 9bie | Bypass de ASLR / RCE | pass en laboratorio con `display_errors=On` (8/8 ASLR On, 8 bases distintas; 5/5 Off, 1 base); la cadena anterior 0/3 con `display_errors=Off` + ASLR On; la cadena `pure/` del upstream **10/10** con `Off` + ASLR On en el perfil stock 128M/150 workers (`raw/9bie-pure-off-aslr-on-10run.txt`); `raw/9bie-aslr-on-8run.txt`, `raw/9bie-aslr-off-5run.txt`, `raw/9bie-display-off-3run.txt`; el autor reporta 10/10 con el perfil stock de 150 workers; dependiente de la memoria del host | | ikow A/B/C | Cadenas | Código analizable; A/C requieren prerrequisitos; B es local | | ikow D/E | Scripts de verificación | Públicos; exploits completos cerrados/de pago | | GoldraK scanner A/C/E | 24/24 vulnerable | pass para A/C/E; no mide OOM; 8.4.27 omitido de la tabla | @@ -83,7 +83,7 @@ Ramas fijadas 8.3.36 y 8.2.35 (mismo arnés, 3 corridas c/u): 0/3, bloqueadas en | Capa | Qué se probó | Generalización | |---|---|---| -| Oficial `php:8.4.26-apache` | RCE 3/3 On/ASLR On; 3/3 Off/ASLR Off; cadena de cookie 0/3 Off/ASLR On, cadena 9bie 8/8 con ASLR On y 0/3 con ASLR On + `display_errors=Off` | Primitiva a nivel de motor PHP; dependiente del build/allocator | +| Oficial `php:8.4.26-apache` | RCE 3/3 On/ASLR On; 3/3 Off/ASLR Off; cadena de cookie 0/3 Off/ASLR On, cadena `pure/` de 9bie 10/10 con `Off` + ASLR On en stock 128M (0/3 en 8M) | Primitiva a nivel de motor PHP; dependiente del build/allocator | | Oficial `php:8.4.27-apache` | 0/5 control | Parche oficial para la clase OOM | | Nextcloud 33.0.x test, PHP 8.4.21 `mod_php` | Versión/configuración validada; RCE no ejecutado | Primitiva independiente de la app; dependiente del despliegue | | FPM A/C/E | RCE con prerrequisitos de laboratorio | Dependiente de aplicación/configuración | diff --git a/COVERAGE.md b/COVERAGE.md index 1167b44..1206fc6 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -31,7 +31,7 @@ Status definitions: | OOM abort / heap corruption | affected by version; not exploited | fail | pass | pass (0/3) | 8.4.27 control: 0/5, no aborts; fixed branches blocked at stage 1 | | RCE, `display_errors=On`, ASLR on | not tested | fail (3/3) | pass (0/5) | pass (0/3) | mod_php prefork; control rerun 3/3 in the same harness | | RCE, `display_errors=Off`, ASLR off | not tested | fail (3/3) | pass | not tested here | offset-calibrated build; fixed branches not exercised in this profile | -| RCE, `display_errors=Off`, ASLR on | not tested | not achieved: cookie-only chain 0/3 controlled; 9bie chain also 0/3, stops at stage 1 (its 8/8 needs `display_errors=On`) | pass | not tested | 12/12 claim withdrawn; the 4000-byte cookie is state-dependent, and the 0xC8000 read needs error output for stage 1 | +| RCE, `display_errors=Off`, ASLR on | not tested | **achieved 10/10** with the upstream `pure/` chain on stock 128M/150 workers (10 distinct `libphp` bases, real command execution), 0/5 on 8.4.27; our cookie-only chain 0/3 and the previous 9bie chain 0/3 | pass | not tested | 12/12 claim withdrawn; the 4000-byte cookie is state-dependent, and the 0xC8000 read needs error output for stage 1 | | FPM session decode OOM / stale BG | not tested | deterministic UAF crash (502) verified with watchpoint; with the multipart-reclaimed struct, controlled 16-byte write to `sapi_module.ub_write` = `&system` -> command execution in the lab | not tested | not tested | CVE-2026-109619; default mechanism = allocator free-list encoding over `used_slots`; controlled variant = fake `var_dtor_entries` (ASLR off, planted 24-byte session); 8.4.27 stable (early reset) | | Memory disclosure via cookie | not tested | partial (4000-byte cookie discloses heap pointers; session table not reachable; libphp narrowed ~1.88 MB at that read size, pinned with a `len=0xC8000` forged `zend_string` -- 9bie chain, 8/8 ASLR-on reproduced) | pass | pass (0/3) | geometry-dependent; fixed branches: leak not reproducible | | CVE-2026-115560 (informational, 2026-10-23) | not tested | not tested | not fixed | not tested | narrow condition (config `php_binary`/`php_serialize` + runtime `ini_set()` switch to `php`); mitigation `session.upload_progress.enabled=0` | @@ -71,7 +71,7 @@ leak). Raw: `raw/fixed-branches-verify.txt`. | PoC | Claim | Independent status | |---|---|---| -| 9bie | ASLR bypass / RCE | pass in the lab with `display_errors=On` (8/8 ASLR-on, 8 distinct bases; 5/5 off, 1 base); 0/3 with `display_errors=Off` + ASLR on in the same lab profile; `raw/9bie-aslr-on-8run.txt`, `raw/9bie-aslr-off-5run.txt`, `raw/9bie-display-off-3run.txt`; author reports 10/10 on the stock 150-worker profile; host-layout dependent | +| 9bie | ASLR bypass / RCE | pass in the lab with `display_errors=On` (8/8 ASLR-on, 8 distinct bases; 5/5 off, 1 base); the previous chain 0/3 with `display_errors=Off` + ASLR on; the upstream `pure/` chain **10/10** with `Off` + ASLR on on the stock 128M/150-worker profile (`raw/9bie-pure-off-aslr-on-10run.txt`); `raw/9bie-aslr-on-8run.txt`, `raw/9bie-aslr-off-5run.txt`, `raw/9bie-display-off-3run.txt`; author reports 10/10 on the stock 150-worker profile; host-layout dependent | | ikow A/B/C | chains | readable; A/C need prerequisites; B local | | ikow D/E | verification scripts | public; full exploits not public | | GoldraK A/C/E scanner | 24/24 vulnerable | pass for A/C/E; does not measure OOM; 8.4.27 omitted from the table | @@ -83,7 +83,7 @@ leak). Raw: `raw/fixed-branches-verify.txt`. | Layer | What was tested | Generalization | |---|---|---| -| Official `php:8.4.26-apache` | RCE 3/3 On/ASLR on; 3/3 Off/ASLR off; cookie-only chain 0/3 Off/ASLR on, 9bie chain 8/8 with ASLR on and 0/3 with ASLR on + `display_errors=Off` | PHP-level primitive; build/allocator dependent | +| Official `php:8.4.26-apache` | RCE 3/3 On/ASLR on; 3/3 Off/ASLR off; cookie-only chain 0/3 Off/ASLR on, 9bie `pure/` chain 10/10 with `Off` + ASLR on at stock 128M (0/3 at 8M) | PHP-level primitive; build/allocator dependent | | Official `php:8.4.27-apache` | 0/5 control | Patch for the OOM class | | Nextcloud 33.0.x test deployment, PHP 8.4.21 `mod_php` | Version/config validated; RCE not executed | App-independent primitive; deployment-specific | | FPM A/C/E | RCE with lab prerequisites | App/config-dependent | @@ -103,9 +103,12 @@ leak). Raw: `raw/fixed-branches-verify.txt`. discloses heap (id chunk base) but never the session HashTable, and at the 4000-byte read it narrows `libphp` to a 1.88 MB window. A larger OOB read (`len=0xC8000` forged `zend_string`) pins `libphp` and the 9bie chain reaches RCE with ASLR on; we replicated it 8/8 ASLR-on and - 5/5 off. What stays closed in our measurements is the combination: the channel is single-shot - per OOM and `mod_php`-only, and on `mod_php` the leak and the stale-`BG(unserialize)` - primitive are mutually exclusive. + 5/5 off. What stays closed is the combination **with our cookie chain**: that channel is single-shot + per OOM and `mod_php`-only, and there the leak and the stale-`BG(unserialize)` primitive are + mutually exclusive. That does not bound the family: the upstream `pure/` chain does not use + `BG(unserialize)` and closes `Off` + ASLR-on at the stock 128M geometry — 10/10 measured on + 2026-11-09, 0/5 on 8.4.27. At 8M the chain is 0/3 and needs recalibration, and the cold-start + rate is not characterized. - 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. diff --git a/PORTABILITY.es.md b/PORTABILITY.es.md index bd8a1c3..7258814 100644 --- a/PORTABILITY.es.md +++ b/PORTABILITY.es.md @@ -26,6 +26,10 @@ cadena y cuáles solo lo parecen. | `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 | +> La fila `Off` + ASLR On de arriba es el **perfil de laboratorio de 8M**. La geometría stock es +> otra celda: con `php.ini-production` (128M) y la cadena `pure/` del upstream, `display_errors=Off` +> con ASLR On da 10/10 — ver `COVERAGE.es.md` §1 y `raw/9bie-pure-off-aslr-on-10run.txt`. + ## Lo que dice la matriz 1. **`memory_limit` no es el candado: lo es `post_max_size`.** Con 512M/480 MB la cadena corre diff --git a/PORTABILITY.md b/PORTABILITY.md index a030e30..3357c00 100644 --- a/PORTABILITY.md +++ b/PORTABILITY.md @@ -26,6 +26,11 @@ and which ones only look like gates. | `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 | +> The `Off` + ASLR-on row above is the **8M lab profile**. The stock geometry is a different +> cell: with `php.ini-production` (128M) and the upstream `pure/` chain, `display_errors=Off` +> with ASLR on gives 10/10 — see `COVERAGE.md` §1 and +> `raw/9bie-pure-off-aslr-on-10run.txt`. + ## What the matrix says 1. **`memory_limit` is not the gate; `post_max_size` is.** At 512M/480 MB the chain runs the diff --git a/README.es.md b/README.es.md index d3dc570..f6a8926 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.10.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.10.0) +[![Release](https://img.shields.io/badge/Release-v1.11.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.11.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)](#) @@ -103,7 +103,7 @@ La evaluación empírica de la primitiva E-OOM en PHP-FPM 8.4.26 (`fpmdbg` detr |---|---|---|:---:|---| | mod_php 8.4.26 | `display_errors=On` · ASLR On | RCE alcanzado (3/3 ejecuciones exitosas, `uid=33`) | `CONFIRMADO` | Sección 1 | | mod_php 8.4.26 | `display_errors=Off` · ASLR Off | RCE alcanzado (3/3 ejecuciones exitosas, `uid=33`) | `CONFIRMADO` | Sección 1 | -| mod_php 8.4.26 | `display_errors=Off` · ASLR On | Cadena de cookie: solo divulgación de heap con 4000 B (ventana de 1.88 MB), 0/3 RCE. Cadena 9bie: 0/3 en el perfil de laboratorio, se detiene en la etapa 1 (`raw/9bie-display-off-3run.txt`) | `NO ALCANZADO` | Sección 11 | +| mod_php 8.4.26 | `display_errors=Off` · ASLR On | Cadena de cookie: solo divulgación de heap con 4000 B (ventana de 1.88 MB), 0/3 RCE. Cadena 9bie anterior: 0/3 en el perfil de laboratorio, se detiene en la etapa 1 (`raw/9bie-display-off-3run.txt`); cadena `pure/` del upstream: **10/10** en stock 128M/150 workers con `display_errors=Off` y ASLR On (`raw/9bie-pure-off-aslr-on-10run.txt`), 0/5 en 8.4.27 | `NO ALCANZADO` | Sección 11 | | FPM 8.4.26 (Leak) | FastCGI Stream · ASLR On | Worker muere en OOM (`exit 70`, 502); worker nuevo con heap limpio | `SIN CANAL` | Sección 10 | | CVE-2026-115560 | `php_binary` + runtime `ini_set()` | Fallo de decode observado; sin impacto en seguridad de memoria | `NO CONCLUYENTE` | Sección 12 | | Chunked `post_max_size` | Sin parche (8.2.34 / 8.3.35 / 8.4.26) | 30M parsea completo, 70M causa OOM en límite de 128M | `BYPASS CONFIRMADO` | Sección 12 | diff --git a/README.md b/README.md index c043590..6b482c6 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.10.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.10.0) +[![Release](https://img.shields.io/badge/Release-v1.11.0_(2026--11--09)-0284c7?style=flat-square&logo=github)](https://github.com/JoshRob297/php2shell-research/releases/tag/v1.11.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,7 +19,7 @@ Contact: Telegram [@JocesLab](https://t.me/JocesLab) · email [joshrob.sec@proto --- -- **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). +- **Status**: public release v1.11.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**: `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. @@ -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; 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/`. +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 the previous chain stops at stage 1 under `display_errors=Off` with ASLR on (0/3 in the same lab profile, `raw/9bie-display-off-3run.txt`). That limit belongs to that chain: the upstream `pure/` chain, public since commit `d6f888e`, reproduces **10/10** on the stock 128M/150-worker profile with `display_errors=Off` and ASLR on (`raw/9bie-pure-off-aslr-on-10run.txt`), and 0/5 on 8.4.27 (`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/raw/9bie-pure-off-aslr-on-10run.txt b/raw/9bie-pure-off-aslr-on-10run.txt new file mode 100644 index 0000000..262ccfd --- /dev/null +++ b/raw/9bie-pure-off-aslr-on-10run.txt @@ -0,0 +1,77 @@ +9bie/php2shell-bypass-aslr-exp — `pure/` chain under display_errors=Off + ASLR on +Verified 2026-11-09 by T-GAP1. Transcript condensed from the run log; the operational bodies +stay in the private lab log. + +upstream commit: d6f888ebd3f0e188bb662e16f23747afdf35e42f (2026-10-24, "Merge PR #8: add Japanese README") +--- target --- +PHP 8.4.26 (cli) (built: Oct 21 2026 00:31:19) (NTS) +display_errors => Off => Off +display_startup_errors => Off => Off +memory_limit => 128M => 128M +post_max_size => 8M => 8M +session.sid_length => 32 => 32 +session.upload_progress.enabled => On => On +session.use_strict_mode => Off => Off +--- MPM (official prefork) --- +StartServers 5 | MinSpareServers 5 | MaxSpareServers 10 | MaxRequestWorkers 150 | MaxConnectionsPerChild 0 +--- webroot --- + +--- image --- +php:8.4.26-apache sha256:60111400ff77bdc44fa79b4f072afe49a42abba71e0846ad496be4761dc9c11a +host randomize_va_space: 2 (no setarch, no gdb, no symbols, nothing from /proc of the target) + +=== canonical run === +$ ./pure/run_pure.sh # p2s-pure-18110, 127.0.0.1:18110 +$ python3 exploit.py --port 18110 --docker docker --cmd 'id>/out/p6' --proof out/p6 + + pinned 150/150 workers (149 holders) +[try 1] A1: END-cleanup send_cookie OOM +[try 1] B1: stale-id OOB read (chunk base + libphp) + arch=x86_64 decoded=819208 zval_ptr_dtor=0x7fef35324b60 -> libphp=0x7fef34cc6000 + chunk buckets: [('0x7fef32a00000', 7495), ('0x7fef35200000', 1095), ('0x7fef33000000', 87), ('0x7fef32800000', 79)] +[try 1] A2 + B2: main=0x7fef33000000 S=0x7fef330f8398 zif_system=0x7fef3512b370 +[4/4] RCE OK; proof out/p6: +uid=33(www-data) gid=33(www-data) groups=33(www-data) + +=== 10 runs, fresh ASLR each (docker restart before every run) === +run | libphp | main | result + 1 | 0x7f783c99e000 | 0x7f783ae00000 | RCE OK + 2 | 0x7f7c502e2000 | 0x7f7c4e600000 | RCE OK + 3 | 0x7f27e14cf000 | 0x7f27df800000 | RCE OK + 4 | 0x7f5877497000 | 0x7f5875800000 | RCE OK + 5 | 0x7f9b79692000 | 0x7f9b77a00000 | RCE OK + 6 | 0x7fe1a1994000 | 0x7fe19fe00000 | RCE OK + 7 | 0x7f9360223000 | 0x7f935e600000 | RCE OK + 8 | 0x7fd1c7c70000 | 0x7fd1c6000000 | RCE OK + 9 | 0x7f562961b000 | 0x7f5627a00000 | RCE OK + 10 | 0x7fb4a5dff000 | 0x7fb4a4200000 | RCE OK + +10/10, 10 distinct libphp bases, 10 distinct main allocations, decoded=819208 in every run. + +=== control: php:8.4.27-apache, same profile, same exploit === + pinned 150/150 workers (149 holders) +[try 1] A1: END-cleanup send_cookie OOM +[try 1] B1: stale-id OOB read (chunk base + libphp) + leak failed: RuntimeError('no heap pointers in leak window') +[try 2] A1: END-cleanup send_cookie OOM +[try 2] B1: stale-id OOB read (chunk base + libphp) + leak failed: RuntimeError('no heap pointers in leak window') +[try 3] A1: END-cleanup send_cookie OOM +[try 3] B1: stale-id OOB read (chunk base + libphp) + leak failed: RuntimeError('no heap pointers in leak window') +[4/4] all attempts failed +0/5 runs, identical output. + +=== boundaries of this row === +- Requires the stock 128M geometry + the official php.ini-production. At memory_limit=8M the + chain is 0/3: A1 does not exhaust memory, so B1 never gets its stale id. +- The repo's own tooling/docker-compose.yml does not reproduce it: vuln-8426 loads no php.ini + (Loaded Configuration File => (none)) and reports display_errors => STDOUT. Adding only + display_errors=Off on top of hardening-off.ini gives 0/3, because B1 finds no pointers: the + chain is calibrated for php.ini-production, not for a conf.d overlay. +- Cold start not characterized: a first run on a freshly created target gave 0/3 with + 'no heap pointers in leak window'; after docker restart the same target gave 3/3. The 10/10 + above restarts before each run. +- 149 holders pinned; if MaxRequestWorkers differs, --workers N has to be adjusted, and if more + than N-1 are busy the requests queue and B1 may land on another child. +- FPM and CVE-2026-115560 were not measured: the stock path already closed the question.