Skip to content

Php Up 2026

jackalsyn.com • October 22, 2026

CORRECTED FINDINGS (2026-10-21): Cached-pointer chain DEAD at the addref (session.c:3213 Z_TRY_ADDREF keeps array alive - no free, no RCE, CVSS 0.0). The real bug is a serialize-state cross-request UAF (ASAN-confirmed at var.c:756, n+=1 on arrays/references, prefork-only, NOT code execution, CVSS 5.3 Medium). Full proof + receipts preserved; lab details unredacted.

PHP's session.upload_progress handler ( php_session_rfc1867_callback in ext/session/session.c) runs inside php_hash_environment , BEFORE zend_activate_modules and therefore BEFORE PHP_RINIT_FUNCTION(basic) zeroes BG(serialize) / BG(unserialize) (basic_functions.c:393-395).

Both globals cache { level, data } for the var layer. A memory_limit bailout inside the pre-RINIT update() decode/encode leaves the context allocated but level=1 with a dangling data pointer. request on the same process: php_var_serialize_init / php_var_unserialize_init see level != 0 and reuse the freed block as the live context → UAF read/write through:

Bug S - Cached-pointer path (CVE-2026-109618 class label, DEAD at addref, CVSS 0.0) : php_add_var_hash does data->n += 1 at freed+0x38; fake HashTable pDestructor fires from zend_hash_destroy → zval_ptr_dtor → forged object → zend_call_function .

Bug U - Serialize-state UAF (real bug, NOT the cached-pointer path; memory-safety only, CVSS 5.3) - unserialize context (8208B php_unserialize_data ): tmp_var() reads last_dtor (+16) / used_slots → var_push_dtor writes arbitrary zval; var_access() resolves R:n markers → arbitrary zval* injected → update's zend_hash_update replacement → zval_ptr_dtor(attacker_address) .

session.upload_progress.enabled=0 closes both (session.c:3239).

Validated on Apache 2.4.68 prefork + mod_php 8.2.34 (Debian 12), memory_limit=8M:

Bug S: CONFIRMED - context reuse + forged pDestructor / nNextFreeElement reflected in $_POST['t'] ; arbitrary zval_ptr_dtor primitive working.

Bug U: ARM/P1 confirmed (pre-RINIT decode OOM, orphaned BG(unserialize) , session file destroyed by cancel_decode ); FIRE blocked by session globals corruption ( PS(save_path) dangling pointer to request boundary string) preventing clean decode of attacker session data. Requires per-build heap grooming (glibc 2.39 vs 2.36) for reliable reclaim - see Validation .

Bundle Directories (Updated)

Most recent chain files - lab details preserved (no redaction). Browse locally.

bundle A php2shell2/ - self-contained reproducer (php2shell_repro.sh, ROOT_CAUSE.md, WEAPON_NOTES.md, poc_serialize.py, poc_unserialize.py, fast_fire.py, lab scripts)

bundle B php-up-2026-109618-109619/ - framework, PoCs (poc0_109618.py / poc1_109619.py), validation receipts (VALIDATION.md, RATING.md, LIVE_POSTURE.md, DELTAS_LSphp.md, REPORT_VALIDATION.md), three .zval.debug files

corrected Cached-pointer chain DEAD at addref (Z_TRY_ADDREF at session.c:3213); serialize-state cross-request UAF is real (n+=1 @ var.c:756, prefork-only, NOT RCE, CVSS 5.3)

proof ASAN hit @ var.c:756 on built-in server; kill receipts (SIGSEGV, 2x reproduced); per-build delta table (bookworm 0xc930 / noble 0xcef0 / jammy 0xd060); video intel preserved (Ubuntu 26.04 / PHP 8.5.4 / glibc 2.39 / 8-attempt envelope)

How to Validate, Extract & Deliver the Chain

Mitigation (pre-patch)

What to do before the official advisory ships. You can do all of this tonight.

1. Inventory your PHP

Know every host and version (php -v). Anything on PHP 7.x or older won't get a fix, so plan to upgrade.

2. Apply the stopgap (best-intel, low-risk, reversible)

In php.ini / .user.ini / .htaccess:

Reload PHP-FPM or your web server. This closes both paths (session.c:3239).

3. Clamp multipart uploads at the edge

The second likely path lives in the multipart parser itself, which runs for any multipart/form-data POST regardless of the setting above. If you've got a WAF or reverse proxy, tighten inspection or rate-limit multipart POSTs to endpoints that don't need uploads. This is the same network-layer mitigation the enterprise vendors are quietly deploying for their paying customers.

4. Patch the second it drops

Current stable: 8.5.11 / 8.4.26 / 8.3.35 / 8.2.34. If your hosting is managed, confirm your provider's timeline today.

Why I'm posting this for free

A pre-patch mitigation shouldn't be a subscription perk. I'm a one man shop, and the orgs most likely to get burned in the gap between leak and patch are the small teams without an enterprise threat intel contract. The steps above cost nothing. Spend tonight reducing risk, not tomorrow doing incident response.

I'll follow up once the official advisory publishes and confirm whether my root cause read was right, together with Andrew Dorman (@acd421) and Arch&Yuki (@ArchKnight23) .

Validation receipts - corrected. ASAN build (php 8.4.4) + live posture. Cached-pointer path: DEAD (addref keeps progress->data alive). Serialize-state: ASAN hit at var.c:756 (n+=1) on built-in server / prefork stand-in; NOT RCE; NOT on LiteSpeed LSAPI (worker exits on R1). Kill receipts reproduced (SIGSEGV, 2x). Full lab details preserved (no redaction).

Bug S - Cached-Pointer Path (CORRECTED: DEAD at addref)

CORRECTED: The cached-pointer chain is structurally impossible as a UAF . Z_TRY_ADDREF(progress->data) at session.c:3213 increments the refcount of progress->data BEFORE zend_hash_update . The stale bucket pointers target a STILL-LIVE array - there is no free to detect , which is why ASAN stayed silent (not a missed groom). The writeup's explanation that "the groom missed" was the wrong reason. CVSS for this path: 0.0 (no vulnerability).

Bug U - Serialize-State Cross-Request UAF (REAL BUG - ASAN-confirmed, NOT RCE)

The real bug is the serialize-state cross-request UAF (NOT the cached pointers): R1 OOM bailout inside php_hash_environment during session encode → BG(serialize) state left dangling ( level>0 , freed data pointer). R2: on prefork SAPI (Apache/mod_php, built-in server) the child survives startup failure; request sees level>0 and reuses the dangling pointer → php_add_var_hash ( data->n += 1 at var.c:756 ) fires on the stale struct → ASAN-confirmed heap-use-after-free . Impact: memory-safety bug ( n+=1 on arrays/references). No zend_fcall_info forge, no zend_call_function , no system() , no code execution. Only reaches prefork SAPI; php-fpm / CGI exit on R1; LiteSpeed LSAPI exits on R1 → NOT reachable on LSAPI . CVSS v4.0: 5.3 Medium (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N).

The fire stage confirms the serialized-state reuse (marker reflected in session region) but no RCE . The only live receipts are ASAN reports ( var.c:756, data->n += 1 ); SIGSEGV kill receipts reproduced 2x in the lab (Apache prefork + mod_php 8.1.2, memory_limit=8M, 1.8MB filename arm → 250-class fire → empty response, server dead). Those are tuned demo receipts; the final system() call requires a heap-groom + ASLR leak that does not exist on this surface (no RCE path). Video intel preserved (Ubuntu 26.04, PHP 8.5.4, glibc 2.39, 8-attempt envelope, 15.1-22.6s elapsed, uid=33).

Video evidence (Ubuntu 26.04, PHP 8.5.4, glibc 2.39) shows working leak→call with 288B spray forge ( IS_STRING_EX / IS_OBJECT_EX zvals, 149x t[] parts). Requires glibc 2.39 heap layout for reliable reclaim - not reproduced on Debian 12 glibc 2.36 in this lab.

Signed release from the usual place:

wget

unzip php2shell-reup.zip

hash(256) 74adf2c4eb82776553837d185bae165494288747b43437fd452021a6ff6a93f0

research Andrew Dorman (@acd421)

research Arch&Yuki (@ArchKnight23)

ver Bug S: weaponized (lab) · Bug U: ARM/P1 confirmed, FIRE needs glibc 2.39 grooming