CVE-2026-109618/109619: Two Independent Pre-Auth RCE Chains on PHP 8.5.11 (Lab Reproduction & PoC)
Summary
Why it matters
- CVE
- CVE-2026-109618
- Vendor
- PHP
- Product
- PHP
- Affected versions
- <= 8.5.11 (fixed in 8.5.12, 8.4.27, 8.3.36, 8.2.35)
- CVSS
- 9.2
- Published
- Collected
📦 Full-Text Content Snapshot
R2 Archive · Full Article PreservedAuthor: dinosn · Published: October 21, 2026 · Source: github.com/dinosn/php-cve-2026-10961x-lab-poc
Lab reproduction and vulnerability-research package for the PHP 8.5.11 php2shell class (CVE-2026-109618, CVE-2026-109619), October 2026. Status: Proven — self-contained RCE (four plain HTTP requests, no debugger, no local access), plus amplification, patch-gap, and FPM stale-state findings below.
Results at a Glance
| # | Finding | Classification |
|---|---|---|
| 1 | End-to-end self-contained RCE on PHP 8.5.11 — four plain HTTP requests, in-band address sourcing via PS(id)→cookie disclosure, both bases from one 512-byte PHPSESSID leak; 12/12 fires, uid=33, 8 ASLR generations | Proven — self-contained RCE |
| 2 | 52x memory amplification via duplicate 6-byte multipart headers — defeats post_max_size (2.57 MB body OOMs a 128M limit) on PHP 7.4–8.5.11 | Proven — lab, all four versions |
| 3 | Three exploit paths survive the 8.5.12 security patch (var_destroy re-entry UAF at $n=1015$, serialize-handler mismatch, upload-progress injection) | Proven — ASAN on GPG-verified 8.5.12 |
| 4 | FPM stale BG(unserialize): worker survives a session-decode OOM, stale state is consumed by the next request's rfc1867 window, heap-shaped consumption SIGSEGVs the worker | Proven — mechanism + DoS; RCE unproven |
| 5 | Content-Type separator bypass (multipart/form-data, boundary=X) — every published Content-Type-anchored WAF signature is bypassable | Proven — SAPI.c parser behavior |
| 6 | Both CVE chains dead on patched 8.5.12 (token-identical configure, GPG-verified source) | Proven — negative control |
| 7 | The public "cached-zval session.upload_progress" theory — refuted on 4 stock PHP versions + 3 LiteSpeed vendor builds, all cleanup/concurrency corners | Refuted — exhaustive |
| 8 | The same stale-globals class on PHP 7.2 (EOL): both bases leak from one in-band cookie, zif_system dispatch runs as uid=33 — 6/6 under stock post_max_size=8M, incl. 5-worker pools | Proven — lab, 7.2.34 + 7.2.21 |
| 9 | The 129 MiB producer is replaceable by a 1.41 MB header-dense part (470k × x:\n) with zero recalibration — 3/3 full chain, identical bases | Proven — lab |
Chains
Two independent memory-corruption chains reach attacker-controlled indirect calls on the stock php:8.5.11-apache image; the end-to-end wrapper combines Chain B's mechanism with a novel in-band disclosure for a fully remote fire.
| Feature | Chain A — stale bucket | Chain B — dangling reference |
|---|---|---|
| Mechanism | OOM bailout leaves a HashTable bucket published but uninitialized; shutdown consumes it as a fake zval → fake array → pDestructor | Pre-RINIT OOM leaves PS(http_session_vars) holding a dangling zend_reference; a live 31-byte string reclaims it; forged reference → zend_hash_destroy pDestructor |
| Allocator | tracked/system (USE_ZEND_ALLOC=0) | stock default Zend MM |
| Terminal | libc system() (gdb-calibrated addresses) | zif_system @ libphp+0x5996e0 (no libc) |
| Debugger at fire time | calibration only | none — FIRE_GDB=0 |
| Reproducibility | 2 randomized parent generations | repeated, incl. cross-run |
End-to-end receipt (12/12 fires, 8 ASLR generations, no debugger, no local access)
Leak-A arms a dangling PS(id) (1001st variable trips max_input_vars → headers_sent=1; the empty-cookie reset_id failure keeps send_cookie=1; a 24 MiB field OOMs pre-RINIT). Leak-B lands a 257-element array whose zend_array overlaps PS(id)−0x10: ZSTR_LEN reads nTableSize=512 and the URL-encoded cookie carries pDestructor (→ libphp base) plus nine arData heap pointers (majority vote → chunk base). The measured chunk−libphp delta is not constant across generations (−0x1a00000 / +0x3600000 / −0x1c00000), so the disclosure is genuinely load-bearing. The fire then runs with the leaked addresses. Prerequisites: display_errors=On, post_max_size above the OOM field, memory_limit below it, single-worker prefork.
A status='HTTP/1.1 200 OK' → OOM fatal, worker survives, PS(id) armed
B Set-Cookie: PHPSESSID=<512-byte encoded disclosure>
FIRE → MARKER=uid=33(www-data) gid=33(www-data) groups=33(www-data)
FIRE_OK (×12 across 8 distinct ASLR layouts)Full receipt and control ladder: docs/END-TO-END.md.
52x Memory Amplification
Optimized duplicate-header amplification: short X: a headers (6 bytes wire) exploit PHP's fixed 256-byte SMART_STR_START_SIZE per saved header (~314 B memory per 6 B wire). Measured by exact OOM-threshold bisection on 8.5.11; identical on 7.4.33 / 8.1.34 / 8.4.26 (51–52x):
| memory_limit | min OOM body | amplification |
|---|---|---|
| 8M | 153 KB | 54.8x |
| 32M | 636 KB | 52.8x |
| 128M | 2.57 MB | 52.3x |
| 256M | 5.14 MB | 52.2x |
All bodies fit inside a stock post_max_size=8M — the practical upload barrier is defeated on every unpatched version. Chunked framing additionally bypasses the Content-Length pre-check (8.85 MB chunked parsed and OOM'd where the same body with Content-Length was rejected). The 8.5.12 patch closes both vectors (unknown-header filtering + streaming post_max_size enforcement). The nested-array "bomb" shape does not reproduce as exponential (max 9.6x, 1.16 MiB — cannot OOM even 8M). Details: docs/RELEASE-RECONCILIATION-2026-10-21.md.
Patch Gaps on 8.5.12
Verified on a GPG-verified, token-identical 8.5.12 ASAN build — the two CVEs are fixed, but three separate paths to impact survive the patch:
var_destroyre-entry UAF (CWE-416) — boundary unchanged at $n=1015$, identical to 8.5.11 (fault atvar_unserializer.c:906:67); the patch'sZEND_HAS_OOMguards are inert on this path;BG(serialize_lock)is still not taken around__destructdispatch.- Serialize-handler mismatch — a
php_serialize-written|-key session decoded by thephphandler after an app-phasesession_start()instantiates the gadget onmod_phpphp_valueand FPM.user.inialike; the patch'sincomplete_classes_onlyguard is scoped to the pre-RINIT upload window only. - Upload-progress injection — attacker-controlled key stored verbatim in the session file.
FPM Stale BG(unserialize)
Lab-verified on php:8.5.11-fpm: a 24 MB session at memory_limit=32M OOMs inside php_var_unserialize and the worker survives — the exit-70 barrier applies only to startup OOM. gdb shows BG(unserialize).level=1 and the stale data pointer persisting into the next request's rfc1867 pre-RINIT window; an 8191-byte multipart field produces an 8216-byte _emalloc that overlaps the stale pointer and SIGSEGVs the worker in var_tmp_var during request startup. Measured impact: repeatable worker crash (DoS) plus a memory-corruption primitive; RCE beyond the crash is unproven. Prerequisites: low memory_limit, large attacker-influenced session, a session_start() endpoint, upload_progress=On, and pm=static max_children=1 for reliable same-worker pairing. FPM is therefore conditionally, not categorically, protected for this class.
WAF Signature Bypass
Content-Type: multipart/form-data, boundary=X (comma separator) is accepted by PHP — SAPI.c trims at ;, ,, and space. Every published php2shell WAF signature anchored on Content-Type: multipart/form-data; is bypassable. Defender guidance in docs/WAF-BYPASS.md.
What This Repository Does Not Prove
- A single-shot exploit against hardened production deployments (multi-worker prefork reliability,
display_errors=Off, and highmemory_limitall remain genuine barriers); - RCE from the FPM stale-state primitive (crash only, so far);
- Impact on PHP versions other than those explicitly tested (7.4.33 / 8.1.34 / 8.4.26 / 8.5.11 / 8.5.12 stock, 3 LiteSpeed builds).
Exact Environment
| Property | Required value |
|---|---|
| Host | Native Linux x86_64 |
| Target | PHP 8.5.11, Apache module (Chain B also on FPM for stale-state work) |
| Image | php@sha256:974e3a920309308e1690ed607e6cc3061d6906f90cc8d30fe519daa48bccf101 |
| Chain A allocator | USE_ZEND_ALLOC=0, USE_TRACKED_ALLOC=1 (intentionally non-default) |
| Chain B allocator | stock default Zend MM |
| PHP limits (chains) | memory_limit=16M, post_max_size=32M, upload_max_filesize=32M |
| Apache | prefork, exactly one worker |
| Debugger | Host GDB with Python support (calibration/receipts only) |
Quick Start (Chain A Runner)
Run only on an owned, isolated x86_64 Linux lab:
sudo ./scripts/preflight.sh
sudo ./scripts/reproduce.shThe runner validates the environment, generates the byte-identical 5,767,505-byte fixture, calibrates a safe type-0x7 slot, then runs fresh-worker negative → abort → marker with independent marker-byte, worker-transition, health, and trace checks, and writes a full SHA-256 manifest.
sudo ./scripts/lab-stop.sh # stop container, keep state
sudo ./scripts/lab-reset.sh # remove PoC container + network onlyProof Discipline
| Case | Expected effect | RCE oracle |
|---|---|---|
| Calibration | Type-0x7 stale slot observed; worker survives | none |
| Negative | Fake structures present; no dispatch; same worker | marker absent |
| Abort | Refcount decrement; exact abort@plt; SIGABRT | marker absent |
| Marker | Exact target dispatch; command output under /tmp | exact unique bytes |
An HTTP error, empty response, PHP fatal error, or worker crash is not accepted as command-execution evidence.
Safety Properties
- Dedicated Docker containers on internal networks; no host port published.
- Images pinned by digest. PoC builders accept no arbitrary command.
- Execution payloads are restricted
id/printf-class commands writing under/tmpinside the dedicated container only. - Negative and controlled-abort cases are mandatory in every session.
- Host GDB attaches only to the sole worker of the named lab container.
Defender Screening
tools/screen/graded_screen.py is the graded triage for the parser class behind both CVEs, packaged for defenders: banner/branch triage, a safe surface probe, and two controlled parser-heap ladders (unique part headers; small-body empty-filename fast fill).
python3 tools/screen/graded_screen.py https://host/ --dry-run
python3 tools/screen/graded_screen.py https://host/ --max-tier 3 --authorizedRepository Layout
app/ Minimal PHP/Apache target
tools/ php2shell_test.py (v1+v2) + domain_php2shell_scan.py
tools/screen/ graded_screen.py - defender triage (banner -> ladders)
tools/bombchain.py 1.41 MB amplified-producer variant of the stock chain
tools/php72/ PHP 7.2 chain: drive.py, chain72.py, fullchain.py
docs/SCREENING.md Screening tiers, controls, verdicts, false positives, pacing
ninebie/ Chain B PoC (send/env scripts, fire entry point, receipts)
docs/ Proof boundary, port write-ups, reconciliation log
evidence/reference/ Chain A two-generation proof summary + provenance
evidence/validation-*/ Chain A compact evidence, two fresh generations
scripts/ Chain A lab, calibration, body, runner, verifier
tests/ Deterministic fixture and safety tests
artifacts/, runs/ Generated locally; ignored by GitSOURCE-SHA256SUMS authenticates the tree: python3 scripts/make_manifest.py --verify.
Disclosure Statement
In an isolated PHP 8.5.11 Apache lab, two independent multipart-triggered chains reached attacker-controlled indirect calls and executed benign commands; a later end-to-end wrapper removed the debugger and local-access dependencies entirely using an in-band disclosure. On patched 8.5.12 both CVE chains are dead, but three separate paths to impact survive the patch. A 52x memory-amplification technique defeats
post_max_sizeon all unpatched versions since 7.4. These results demonstrate code-execution feasibility in the stated configurations; they are not evidence of a universal or unconfigured-deployment exploit.
📦 Full-Text Markdown Snapshot archived by Phying News · Original source: github.com
--- id: 10227 canonical_url: "https://phying.com/item/10227" title: "CVE-2026-109618/109619: Two Independent Pre-Auth RCE Chains on PHP 8.5.11 (Lab Reproduction & PoC)" type: "vulnerability" cve: "CVE-2026-109618, CVE-2026-109619" cvss: 9.2 severity: "critical" vendor: "PHP" product: "PHP" affected_versions: "<= 8.5.11 (fixed in 8.5.12, 8.4.27, 8.3.36, 8.2.35)" source_domain: "github.com" source_url: "https://github.com/dinosn/php-cve-2026-10961x-lab-poc" published_at: "2026-10-21T14:30:01Z" snapshot_at: "2026-10-26T05:52:00Z" archived_by: "Phying News (https://phying.com/item/10227)" lang: "en" --- # php2shell lab — PHP CVE-2026-109618 / CVE-2026-109619 *Author: dinosn · Published: October 21, 2026 · Source: [github.com/dinosn/php-cve-2026-10961x-lab-poc](https://github.com/dinosn/php-cve-2026-10961x-lab-poc)* Lab reproduction and vulnerability-research package for the PHP 8.5.11 php2shell class (**CVE-2026-109618, CVE-2026-109619**), October 2026. **Status: Proven — self-contained RCE** (four plain HTTP requests, no debugger, no local access), plus amplification, patch-gap, and FPM stale-state findings below. --- ## Results at a Glance | # | Finding | Classification | | :--- | :--- | :--- | | **1** | End-to-end self-contained RCE on PHP 8.5.11 — four plain HTTP requests, in-band address sourcing via `PS(id)`→cookie disclosure, both bases from one 512-byte PHPSESSID leak; 12/12 fires, `uid=33`, 8 ASLR generations | **Proven — self-contained RCE** | | **2** | 52x memory amplification via duplicate 6-byte multipart headers — defeats `post_max_size` (2.57 MB body OOMs a 128M limit) on PHP 7.4–8.5.11 | **Proven — lab, all four versions** | | **3** | Three exploit paths survive the 8.5.12 security patch (`var_destroy` re-entry UAF at $n=1015$, serialize-handler mismatch, upload-progress injection) | **Proven — ASAN on GPG-verified 8.5.12** | | **4** | FPM stale `BG(unserialize)`: worker survives a session-decode OOM, stale state is consumed by the next request's rfc1867 window, heap-shaped consumption SIGSEGVs the worker | **Proven — mechanism + DoS; RCE unproven** | | **5** | `Content-Type` separator bypass (`multipart/form-data, boundary=X`) — every published Content-Type-anchored WAF signature is bypassable | **Proven — SAPI.c parser behavior** | | **6** | Both CVE chains dead on patched 8.5.12 (token-identical configure, GPG-verified source) | **Proven — negative control** | | **7** | The public "cached-zval `session.upload_progress`" theory — refuted on 4 stock PHP versions + 3 LiteSpeed vendor builds, all cleanup/concurrency corners | **Refuted — exhaustive** | | **8** | The same stale-globals class on **PHP 7.2** (EOL): both bases leak from one in-band cookie, `zif_system` dispatch runs as `uid=33` — 6/6 under stock `post_max_size=8M`, incl. 5-worker pools | **Proven — lab, 7.2.34 + 7.2.21** | | **9** | The 129 MiB producer is replaceable by a **1.41 MB** header-dense part (470k × `x:\n`) with zero recalibration — 3/3 full chain, identical bases | **Proven — lab** | --- ## Chains Two independent memory-corruption chains reach attacker-controlled indirect calls on the stock `php:8.5.11-apache` image; the end-to-end wrapper combines Chain B's mechanism with a novel in-band disclosure for a fully remote fire. | Feature | Chain A — stale bucket | Chain B — dangling reference | | :--- | :--- | :--- | | **Mechanism** | OOM bailout leaves a HashTable bucket published but uninitialized; shutdown consumes it as a fake zval → fake array → `pDestructor` | Pre-RINIT OOM leaves `PS(http_session_vars)` holding a dangling `zend_reference`; a live 31-byte string reclaims it; forged reference → `zend_hash_destroy` `pDestructor` | | **Allocator** | tracked/system (`USE_ZEND_ALLOC=0`) | stock default Zend MM | | **Terminal** | libc `system()` (gdb-calibrated addresses) | `zif_system` @ libphp+0x5996e0 (no libc) | | **Debugger at fire time** | calibration only | **none — `FIRE_GDB=0`** | | **Reproducibility** | 2 randomized parent generations | repeated, incl. cross-run | ### End-to-end receipt (12/12 fires, 8 ASLR generations, no debugger, no local access) Leak-A arms a dangling `PS(id)` (1001st variable trips `max_input_vars` → `headers_sent=1`; the empty-cookie `reset_id` failure keeps `send_cookie=1`; a 24 MiB field OOMs pre-RINIT). Leak-B lands a 257-element array whose `zend_array` overlaps `PS(id)−0x10`: `ZSTR_LEN` reads `nTableSize`=512 and the URL-encoded cookie carries `pDestructor` (→ libphp base) plus nine `arData` heap pointers (majority vote → chunk base). The measured chunk−libphp delta is *not* constant across generations (−0x1a00000 / +0x3600000 / −0x1c00000), so the disclosure is genuinely load-bearing. The fire then runs with the leaked addresses. Prerequisites: `display_errors=On`, `post_max_size` above the OOM field, `memory_limit` below it, single-worker prefork. ```text A status='HTTP/1.1 200 OK' → OOM fatal, worker survives, PS(id) armed B Set-Cookie: PHPSESSID=<512-byte encoded disclosure> FIRE → MARKER=uid=33(www-data) gid=33(www-data) groups=33(www-data) FIRE_OK (×12 across 8 distinct ASLR layouts) ``` Full receipt and control ladder: `docs/END-TO-END.md`. --- ## 52x Memory Amplification Optimized duplicate-header amplification: short `X: a` headers (6 bytes wire) exploit PHP's fixed 256-byte `SMART_STR_START_SIZE` per saved header (~314 B memory per 6 B wire). Measured by exact OOM-threshold bisection on 8.5.11; identical on 7.4.33 / 8.1.34 / 8.4.26 (51–52x): | memory_limit | min OOM body | amplification | | :--- | :--- | :--- | | **8M** | 153 KB | 54.8x | | **32M** | 636 KB | 52.8x | | **128M** | 2.57 MB | 52.3x | | **256M** | 5.14 MB | 52.2x | All bodies fit inside a stock `post_max_size=8M` — **the practical upload barrier is defeated** on every unpatched version. Chunked framing additionally bypasses the Content-Length pre-check (8.85 MB chunked parsed and OOM'd where the same body with Content-Length was rejected). The 8.5.12 patch closes both vectors (unknown-header filtering + streaming `post_max_size` enforcement). The nested-array "bomb" shape does **not** reproduce as exponential (max 9.6x, 1.16 MiB — cannot OOM even 8M). Details: `docs/RELEASE-RECONCILIATION-2026-10-21.md`. --- ## Patch Gaps on 8.5.12 Verified on a GPG-verified, token-identical 8.5.12 ASAN build — the two CVEs are fixed, but three separate paths to impact survive the patch: 1. **`var_destroy` re-entry UAF (CWE-416)** — boundary unchanged at $n=1015$, identical to 8.5.11 (fault at `var_unserializer.c:906:67`); the patch's `ZEND_HAS_OOM` guards are inert on this path; `BG(serialize_lock)` is still not taken around `__destruct` dispatch. 2. **Serialize-handler mismatch** — a `php_serialize`-written `|`-key session decoded by the `php` handler after an app-phase `session_start()` instantiates the gadget on `mod_php` `php_value` and FPM `.user.ini` alike; the patch's `incomplete_classes_only` guard is scoped to the pre-RINIT upload window only. 3. **Upload-progress injection** — attacker-controlled key stored verbatim in the session file. --- ## FPM Stale `BG(unserialize)` Lab-verified on `php:8.5.11-fpm`: a 24 MB session at `memory_limit=32M` OOMs inside `php_var_unserialize` and **the worker survives** — the exit-70 barrier applies only to *startup* OOM. gdb shows `BG(unserialize).level=1` and the stale data pointer persisting into the *next* request's rfc1867 pre-RINIT window; an 8191-byte multipart field produces an 8216-byte `_emalloc` that overlaps the stale pointer and SIGSEGVs the worker in `var_tmp_var` during request startup. Measured impact: repeatable worker crash (DoS) plus a memory-corruption primitive; RCE beyond the crash is unproven. Prerequisites: low `memory_limit`, large attacker-influenced session, a `session_start()` endpoint, `upload_progress=On`, and `pm=static max_children=1` for reliable same-worker pairing. FPM is therefore *conditionally*, not categorically, protected for this class. --- ## WAF Signature Bypass `Content-Type: multipart/form-data, boundary=X` (comma separator) is accepted by PHP — `SAPI.c` trims at `;`, `,`, and space. Every published php2shell WAF signature anchored on `Content-Type: multipart/form-data;` is bypassable. Defender guidance in `docs/WAF-BYPASS.md`. --- ## What This Repository Does Not Prove - A single-shot exploit against hardened production deployments (multi-worker prefork reliability, `display_errors=Off`, and high `memory_limit` all remain genuine barriers); - RCE from the FPM stale-state primitive (crash only, so far); - Impact on PHP versions other than those explicitly tested (7.4.33 / 8.1.34 / 8.4.26 / 8.5.11 / 8.5.12 stock, 3 LiteSpeed builds). --- ## Exact Environment | Property | Required value | | :--- | :--- | | **Host** | Native Linux x86_64 | | **Target** | PHP 8.5.11, Apache module (Chain B also on FPM for stale-state work) | | **Image** | `php@sha256:974e3a920309308e1690ed607e6cc3061d6906f90cc8d30fe519daa48bccf101` | | **Chain A allocator** | `USE_ZEND_ALLOC=0`, `USE_TRACKED_ALLOC=1` (intentionally non-default) | | **Chain B allocator** | stock default Zend MM | | **PHP limits (chains)** | `memory_limit=16M`, `post_max_size=32M`, `upload_max_filesize=32M` | | **Apache** | prefork, exactly one worker | | **Debugger** | Host GDB with Python support (calibration/receipts only) | --- ## Quick Start (Chain A Runner) Run only on an owned, isolated x86_64 Linux lab: ```bash sudo ./scripts/preflight.sh sudo ./scripts/reproduce.sh ``` The runner validates the environment, generates the byte-identical 5,767,505-byte fixture, calibrates a safe type-0x7 slot, then runs fresh-worker negative → abort → marker with independent marker-byte, worker-transition, health, and trace checks, and writes a full SHA-256 manifest. ```bash sudo ./scripts/lab-stop.sh # stop container, keep state sudo ./scripts/lab-reset.sh # remove PoC container + network only ``` --- ## Proof Discipline | Case | Expected effect | RCE oracle | | :--- | :--- | :--- | | **Calibration** | Type-0x7 stale slot observed; worker survives | none | | **Negative** | Fake structures present; no dispatch; same worker | marker absent | | **Abort** | Refcount decrement; exact `abort@plt`; SIGABRT | marker absent | | **Marker** | Exact target dispatch; command output under `/tmp` | exact unique bytes | An HTTP error, empty response, PHP fatal error, or worker crash is **not** accepted as command-execution evidence. --- ## Safety Properties - Dedicated Docker containers on internal networks; no host port published. - Images pinned by digest. PoC builders accept no arbitrary command. - Execution payloads are restricted `id`/`printf`-class commands writing under `/tmp` inside the dedicated container only. - Negative and controlled-abort cases are mandatory in every session. - Host GDB attaches only to the sole worker of the named lab container. --- ## Defender Screening `tools/screen/graded_screen.py` is the graded triage for the parser class behind both CVEs, packaged for defenders: banner/branch triage, a safe surface probe, and two controlled parser-heap ladders (unique part headers; small-body empty-filename fast fill). ```bash python3 tools/screen/graded_screen.py https://host/ --dry-run python3 tools/screen/graded_screen.py https://host/ --max-tier 3 --authorized ``` --- ## Repository Layout ```text app/ Minimal PHP/Apache target tools/ php2shell_test.py (v1+v2) + domain_php2shell_scan.py tools/screen/ graded_screen.py - defender triage (banner -> ladders) tools/bombchain.py 1.41 MB amplified-producer variant of the stock chain tools/php72/ PHP 7.2 chain: drive.py, chain72.py, fullchain.py docs/SCREENING.md Screening tiers, controls, verdicts, false positives, pacing ninebie/ Chain B PoC (send/env scripts, fire entry point, receipts) docs/ Proof boundary, port write-ups, reconciliation log evidence/reference/ Chain A two-generation proof summary + provenance evidence/validation-*/ Chain A compact evidence, two fresh generations scripts/ Chain A lab, calibration, body, runner, verifier tests/ Deterministic fixture and safety tests artifacts/, runs/ Generated locally; ignored by Git ``` `SOURCE-SHA256SUMS` authenticates the tree: `python3 scripts/make_manifest.py --verify`. --- ## Disclosure Statement > In an isolated PHP 8.5.11 Apache lab, two independent multipart-triggered chains reached attacker-controlled indirect calls and executed benign commands; a later end-to-end wrapper removed the debugger and local-access dependencies entirely using an in-band disclosure. On patched 8.5.12 both CVE chains are dead, but three separate paths to impact survive the patch. A 52x memory-amplification technique defeats `post_max_size` on all unpatched versions since 7.4. These results demonstrate code-execution feasibility in the stated configurations; they are not evidence of a universal or unconfigured-deployment exploit. --- > 📦 **Full-Text Markdown Snapshot** archived by [Phying News](https://phying.com/item/10227) · Original source: [github.com](https://github.com/dinosn/php-cve-2026-10961x-lab-poc)