Status as of October 21, 2026, 9:41 AM EDT. We will update it as fixed packages land and exploitation reports are confirmed or ruled out.
Two vulnerabilities in PHP’s request handling, collectively nicknamed “php2shell”, allow remote code execution. Both are reached while PHP parses the incoming request, before your framework, middleware, login checks or security plugins run. Any internet-facing PHP application should be treated as exposed until it is upgraded.
Summary
- Upgrade PHP to 8.2.35, 8.3.36, 8.4.27 or 8.5.12. PHP says this is the only complete fix.
- PHP 8.1 and older will not be fixed. Keep the mitigations below in place, or take those sites offline, until you’ve migrated.
- Until you can upgrade: set
memory_limit = -1 and session.upload_progress.enabled = 0 server-wide, block multipart requests at the web server for apps that don’t accept uploads, and restart every PHP worker.
1. The fix: upgrade PHP
Fixed versions are 8.2.35, 8.3.36, 8.4.27 and 8.5.12. PHP’s release builds are scheduled for 10:00 AM EDT (14:00 UTC) on October 21.
After upgrading, restart everything that runs PHP: FPM masters, Apache with mod_php, and Laravel Octane and queue workers. A worker that was started before the upgrade is still running the vulnerable code.
How you get the fix depends on where your PHP comes from:
- Ondřej Surý’s Ubuntu PPA: run
apt-get update, upgrade the phpX.Y-* packages, then confirm the new version with apt-cache policy php8.4-fpm. Some mirrors lag behind; use ppa.launchpadcontent.net directly if yours doesn’t have the update yet. - Ubuntu and Debian distribution packages: these may backport the fix without changing the PHP version number. Check the package version, not
php -v. - Remi’s repository (common on RHEL, CentOS, AlmaLinux and Rocky): will not have the fix today. Apply the mitigations below.
- FrankenPHP and other static builds: need a new release of the binary.
apt can’t patch PHP that’s compiled into them. - Chainguard images: Chainguard says its free images for 8.2 to 8.5 are already patched (image directory). We haven’t verified this; check the image build date before relying on it.
2. The vulnerabilities
CVE-2026-109618: out-of-memory during multipart parsing
Official advisory (GHSA-w8hw-w9fx-j79w)
An attacker can force an out-of-memory error while PHP parses a multipart upload. Ways to trigger it include a request with no usable Content-Length, parts with unlimited headers, or empty file fields with nested names. The end-of-request cleanup then reads the broken memory left behind, which can lead to remote code execution.
- Apache mod_php: confirmed exploitable with default settings, before any application code runs.
- PHP-FPM: vulnerable according to the source code. No working FPM exploit has been shown publicly.
- FrankenPHP: likely affected. LSAPI: may be affected.
CVE-2026-109619: use-after-free across requests
Official advisory (GHSA-6wmf-4rv5-p3rp)
State left over from one request is used after it has been freed, on the next request handled by the same worker. Remote code execution is confirmed:
- on mod_php and FPM through session upload progress, which is on by default and does not require your code to call
session_start() - on mod_php through PHP 8.5’s
fatal_error_backtraces
3. Interim mitigations until you’ve patched
Set each of these in the server-wide php.ini or the FPM pool config, not per directory, then restart every worker.
memory_limit = -1 ; the main stopgap for 109618, but not complete
session.upload_progress.enabled = 0 ; keep this off even after patching
fatal_error_backtraces = 0 ; PHP 8.5 only
In an FPM pool config:
php_admin_flag[session.upload_progress.enabled] = off
Then:
- Block multipart requests at the web server for any app that doesn’t take uploads (rules in section 4).
- Shrink what PHP will parse. Keep
post_max_size small and enforce the decoded body size at the web server as well. Lower max_input_nesting_level and max_input_vars, and cap the number of multipart parts, headers per part and uploads. - Do not set
RLIMIT_AS or RLIMIT_DATA. They make mmap() fail, which triggers the vulnerable code path. A cgroup memory.max limit helps only partly. - Remove per-directory PHP settings that throw warnings before the request starts:
.htaccess and Apache php_value or php_admin_* lines, and nginx PHP_VALUE or PHP_ADMIN_VALUE parameters. Hiding the warnings with display_errors, error_reporting or @ does not protect you. - On FPM, if you can take the performance hit, set
pm.max_requests = 1 so no worker serves a second request.
Verifying the upload progress setting:
ini_set() can’t change session.upload_progress.enabled, so setting it in application code does nothing.- Check the effective value from the web side with
ini_get(), or on the server with php-fpm8.4 -i | grep upload_progress. .user.ini only works under FPM and can take up to 5 minutes to apply.
4. Web server rules: block multipart requests
These rules come from Mattias Geniar, who tested them on nginx 1.29, Apache 2.4, Caddy 2.10, lighttpd 1.4 and FrankenPHP 1.13 (write-up). They match “multipart” in any letter case.
File uploads will break unless you allowlist those paths, and allowlisted paths remain exposed.
nginx
In the http {} block:
map $http_content_type $block_multipart { default 0; "~*multipart" 1; }
Then in every server {} block that passes requests to PHP, including the catch-all server:
if ($block_multipart) { return 403; } # use 444 if error_page 403 routes back to PHP
With an upload allowlist:
map "$http_content_type|$uri" $block_multipart {
default 0;
"~*multipart.*\|/(avatar/upload|livewire/upload-file)$" 0; # your upload paths
"~*multipart" 1;
}
Apache 2.4
In each vhost or in .htaccess:
<If "req('Content-Type') =~ /multipart/i">
Require all denied
</If>
Caddy
@multipart header_regexp Content-Type (?i)multipart
respond @multipart 403
lighttpd
server.modules += ( "mod_access" )
$REQUEST_HEADER["Content-Type"] =~ "(?i)multipart" { url.access-deny = ( "" ) }
FrankenPHP
In the Caddyfile’s global block:
{ frankenphp { php_ini session.upload_progress.enabled 0 } }
Read-only fallback
If you can’t take a site offline, you can reject everything except GET and HEAD. Logins, forms and checkout will stop working.
if ($request_method !~ ^(GET|HEAD)$) { return 503; }
Test the block
curl -s -o /dev/null -w '%{http_code}\n' -F a=b https://example.com/ # expect 403
Managed rules can stop the current public exploit. They don’t fix the memory bugs, so use them alongside patching, not instead of it.
- Imunify360: ModSecurity ruleset 8.83 blocks the public exploit. Check your version with
cat /etc/apache2/conf.d/modsec_vendor_configs/imunify360-full-apache/VERSION and update with imunify360-agent update --force modsec-rules. - Bunny Shield: blocks the public exploit pattern automatically.
- OWASP Core Rule Set: an unverified report says it catches more than 95% of attack attempts.
Any WAF can be bypassed if your origin server is reachable directly. Lock the origin down so it only accepts traffic from your WAF’s IP addresses.
6. Don’t rely on these
- Blocking the
PHPSESSID cookie breaks every login and cart, and doesn’t stop the attack. file_uploads = Off: PHP still parses the multipart body.enable_post_data_reading = Off breaks forms and doesn’t cover request_parse_body() in PHP 8.4.- Application-level defenses (middleware, CSRF or login checks, Laravel maintenance mode, WordPress security plugins) all run after PHP has already parsed the request.
disable_functions (for example, disabling system) only stops the current public exploit, not the underlying memory bug.- The Qi Deng write-up and the ModSecurity rule based on it: the author retracted the use-after-free path. The official advisories supersede it.
- The LLM-written patch posted to pastebin from Hacker News is untrusted code. Don’t apply it.
7. Taking a site offline safely
Stop the web server, or return 503 for every request.
Do not just disable the PHP handler. Apache and nginx will then serve your .php files as plain text, including any passwords and API keys in them. Stopping only PHP-FPM is safe, because nginx returns 502 instead.
CERT-EU advises taking internet-facing PHP applications off the public network if you can’t patch. Short of that, put them behind a VPN, an IP allowlist or a login at the edge.
8. Reduce the damage and watch for attacks
Hardening:
- Run PHP as a user that can’t write to disk, keep
sudoers clean, and use AppArmor or SELinux to stop PHP from launching other programs. - Remove stored credentials and cloud IAM roles from servers that run PHP.
Detection:
- Alert when
php-fpm, php-cgi, httpd or apache2 starts a shell, a script interpreter, curl or wget. - Watch for PHP workers crashing with
SIGSEGV or SIGABRT on multipart POSTs. - Watch for
PHP_SESSION_UPLOAD_PROGRESS in requests to routes that don’t accept uploads, and for unexpected session files.
If you find a hit, isolate the server, preserve the logs, and check for web shells, new cron jobs and new user accounts. Patching doesn’t remove anything an attacker already left behind.
Exploitation status
Some security vendors report attacks in the wild. Germany’s BSI says none are confirmed. As of this writing, nobody has published evidence either way.
Questions about your setup? Contact us or open a ticket from your dashboard.