TL;DR
PHP is releasing emergency fixes today for two critical use-after-free vulnerabilities in the session upload progress handler. Both allow remote code execution on default configurations and were leaked before the coordinated disclosure.
What’s happening?
The bugs live in php_session_rfc1867_callback (ext/session/session.c). PHP caches two zval * pointers (post_bytes_processed and current_file_bytes_processed) into HashTables while tracking multipart upload progress. A second request triggers session re-read and zend_hash_update, which destroys the HashTable via zval_ptr_dtor. The freed memory can then be reclaimed and controlled by the attacker.
How the attack works
An attacker sends a fast multipart POST that populates the cached pointers, followed by a second request that forces the HashTable destruction. After the free, the attacker places a controlled zend_fcall_info / zend_internal_function structure at the reclaimed address. The next use of the dangling pointer reaches zend_call_function, which can be directed at system() or equivalent.
The exploit requires the upload-progress key as a multipart form field (default name PHP_SESSION_UPLOAD_PROGRESS) before a file part. Stock php.ini is sufficient (session.upload_progress.enabled=1). The field name is configurable via session.upload_progress.name, so only the default is a reliable indicator.
Successful exploitation needs a per-build offset between zval_ptr_dtor and zend_call_function. A wrong offset or a lost heap race typically segfaults the PHP worker. When a proxy sits in front (nginx → php-fpm), this produces a 5xx (commonly 502). A correct offset that wins the race returns 200 and executes the payload. mod_php without a proxy often just drops the connection.
Independent research published confirmed the issue on stock PHP 8.4.4 and 8.2.34 and supplied a framework that extracts build-specific offsets, forges the body, and delivers the payload. PHP’s multipart parser is also looser than a simple line-based field matcher: it accepts folded headers, lines with no colon (glued to the previous header), Content-Disposition without form-data, missing spaces, and junk after the closing quote. Detection logic that only looks for a clean name=”PHP_SESSION_UPLOAD_PROGRESS” on a single line can be evaded.
Why this matters
- Default configuration is vulnerable.
- PHP powers a large fraction of the web (WordPress, Drupal, Magento, control panels, shared hosting).
- Successful exploitation yields command execution as the PHP process user.
- End-of-life branches (8.1 and earlier, 7.x, 5.x) will not receive official fixes.
Recommended actions
- Apply the official PHP security release as soon as it is available (targeted for today at 14:00 UTC). Expected fixed versions include the current supported branches (8.2–8.5).
- Immediate mitigation: set session.upload_progress.enabled=0 in php.ini, .user.ini, or .htaccess.
- Inventory every PHP installation (php-fpm, php-cgi, mod_php, control panels, Windows bundles, application-bundled PHP).
- For detection, look for POST requests containing the upload-progress field (accounting for the parser’s loose header handling) and treat 5xx responses from PHP workers as a high-confidence crash signal of an attempted exploit. A 200 on the same pattern is the success case.
- Isolate or upgrade any internet-facing end-of-life PHP.
The bigger picture
These are classic use-after-free bugs in a high-traffic request path. The leak forced an accelerated release, and the technical details (including the precise free and reclaim path plus the need for per-build offsets) are already public. Until systems are patched or the feature is disabled, any exposed PHP process that handles multipart uploads is a candidate for remote code execution.
Bottom line
Two critical use-after-free flaws in PHP’s session upload progress handling allow remote code execution on default configurations. Disable session.upload_progress.enabled now if you cannot patch immediately, apply the official release as soon as it drops, and treat end-of-life PHP as high risk.
