Skip to content
Exploit for Improperly Controlled Sequential Memory Allocation in Php

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 22, 2026

**Pre-auth use-after-free in PHP core (request-body parser) → remote code execution.**

Root-cause analysis, affected-version detection, and a demonstration transcript.

> The vulnerability is in **PHP itself** (`main/rfc1867.c`, the session

> `upload_progress` path and the `BG(unserialize)` / `PS(id)` process-globals),

> not in any application. The HTTP-only ASLR bypass works against a bare PHP

> La vulnérabilité est dans **PHP lui-même**, pas dans une application. Le

> contournement d'ASLR (100 % HTTP) fonctionne contre une page PHP nue —

> **aucun CMS ni code applicatif requis**.

> This repository is **defensive and educational**: root-cause write-up and a

> passive **Nuclei** detection template. The weaponized exploit chain is **not

> published** (responsible disclosure). Update to a fixed PHP — see *Remediation*.

> Ce dépôt est **défensif et pédagogique** : analyse de cause racine et un

> template **Nuclei** de détection passive. La chaîne d'exploitation n'est **pas

> publiée** (divulgation responsable). Mettez PHP à jour — voir *Remédiation*.

| **CVSS 4.0** | 9.2 — `AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H` |

ASLR-bypass technique credit: 9bie (github.com/9bie/php2shell-bypass-aslr-exp)

00:00.08 OK DETECTED PHP/8.4.26 · Apache/2.4.69 (Debian)

00:00.14 OK HTTP vulnerable range (PHP forged-length PS(id) -> php_url_encode over-read

00:03.91 OK CAPTURE live heap url-encoded into Set-Cookie: PHPSESSID=

00:03.95 INFO CHUNK chunk base = 0x7f3d63200000 (2MiB-aligned mode)

00:03.98 INFO LIBPHP libphp base = 0x7f3d65264000 (pDestructor - ZVAL_PTR_DTOR)

00:49.84 INFO OVERRIDE fake reference destructor -> zif_system(command)

=====================================================================

00:58.91 OK RESULT PRE-AUTH RCE CONFIRMED

00:58.91 OK OUTPUT uid=33(www-data) gid=33(www-data) groups=33(www-data)

=====================================================================

Full transcript: [`transcript.txt`](transcript.txt).

When an allocation crosses `memory_limit`, the **unpatched** Zend memory manager

calls `zend_mm_safe_error()` (`Zend/zend_alloc.c`), which ends in

`zend_bailout()` — a **`longjmp()` out of the allocator itself**. The HTTP body

parser (`main/rfc1867.c`, `main/php_variables.c`), whose allocation sizes are

attacker-controlled (unbounded multipart part headers; chunked transfer bypassing

the `post_max_size` pre-check), is abandoned **mid-mutation of a request

The exploitable defect is an **ordering bug** in `php_request_startup()`

1873: php_hash_environment(); // POST parse -> upload-progress callback -> session decode (publishes BG(unserialize))

1874: zend_activate_modules(); // basic module RINIT -> memset(&BG(unserialize), 0, …), AFTER

The upload-progress session decode runs **before** the RINIT that clears

`BG(unserialize)`. If the OOM bails during that decode, the destroy is skipped

and the process-global `BG(unserialize)` is left **dangling** into a torn-down

request heap. The * * request on the same worker reuses it (its own RINIT

clear has not run yet — it is at line 1874, after the parse). A reclaiming spray

then reforges the dangling process-globals (`BG(unserialize)`, `PS(id)`,

`PS(http_session_vars)`), which is turned into a controlled destructor call →

**ASLR defeat — HTTP only, no CMS, no debugger.** The chain never needs local

access or an instantiated application object. A reclaimed `PS(id)` is reforged

with an attacker-chosen length; the session layer then `php_url_encode()`s it

into a `Set-Cookie: PHPSESSID=` header, **over-reading live heap** straight into

the HTTP response. The disclosed qwords give the 2 MiB-aligned heap chunk base

(by mode), and a leaked destructor pointer gives the libphp base

(`libphp = pDestructor − ZVAL_PTR_DTOR_OFFSET`); `system` is `zif_system` inside

libphp, so no libc leak is required. This remote Set-Cookie disclosure is the

technique of **9bie** ( it

works against a bare ` callback upload-progress -> décode session (publie BG(unserialize))

1874: zend_activate_modules(); // RINIT module « basic » -> memset(&BG(unserialize), 0, …), APRÈS

Le décode upload-progress tourne **avant** le RINIT qui remet `BG(unserialize)`

à zéro. Si l'OOM frappe pendant ce décode, le `destroy` est sauté et le global

process `BG(unserialize)` reste **dangling**. La requête suivante sur le même

worker le réutilise ; un spray de reclaim reforge les globals process dangling

(`BG(unserialize)`, `PS(id)`, `PS(http_session_vars)`), menés à un appel de

**Défaite de l'ASLR — 100 % HTTP, sans CMS ni débogueur.** La chaîne n'a besoin

ni d'accès local ni d'un objet applicatif instancié. Un `PS(id)` récupéré est

reforgé avec une longueur choisie par l'attaquant ; la couche session le

`php_url_encode()` ensuite dans un en-tête `Set-Cookie: PHPSESSID=`, **lisant

au-delà** du tampon directement dans la réponse HTTP. Les qwords divulgués

donnent la base du chunk de tas (aligné 2 Mio, par mode), et un pointeur de

destructeur divulgué donne la base de libphp

(`libphp = pDestructor − ZVAL_PTR_DTOR_OFFSET`) ; `system` est `zif_system` dans

libphp, donc aucune fuite de libc n'est requise. Cette divulgation Set-Cookie

( ; elle fonctionne contre une

page `<?php` nue avec `display_errors=On`.

**Le correctif** (8.4.27 / 8.5.12) ajoute `php_basic_early_rinit()`, **avant** le

parse (`main.c:1869`). Analyse détaillée : [`ROOTCAUSE.fr.md`](ROOTCAUSE.fr.md).

Passive, zero-impact version-range detection (Nuclei):

nuclei -t nuclei/cve-2026-109618.yaml -u

It parses the PHP banner and flags the affected ranges. A banner-suppressed host

(`expose_php=Off`) will not match — use a behavioural check in that case.

Template: [`nuclei/cve-2026-109618.yaml`](nuclei/cve-2026-109618.yaml).

- **Update to PHP ≥ 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.** The fix closes the

- Temporary mitigation: disable `session.upload_progress.enabled`. Lowering

`memory_limit` does **not** help (the attacker sizes the OOM).

- Wiz — *PHP2Shell: PHP RCE* —

- Huntress — *PHP2Shell: Leaked Critical PHP Vulnerabilities*

- PHP security release 8.4.27 / 8.5.12 (fix commit `506e87fa7`)

- GHSA-w8hw-w9fx-j79w · GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)

- 9bie — *php2shell HTTP-only ASLR bypass (Set-Cookie heap disclosure)* —

Analysis and detection template released for defensive use. See [`LICENSE`](LICENSE).

Extracted Entities