DEV Community

Cover image for Patching PHP2Shell (CVE-2026-109618) Doesn't Stop Session-Poisoning LFI to RCE
Anwar Iqbal Dawar
Anwar Iqbal Dawar

Posted on

Patching PHP2Shell (CVE-2026-109618) Doesn't Stop Session-Poisoning LFI to RCE

Updating to PHP 8.2.35, 8.3.36, 8.4.27, or 8.5.12 fixes the engine's out-of-memory corruption and stale worker state, and CISA placed both CVEs on the KEV catalog with an October 29 federal deadline. What stood out to me in the post-patch testing from Abstract Security is that patched hosts still leave session.upload_progress.enabled = 1 on by default, turning any postponed local file inclusion (LFI) bug into an unauthenticated two-request RCE.

Vulnerability vs. Post-Patch Exposure Matrix

Vector / Mechanism CVE-2026-109618 (longjmp OOM) CVE-2026-109619 (Stale Worker State) Session Upload Progress LFI-to-RCE
Execution Boundary Pre-script (php_request_startup) Cross-request (PHP-FPM / mod_php worker) Request startup multipart parsing (RFC1867)
Affected Versions PHP 5.4 – 8.5 (unpatched) PHP 5.4 – 8.5 (unpatched) All versions (including patched 8.2.35–8.5.12)
Fixed by Oct 2026 Patch? Yes (8.2.35, 8.3.36, 8.4.27, 8.5.12) Yes (8.2.35, 8.3.36, 8.4.27, 8.5.12) No (enabled by default in php.ini)
App-Level ini_set() Fix? Ineffective (triggers before script) Ineffective (triggers during shutdown/reuse) Ineffective (PHP_INI_PERDIR stage only)
Primary Mitigation Upgrade binary / backport engine patch Upgrade binary + reduce pm.max_requests Set session.upload_progress.enabled = 0 in php.ini

1. Pre-Script Execution via longjmp() Cleanup

In unpatched versions (5.4 through 8.5), CVE-2026-109618 triggers when an out-of-memory error forces a longjmp() bailout while internal request structures are only partially initialized. During request shutdown, PHP's cleanup routine trusts those uninitialized pointers before a single line of application code runs, while CVE-2026-109619 leaves freed request pointers alive in global state for the next request on the same PHP-FPM or mod_php worker.

Why application WAFs miss it: Because the fault happens inside php_request_startup() during multipart or query parsing, framework middleware, PHP-level authentication guards, and userland input sanitizers never execute.

2. Why ini_set() and Upload Progress Tweaks Fail

Setting session.upload_progress.enabled=0 inside application code (ini_set()) does nothing against the engine CVEs or session creation because multipart parsing happens during the PHP_INI_PERDIR / SYSTEM initialization phase before the script starts.

Even on freshly patched 8.2.35+ servers, an attacker can pass a chosen PHP_SESSION_UPLOAD_PROGRESS field in a multipart POST request alongside a custom PHPSESSID cookie to write an unsanitized filename payload directly into /var/lib/php/sessions/sess_<id> on disk. If your codebase has a dormant include($_GET['page']) style LFI, racing a concurrent read against that active upload executes arbitrary code even when session.use_strict_mode = 1 is enabled.

Configuration Directive Default Value Hardened Value Config Scope (Changeable) Security Impact
session.upload_progress.enabled 1 (On) 0 (Off) PHP_INI_PERDIR (php.ini / .user.ini / FPM pool) Stops unauthenticated creation of /var/lib/php/sessions/sess_* via multipart POST
session.use_strict_mode 0 (Off) 1 (On) PHP_INI_ALL (best in php.ini) Rejects uninitialized session IDs passed via cookies by normal session handlers
max_input_vars 1000 1000 PHP_INI_PERDIR Caps hash table collisions and multipart variable exhaustion
post_max_size 8M 8M (or stricter at proxy) PHP_INI_PERDIR Limits memory allocation spikes during pre-script body parsing

3. Lock Down the Parser Boundary and Verify Builds

First, verify your active PHP-FPM or CLI build date and patch level rather than trusting package manager cache headers:

php -v
php -i | grep -E "session.upload_progress.enabled|session.use_strict_mode|max_input_vars"
Enter fullscreen mode Exit fullscreen mode

Next, disable upload progress at the system php.ini level so session files cannot be used as an unauthenticated staging ground:

; /etc/php/8.3/fpm/php.ini - must be set at the INI or pool level, not runtime
session.upload_progress.enabled = 0
session.use_strict_mode = 1
max_input_vars = 1000
post_max_size = 8M
Enter fullscreen mode Exit fullscreen mode

Optional: Lock down via PHP-FPM Pool Config & Nginx Body Limits (Click to expand)

If you run multi-tenant PHP-FPM pools and want to make sure developers cannot re-enable upload progress via .user.ini or .htaccess, enforce php_admin_flag inside /etc/php/8.3/fpm/pool.d/www.conf:

php_admin_flag[session.upload_progress.enabled] = off
php_admin_flag[session.use_strict_mode] = on
php_admin_value[max_input_vars] = 1000
Enter fullscreen mode Exit fullscreen mode

At the reverse-proxy boundary (Nginx/Caddy), cap request body sizes before payloads ever reach the FastCGI socket:

# nginx.conf / site block
client_max_body_size 8m;
client_body_buffer_size 128k;
Enter fullscreen mode Exit fullscreen mode

For teams triaging exposure across legacy 7.4–8.1 backports and newer runtimes, we map emergency advisories, severity decisions, and response workflows in our xSypher product security runbooks alongside our verification standards.

Are you backporting the minimal source patches for EOL PHP 7.4/8.1 runtimes in production, or isolating legacy workers behind strict reverse-proxy body limits until you can upgrade?

Top comments (0)