Quick Answer: The recent PHP pre-auth RCE vulnerabilities (CVE-2026-109618 and CVE-2026-109619) leverage PHP's
session.upload_progressmechanism to corrupt Zend Engine memory during worker startup. By triggering Out-Of-Memory (OOM) conditions during multipart form parsing, attackers bypass application code entirely. Mitigate immediately by settingsession.upload_progress.enabled = Offinphp.inior blocking thePHP_SESSION_UPLOAD_PROGRESSparameter at your WAF before upgrading to PHP 8.2.35+, 8.3.36+, 8.4.27+, or 8.5.12+.
A critical PHP pre-auth RCE disclosure—tracked under CVE-2026-109618 and CVE-2026-109619—has fundamentally altered threat models for web infrastructure. Spanning 14 years of affected releases from PHP 5.4.0 through 8.5.11, these flaws target the Zend Engine core and Server API (SAPI) request handling layer. According to security analysis published by Wiz Research, stock container images running WordPress, Drupal, Joomla, and Magento are exposed out of the box, even when no explicit session management code exists in the application layer.
The Root Cause: SAPI Initialization and Zend Engine Memory Corruption
To understand why this vulnerability bypasses standard application-level controls, we must inspect how PHP processes HTTP requests at the SAPI layer. When PHP handles a multipart/form-data HTTP POST request containing the PHP_SESSION_UPLOAD_PROGRESS form parameter, the SAPI parser initializes upload progress tracking inside ext/session/session.c and main/rfc1867.c. This parser runs before any application script begins execution—prior to framework bootstrap files or auto-prepended scripts.
During file upload stream parsing, if an attacker sends an enormous parameter payload designed to exceed the runtime memory_limit, the memory allocator triggers a Zend Engine memory corruption sequence. Specifically, the engine calls zend_bailout(), which uses C's longjmp() mechanism to abort request processing and jump back to the main SAPI loop.
Here lies the architectural failure: longjmp() unwinds the C execution stack without invoking cleanup destructors or resetting global variables inside ExecutorGlobals (EG()) and BasicGlobals (BG()). Because PHP-FPM workers and Apache mod_php threads are persistent and reused across thousands of requests, these uninitialized or partially populated global structures persist across request boundaries.
When the next HTTP request hits the contaminated worker process, the engine attempts to evaluate incoming request data using corrupted global pointers. For instance, if EG(unserialize_callback_func) or internal serialization handler tables contain stale memory pointers, subsequent deserialization or variable destruction attempts trigger a Use-After-Free (UAF) condition or direct control flow hijacking.
Most security guides assume an endpoint without a session_start() call is immune to session exploitation. That assumption is dangerously flawed. Because session.upload_progress.enabled is active by default in stock PHP installations, the parser runs unconditionally during multipart body processing. Even requests sent to an unauthenticated, bare-bones script like nosession.php trigger the vulnerable path and corrupt worker state.
This next part trips people up every time...
Analyzing the 5 PHP Pre-Auth RCE Chains
The research published in the php2shell-poc repository breaks down the exploit surface into five distinct attack vectors, designated Chains A through E.
+-------------------------------------------------------------------------+
| PHP Multipart POST Request Parsing |
| (session.upload_progress runs at SAPI startup) |
+-------------------------------------------------------------------------+
|
[ Trigger OOM / Payload ]
|
v
zend_bailout() / longjmp()
|
(Global structures left stale)
|
+------------------------+------------------------+
| |
v v
[ Apache mod_php ] [ PHP-FPM Worker ]
Chain D: EG() corrupted Chain E: BG() corrupted
(Zero-dependency Pre-Auth) (Stale state across requests)
Chain A: Session Injection + Local File Inclusion (LFI)
Chain A targets setups where an application exposes a Local File Inclusion vulnerability. By crafting the PHP_SESSION_UPLOAD_PROGRESS value inside the multipart request, an attacker writes arbitrary PHP code into the session file on disk (/tmp/sess_...). The attacker then executes the payload via the LFI parameter. While requiring an application sink, it functions pre-authentication because session file creation occurs during request header parsing.
Chain B: var_destroy UAF to Handler Overwrite
Chain B is a post-authentication chain targeting memory allocation flaws when calling PHP's native unserialize() function. The exploit triggers a Use-After-Free condition in var_destroy(), reads process memory layouts via /proc/self/mem, locates the address of zif_system, and overwrites standard output function pointers (like var_dump) to achieve command execution.
Chain C: Handler Mismatch via __destruct Gadgets
In environment configurations where two directories enforce different session.serialize_handler settings (for example, php vs php_serialize), Chain C pollutes the FPM worker's serializer state (PS(serializer)). By sending a crafted filename containing serialized object payloads, the worker fails during decode, forcing the Zend Engine to trigger object __destruct lifecycle methods immediately. Frameworks utilizing gadgets—such as Laravel's PendingBroadcast—are easily exploited through this vector.
Chain D: OOM Corruption via Stale EG() (Apache mod_php)
Chain D represents the highest severity scenario: a zero-dependency PHP pre-auth RCE against Apache running mod_php. By crafting a precision multipart request that forces an Out-Of-Memory error during upload progress tracking, zend_bailout() leaves stale pointers in EG(). Because mod_php shares execution threads within the main process memory space, the very next HTTP request executing on that worker turns those corrupted pointers into arbitrary memory writes. No application code, session setup, or gadget chain is required.
Chain E: FPM Session OOM via Stale BG() State
Chain E adapts the OOM exploitation mechanism to PHP-FPM environments using a two-phase model:
- Producer Phase: The attacker sends an OOM-inducing upload request to any endpoint that invokes
session_start()(standard across 95% of CMS platforms). This leavesBG(unserialize)in a corrupted state within the FPM worker pool. - Consumer Phase: The attacker sends a second request to any PHP endpoint handled by that worker pool. The worker attempts to evaluate request state using stale pointers, escalating to a 502 gateway crash or remote code execution.
Here's where it gets interesting...
Comparing Exploitation Vectors Across PHP SAPIs
The target execution environment dictates which attack chain poses an immediate risk. Security teams must prioritize mitigation based on whether they operate PHP-FPM, Apache mod_php, or standalone CLI microservices.
| Attack Chain | Target SAPI | Pre-Auth Required? | Prerequisite Application Conditions | Primary Impact Mechanism |
|---|---|---|---|---|
| Chain A | All SAPIs | Yes | Application must contain an LFI sink | Disk-based session payload execution |
| Chain B | All SAPIs | No | Application must call unserialize() | UAF write via /proc/self/mem pointer swap |
| Chain C | PHP-FPM | Yes | Differing directory serialize_handler settings | Unintended __destruct gadget invocation |
| Chain D | Apache mod_php | Yes | None (Zero-dependency) | EG() memory corruption via zend_bailout() |
| Chain E | PHP-FPM | Yes | Any endpoint executing session_start() | Stale BG() worker global state pollution |
As demonstrated above, Chain D and Chain E eliminate the traditional requirement for application-level software bugs. A fully patched WordPress core installation running on default PHP configurations remains fully exploitable through worker pool state poisoning.
That said, there's a real catch here when designing network detection rules...
Detection Engineering: Suricata, Sigma, and WAF Signatures
Because these exploits operate at the protocol parsing layer, traditional application performance monitoring (APM) tools and application loggers rarely capture the initial attack vectors. Security operations teams must deploy network-level signature inspection and Web Application Firewall (WAF) blocking rules.
1. Suricata Network IDS Rules
To detect active exploitation attempts targeting upload progress tracking, deploy Suricata rules for php2shell that inspect multipart form content and high-volume session key injections.
alert http $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS ( \
msg:"EXPLOIT PHP session.upload_progress Pre-Auth RCE Attempt (CVE-2026-109618)"; \
flow:established,to_server; \
content:"POST"; method; \
content:"multipart/form-data"; http_header; \
content:"PHP_SESSION_UPLOAD_PROGRESS"; http_client_body; \
pcre:"/PHP_SESSION_UPLOAD_PROGRESS[\s\x00-\x1f\"':=]+[^\r\n]{500,}/i"; \
classtype:web-application-attack; \
sid:10002941; rev:1; \
)
2. ModSecurity WAF Enforcement
The most effective php session upload progress remote code execution mitigation without breaking application logic is dropping requests containing the progress parameter at the reverse proxy layer.
# Block PHP_SESSION_UPLOAD_PROGRESS in multipart request bodies
SecRule REQUEST_HEADERS:Content-Type "@rx (?i)multipart/form-data" \
"id:2026101,phase:2,t:none,block,msg:'Blocked PHP session.upload_progress exploit vector',logdata:'%{MATCHED_VAR}'"
SecRule ARGS_NAMES "@eq PHP_SESSION_UPLOAD_PROGRESS" \
"id:2026102,phase:2,t:none,block,msg:'PHP_SESSION_UPLOAD_PROGRESS parameter detected'"
3. OpenResty / Nginx Lua Inline Mitigation
For teams utilizing OpenResty or Nginx with Lua modules, the following lightweight filter strips the vector before handing traffic over to upstream PHP-FPM backends:
-- Save as /etc/nginx/php2shell_mitigation.lua
local req = ngx.req
local headers = req.get_headers()
local content_type = headers["content-type"] or ""
if string.find(content_type, "multipart/form-data", 1, true) then
req.read_body()
local body = req.get_body_data()
if body and string.find(body, "PHP_SESSION_UPLOAD_PROGRESS", 1, true) then
ngx.log(ngx.WARN, "Blocked CVE-2026-109618 exploit attempt from: " .. ngx.var.remote_addr)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
This next part matters more than it looks...
Patching and Remediation Roadmap
Remediating these vulnerabilities requires a layered defense posture. Simply updating application dependencies or web application code will not fix Zend Engine memory corruption.
Phase 1: Immediate Configuration Hardening
If immediate binary upgrading is delayed by change control windows, apply these runtime changes in php.ini:
- Disable session upload tracking globally:
session.upload_progress.enabled = Off - Enforce strict session cookie handling:
session.use_only_cookies = On session.use_trans_sid = Off
Disabling session.upload_progress.enabled removes the primary entry point for multipart body parsing exploits. However, because the fundamental OOM corruption bug exists inside the Zend Engine core, this INI change serves as a temporary virtual patch rather than a permanent architectural fix.
Phase 2: Upgrading PHP Binaries
Upstream PHP maintainers released comprehensive fixes in core patch commit 3584a98, which modified over 300 source files across the Zend Engine and core extensions. Production servers must be updated to the following patched baseline releases:
- PHP 8.2 Branch: Upgrade to PHP 8.2.35 or higher
- PHP 8.3 Branch: Upgrade to PHP 8.3.36 or higher
- PHP 8.4 Branch: Upgrade to PHP 8.4.27 or higher
- PHP 8.5 Branch: Upgrade to PHP 8.5.12 or higher
Verify your active runtime version across all web application workers:
php-fpm -v
# Ensure output reflects the patched release numbers
To answer the pressing operational question of how to fix CVE-2026-109618 without patching binaries immediately: you must combine the session.upload_progress.enabled = Off INI directive with aggressive WAF filtering of PHP_SESSION_UPLOAD_PROGRESS headers at your edge proxies.
Frequently Asked Questions
What is the primary cause of CVE-2026-109618?
CVE-2026-109618 is caused by an Out-Of-Memory (OOM) condition during session.upload_progress parsing in multipart POST requests. When memory limits are reached, the Zend Engine executes zend_bailout(), using longjmp() to abort request processing. This leaves executor structures partially populated, resulting in persistent global state corruption across worker requests.
How to fix CVE-2026-109618 without patching the PHP binary immediately?
To block exploitation without an immediate binary upgrade, disable session upload progress in your global php.ini file by setting session.upload_progress.enabled = Off. Additionally, configure your WAF or Nginx reverse proxy to block or strip any request containing the PHP_SESSION_UPLOAD_PROGRESS parameter in multipart POST bodies.
Why is an endpoint without session_start() vulnerable to this exploit?
PHP processes multipart/form-data requests and evaluates session.upload_progress at the SAPI startup layer before any application code executes. If progress tracking is enabled in php.ini, the runtime parses the upload payload and allocates tracking structures regardless of whether the target script calls session_start() or contains session logic.
How does this PHP pre-auth RCE impact CMS platforms like WordPress?
Stock container deployments running WordPress, Drupal, Joomla, Magento, and Laravel are all exposed. While WordPress core does not rely heavily on native PHP sessions, third-party plugins (such as WooCommerce) and global SAPI startup execution leave default server installations vulnerable to worker poisoning.
Next Steps for Infrastructure Security
Addressing these Zend Engine vulnerabilities requires immediate operational intervention across both network firewalls and runtime environments. Audit your server pool today to confirm that session.upload_progress.enabled is disabled or isolated behind inspection proxies.
To protect your environments against PHP pre-auth RCE vectors, review our detailed guide on Securing PHP-FPM Worker Pools and verify your detection coverage with our breakdown of Enterprise WAF Rules for Zero-Day Protection. Audit your systems this week, apply the runtime configuration overrides, and schedule binary upgrades across all production fleets.