# PHP: OOM-fatal dangling state yields pre-auth RCE

OOM fatals during PHP request startup/decoding leave dangling HashTable and session/unserializer state that mod_php and PHP-FPM workers reuse, yielding pre-auth RCE.

> Note: First to report these bugs: the PHP Foundation says Calif reported them on August 11, about a week before Wiz found them (August 19, by Wiz's own timeline). PHP credits Calif on both CVE-2026-109618 and CVE-2026-109619. Wiz later published [its own write-up](https://bugbountylibrary.com/r/blog:wiz-io-blog-php2shell-php-rce).

- Payout: Research
- Severity: critical
- Program: PHP
- Write-up by: LCFR (Calif)
- Disclosed: 2026-10-21
- Write-up quality: 10/10
- Original: https://calif.io/php2shell/
- Library page: https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell
- Tags: domain:web impact:data-leak impact:rce kind:research platform:research sev:critical tech:php technique:chained technique:source-review vuln:dos vuln:memory-corruption vuln:rce

**Target:** PHP interpreter (Zend HashTable, session upload-progress, unserializer) on mod_php and PHP-FPM
**Root cause:** Fallible allocations (key init, session init, unserializer bookkeeping) publish or increment state before completing; a memory_limit fatal skips cleanup, leaving counts/pointers that teardown or the next request trusts.
**Steps:**
1. Bypass post_max_size via chunked HTTP/1.1 or lengthless HTTP/2 multipart, or input-decompression filter mismatch.
2. Send many `filename=""` upload parts with nested names `files[a][b][c]...` to expand into six $_FILES entries each, exhausting 128MiB heap under 8MiB body.
3. Trigger fatal inside `_zend_hash_str_add_or_update_i` after `nNumUsed++` but before key init; mod_php shutdown consumes stale bucket.
4. For session bug: request A with upload-progress FILE_START initializes session pre-module-activation, then fatal skips END/RSHUTDOWN; request B without file part reaches END cleanup reading freed PS(id)/PS(http_session_vars).
5. For FPM: cause post-startup fatal during unserialize/session decode leaving BG(unserialize).data dangling; next multipart request reclaims heap and reuses stale dtor list with negative used_slots to place controlled zval.
**How found:** Researcher with PHP vuln history hypothesized siblings of Stefan Esser's 2004 memory_limit bug; used LLM to enumerate OOM producers and interruptible interpreter points, then source-reviewed ZendMM/fatal/shutdown ordering and generalized the pattern into a reusable skill.
**Exploitation:** Attacker sends crafted multipart requests to any PHP script; OOM fatal leaves dangling state in surviving Apache/FPM worker; subsequent request reclaims freed heap and consumes stale pointers, giving memory leak and controlled zval write leading to RCE pre-auth.
**Escalation:** Generalized 'publish state → fallible work → fatal skips commit' pattern applied across HashTable, session upload-progress, and unserializer to extend from mod_php-only to PHP-FPM.
**Insight:** Look for code that increments a visible count or publishes a pointer before a fallible allocation; if a fatal can occur between publish and commit, teardown or the next request may trust the stale state — especially where startup failure still runs request shutdown (mod_php) or where post-startup fatals keep the worker alive (FPM).
