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

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 26, 2026

**CVE-2026-109618** is a critical heap use-after-free in the PHP session upload progress handler (`php_session_rfc1867_callback` in `ext/session/session.c`). The flaw allows unauthenticated remote code execution against any Apache mod_php installation with default settings.

The root cause: when a multipart POST triggers an out-of-memory condition during session upload progress tracking, PHP's `longjmp()` error recovery skips normal cleanup. This leaves `PS(id)` -- a module-global pointer to the session ID string -- pointing at freed heap memory. A subsequent request can reclaim that memory, leak heap and library addresses through the `Set-Cookie` header, and ultimately hijack the `pDestructor` function pointer in a forged `HashTable` to call `zif_system()` with an attacker-controlled command string.

**No application vulnerability required.** Any PHP endpoint on an Apache mod_php prefork installation is sufficient. The attacker needs only the ability to send HTTP POST requests.

14 years of vulnerable code -- `session.upload_progress` was introduced in PHP 5.4.0 (2012) and has been enabled by default ever since.

This exploit is based on [3em0/php2shell]( Rewritten with auto-detection of PHP version and symbol offsets, merged profiles, and a cleaner CLI.

The OOM occurs when the attacker sends a multipart POST body exceeding `memory_limit` (typically 8M-128M). PHP's `zend_mm_alloc` calls `zend_mm_gc()`, which fails, then invokes `_zend_bailout()` via `longjmp()`. This unwinds past `php_session_rfc1867_callback` without running the session cleanup that would zero out the module-global `PS(id)` pointer.

The stale `PS(id)` now points at a freed `zend_string`. On the request to the same Apache worker, the session handler reads `PS(id)` to emit a `Set-Cookie` header, calling `php_url_encode()` on a buffer that has been freed and possibly reallocated with attacker-controlled data.

The exploit runs four stages over plain HTTP against a single Apache worker:

An initial OOM request leaves `PS(id)` and `PS(send_cookie)` in a corrupted state. A follow-up session-init request triggers the worker to walk the corrupted heap during pre-RINIT cleanup, causing a segfault. Apache forks a fresh child with clean heap but no stale-pointer protection.

**Stage 1 -- Heap Leak (Zend MM Chunk Base)**

The same OOM pattern leaves `PS(id)` pointing at a freed 48-byte `zend_string`. A spray request sends ~841 form fields of exactly 48 bytes each. One of them reclaims the freed slot with a forged `zend_string` whose `len` field is set to `0x1000`. When PHP calls `php_url_encode(PS(id))`, it over-reads 4096 bytes of heap data into the `Set-Cookie` header.

The leaked bytes contain Zend MM internal pointers. The exploit extracts the 2MB-aligned chunk base by counting the most common page-aligned address in the decoded cookie.

A similar OOM + spray pattern targets a different allocator size class (56-byte `HashTable`). The freed `HashTable` contains `pDestructor`, which points to `zval_ptr_dtor` inside `libphp.so`. The exploit reads the pointer from the leaked cookie at offset +8, verifies the low 12 bits match the known symbol offset, and subtracts to recover the library base address.

With the chunk base and libphp base known, the exploit computes:

- `htdata` = chunk_base + known offset (where the live 167-byte `zend_string` carrier sits)

- `zif` = libphp_base + `zif_system` offset

It constructs a 167-byte payload containing:

- A fake `HashTable` with `pDestructor` set to `zif_system`

- A fake `execute_data` frame with `num_args=1`

- A fake `zend_string` containing the shell command

A spray request writes this payload into the 167-byte carrier slot. A 15-byte `zend_string` is placed at `PS(http_session_vars)` to form a fake `zend_reference` whose `val` points to the fake `HashTable`. When the session handler calls `zval_ptr_dtor` on the reference during cleanup, the destruction chain reaches `zif_system(command_string)`.

chunk_base + 0x690D8 --> | 167-byte carrier string |

PS(http_session_vars) --> fake zend_reference

zval_ptr_dtor(ref) -> array_dtor -> zif_system("id>/out/p1")

| SAPI | Apache mod_php (prefork MPM) | Common |

| `display_errors` | `On` | `On` in development |

| `memory_limit` | Low enough to OOM on 12MB POST | `128M` default (needs `8M`) |

| `session.upload_progress.enabled` | `On` | **Yes (default)** |

| `session.use_strict_mode` | `0` | **Yes (default)** |

| `max_input_vars` | `1000` | **Yes (default)** |

The exploit does **not** work against PHP-FPM. The stale module-global trick depends on Apache prefork reusing the same process memory across requests. FPM worker pools do not module globals between requests in the same way.

| PHP 8.5.x | This exploit has been verified on `php:8.4.26-apache` and `php:8.3.35-apache` (official Docker images, Debian 13 trixie). Versions below 8.3 may be exploitable with different heap grooming constants, but are not included in the shipped profiles.

| **CVSS v3.1 Base Score** | **9.2 (Critical)** |

| **Vector String** | `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H` |

Attack Complexity is rated High because the exploit requires specific server configuration (mod_php + low memory_limit + display_errors) and version-specific heap grooming offsets.

| ATT&CK ID | Tactic | Technique | Relevance |

|-----------|--------|-----------|-----------|

| [T1190]( | Initial Access | Exploit Public-Facing Application | Direct exploitation of PHP over HTTP |

| [T1059]( | Execution | Command and Scripting Interpreter | Shell command execution via `system()` |

| [T1203]( | Execution | Exploitation for Client Execution | Memory corruption leading to code execution |

**Requirements:** Python 3 (standard library only, zero external dependencies).

git clone

No `pip install` needed. The exploit uses only the Python standard library.

poc_cve_2026_109618.py # Full 3-stage exploit with auto-detection

snort.rules # Snort/Suricata detection rules

sigma.yml # Sigma rule for SIEM correlation

The exploit is a single file because the heap UAF chain cannot be meaningfully simplified. The three stages (heap leak, libphp leak, fake HashTable RCE) are the minimum required for exploitation. Auto-detection of PHP version and symbol offsets is built in.

# Start the vulnerable target (php:8.4.26-apache with memory_limit=8M)

docker run -d --name target -p 8088:80 php:8.4.26-apache

# Run the exploit (auto-detects version and offsets from the container)

python3 poc_cve_2026_109618.py \

# With known offsets (from the same PHP build)

python3 poc_cve_2026_109618.py \

--zif-offset 0x465370 --dtor-offset 0x65eb60 \

python3 poc_cve_2026_109618.py --profile official83

python3 poc_cve_2026_109618.py \

| `url` | (required) | Target PHP endpoint URL |

| `-c / --cmd` | `id>/out/p1` | Command to execute (max 14 bytes) |

| `--proof-file` | - | Local file to read for RCE confirmation |

| `--fetch` | - | GET this path after exploitation |

| `--profile` | auto-detect | `official84` or `official83` |

| `--zif-offset` | auto/profile | `zif_system` offset in libphp.so |

| `--dtor-offset` | auto/profile | `zval_ptr_dtor` offset in libphp.so |

| `--htdata-offset` | profile | Chunk-relative offset of 167-byte carrier |

| `--docker-container` | `php2shell-target` | Container name for auto-detection |

| `--no-auto` | off | Disable auto-detection |

| `--huge-mb` | `12` | OOM trigger field size in MB |

| `--timeout` | `30` | Per-request timeout in seconds |

| `--retries` | `3` | Attempts per leak stage |

| `-q / --quiet` | off | Suppress progress output |

The fake `zend_string` payload carrier is 167 bytes. After the struct headers, this leaves 14 bytes for the command. For longer commands, write a script first:

# Write a longer command to a file, then execute it

python3 poc_cve_2026_109618.py \

2. **Abnormally long Set-Cookie** headers (PHPSESSID >500 chars, URL-encoded heap data)

3. **Repeated Apache child crashes** (segfault signals in error.log)

4. **Rapid connection cycling** to the same endpoint (4-8 requests per stage, 3 stages)

- `snort.rules` -- 3 Snort/Suricata rules (OOM trigger, heap spray pattern, cookie leak)

- `sigma.yml` -- Sigma rule for SIEM correlation

- [NVD - CVE-2026-109618](

- [NVD - CVE-2026-109619](

- [PHP Security Advisory GHSA-w8hw-w9fx-j79w](

- [3em0/php2shell - Original exploit](

- [ikow/php2shell-poc - Multi-chain toolkit](

- [PHP session.upload_progress documentation](

- [MITRE ATT&CK - T1190: Exploit Public-Facing Application](

This tool is provided for **authorized security auditing, academic research, and educational purposes only**. Usage against systems without explicit written permission from the system owner is illegal under applicable computer fraud and abuse laws (including but not limited to the CFAA, Computer Misuse Act, and equivalent legislation). The author accepts no liability for unauthorized or malicious use.