Back Sploitus Exploit for Improperly Controlled Sequential Memory Allocation in Php
PoC and RCE derived from the PHP official patch [`3584a98bda`]( Includes a differential environment with both 8.2.34 and 8.2.35 versions of the container. The PoC and RCE are based on the pre-RINIT session decoding in PHP’s upload-progress handler.
- `exp/poc_pre_rinit.py` — Demonstrates that the upload-progress handler decodes the session before RINIT and executes object destructors, without executing commands.
- `exp/exp_rce.py` — Executes arbitrary commands at the same path, with results output via HTTP retrieval.
- `exp/poc_oom.py` — Triggers an OOM crash (CVE-2026-109618); only triggers, not actually causing a RCE.
- `lab/` — Versions `php:8.2.34-fpm` and `php:8.2.35-fpm`, each with nginx.
This chain was successfully tested on 8.2.34 but blocked on 8.2.35 due to the `incomplete_classes_only` rule. It does not involve zero-dependency exploitation: the target must have a class with dangerous magic methods before RINIT, and `lab` uses `opcache.preload` to simulate this.
Same code, same configuration, just different images:
| 8.1 | 8.1.34 (EOL, no official fix) | — |
The output of `/verify.sh` for the 8.2 branch:
Full output and call stack details can be found in [`docs/RESULTS.md`](docs/RESULTS.md).
The patch ensures that the upload-progress handler decodes the session before RINIT, and enforces `incomplete_classes_only` for this stage. Before the patch, this decoding would instantiate objects from the session record before script compilation. The process involves two steps, both of which go through HTTP:
1. `POST /app/upload.php`. The `user.ini` file sets `session.serialize_handler=php_serialize`; the upload-progress handler writes the multipart filename directly into the session file, along with the line `x|O:6:"PrePwn":...`.
2. `POST /api/pre.php`. The default `php` handler is used; there is no `session_start()` in the script. At the start of the request, `php_session_rfc1867_update()` still decodes: The `php` decoder cuts after the first `|`, passing the rest to `un serialize()`.
3. On 8.2.34, objects are created according to real classes; `__destruct()` calls `shell_exec($cmd)`, and the output is written to a random file in the webroot directory.
4. On 8.2.35, the same data is decompiled as `__PHP_Incomplete_Class`; there are no destructors or files. The payload is explicitly generated by `exp/lib2shell.py::php_object()`, with a length prefix calculated in bytes:
x|O:6:"PrePwn":2:{s:3:"cmd";s:12:"id; hostname";s:3:"out";s:33:"/var/www/html/.pwn_xxxxxxxx.txt";
Details the patch and code matches can be found in [`docs/DERIVATION.md`](docs/DERIVATION.md).
Docker Compose and Python 3.8+ (standard library only) are required.
./verify.sh # Create containers, run PoC/RCE, print differential matrix
docker compose -f lab/docker-compose.yml down -v
python3 exp/exp_rce.py --target --command 'id; uname -a'
python3 exp/poc_pre_rinit.py --target
python3 exp/poc_oom.py --target --size-mb 24
Ports: `18080` = 8.2.34 (vulnerable), `18081` = 8.2.35 (fixed).
Versions below 8.2.35, 8.3.36, 8.4.27, and 8.5.12 are vulnerable. Versions starting from 5.4.0 with `session.upload_progress` have the same pre-RINIT decoding; versions like 5.x, 7.x, 8.0, and 8.1 have been EOL, with no official fixes.
- `session.upload_progress.enabled=On` (default).
- `session.upload_progress.cleanup=Off` (default: On).
When `On`, entries for progress updates will be deleted after the request ends; this project does not override this setting, requiring additional mitigation measures.
- Both directories session storage, and the `serialize_handler` is different: `php_serialize` writes, while `php` reads.
This is a configuration error, not the default value. – RINIT already has available classes. The pre-RINIT path only recognizes `opcache.preload` or resident extension classes; the regular `session_start()` path (see below) can accept any automatically loaded gadgets. No authentication, user interaction, or application-layer vulnerabilities are required; both FPM and mod_php are valid. ## Impact Scope
It’s rare for all four conditions to occur simultaneously, so this vulnerability cannot be classified as “affecting 70% of websites.” It’s better to evaluate it as an “configuration error amplifier.” Once exploited, the impact includes arbitrary command execution under Web user privileges: reading `.env` and database passwords, writing webshells, connecting to the internal network, and lateral movement. In multi-tenant environments where session directories are shared, cross-tenancy issues may arise. Classes like `__wakeup`/`__unserialize` can also cause file writing and SSRF-like side effects. The same handler mismatch can also lead to other issues: if the `php` directory contains endpoints that call `session_start()`, triggering normal decoding via GET + Cookie. This also works on version 8.2.35 (tested). `incomplete_classes_only` only affects `PS(in_rfc1867_update)`; the patch that disables it is related to shortcuts before RINIT, not the configuration error itself. OOM triggers do not require any of these conditions: a single, approximately 24MB urlencoded request can cause a FPM worker to exit (`exit 70`), and this happens on both 8.2.34 and 8.2.35. This CVE has the most scalable impact, but it’s just a denial-of-service attack. ## Combined Exploitation
| Compatible Vulnerabilities | Conditions for Elimination/Evasion | Results |
| Arbitrary file writing, path traversal, backup recovery | Handler mismatch, uploadProgress, cleanup are not required | Simply writing session files into `save_path`; still results in RCE on 8.2.35 (tested) |
| SSRF attacks against Redis/Memcached session storage | Same as above, without disk storage | Using `SET` session keys is sufficient |
| Arbitrary file uploads with controllable filenames | Creating handler mismatches | Uploading `.user.ini` and setting `session.serialize_handler` (only for CGI/FPM) |
| Applications with `session_start()` endpoints | Version restrictions | Regular decoding paths; patches do not affect this (tested on 8.2.35) |
| LFI | Handler mismatch | Including session files in `include` executes PHP code (tested on 8.2.35) |
| Fixed sessions/SIDs | Requires the victim’s SID | Can write sessions → account takeover |
| Multi-tenant environments sharing `save_path` | None | Writing sessions to other vhosts, across tenants |
| OOM triggers | None | Disabling one worker per request directly causes DoS |
The most valuable one is the first one: writing `role|s:5:"admin"` into `save_path/sess_` can forge sessions; writing serialized objects into them and then accessing `session_start()` endpoints results in RCE. Both were successfully tested on 8.2.35. ## Project Structure
│ ├── lib2shell.py HTTP / multipart / serialize primitives
│ ├── poc_pre_rinit.py Destruction of destructors (no command execution)
│ ├── exp_rce.py Arbitrary command execution
│ └── poc_oom.py OOM trigger (only triggered)
│ ├── docker-compose.yml 8.2.34 + 8.2.35, each with nginx
│ ├── php.ini Shared configuration (cleanup=Off, opcache.preload)
│ ├── preload.php PrePwn / Probe / WakeProbe
│ ├── app/upload.php PHP serialization write points
│ └── api/ PHP read points (pre.php has no session_start, start.php has one)
│ ├── DERIVATION.md Patch hunk → primitives → code
│ └── RESULTS.md Test outputs and version matrix
The `exp_rce.py` command execution relies on the `PrePwn` gadget; the actual target should use framework gadgets visible before RINIT. This isn’t a limitation of the vulnerability itself, but rather a common prerequisite for deserialization exploitation. The project doesn’t implement zero-dependency OOM exploitation for CVE-2026-109618: that requires request heap layout, chunk reuse after release, and object storage for forgery. The patch only indicates which pointers are dirty, not the layout. `poc_oom.py` can only prove the trigger point; it cannot distinguish between “can be exploited” and “already fixed”. ## References
- Patch: `[php/php-src@3584a98bda]` (
- GHSA-w8hw-w9fx-j79w ( / CVE-2026-109618
- GHSA-6wmf-4rv5-p3rp ( / CVE-2026-109619
- Patches include tests: `ext/session/tests/rfc1867_no_user_code.phpt`, `Zend/tests/oom_request_parsing.phpt`
This is only for private environments or tests with written authorization. Users must assume full responsibility for compliance.
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.
