Later this afternoon, PHP is going to announce the patches for two highly critical vulnerabilities found in the PHP core. Both seem exploitable without authentication on any remote website that has sessions or file uploads enabled, which both are enabled in the default php.ini config.
Details are still emerging, and it’s a little unclear now when the exact patch will come out, since proof-of-concepts are already doing the rounds. Hopefully, the timeline for these patches gets pushed forwards.
In the meantime, information is still vague and confusing out there, but the following might be early mitigation measures you can already take now ahead of the patches in the next few hours. I cannot emphasize enough the importance of these.
It looks like a complete armageddon for PHP websites out there, having the ability to pop a remote shell on just about any public PHP website is … insane. 🤯
Everything that follows here has mostly been written by Claude and Codex. Given the time urgency, I did not write all of these steps manually. They have been kept as brief as possible where available. They’ll be kept up-to-date throughout the day.
Hopefully, these are correct mitigation steps for the PHP vulnerability. Do not rely on these steps alone. You will need to patch ASAP as soon when the updated packages come out. Point your LLM to this blog post, tell it to review and analyze, and make it propose these changes for your own infrastructure environments.
Early mitigation steps#
The only confirmed fix is the patched PHP release. Install it as soon as it is out. The PHP team says patching is the only real mitigation.
Until then, these two steps may reduce the risk. Nobody has proven that they stop the exploit:
- Turn off
session.upload_progress.enabled. If the leaked details are right, the leaked exploit uses this feature. We tested that the setting turns the feature’s code path off. We could not test it against the real exploit. - Block every request with a
multipartContent-Type at the web server, for apps that do not need file uploads. The person who leaked the bug claims a second path in multipart handling. If that is true, this block may also stop that path. No one has confirmed it.
This post was written on 21 October 2026, before the release. Facts may change after the release.
Updates#
- 21 October, 08:30 UTC. Attacks have started. Cloudflare’s CTO wrote at 07:43 UTC : “We are seeing early attacks emerging and are updating mitigations as new vectors show up.” Cloudflare’s WAF rules for this are now on every plan, including Free. See the Cloudflare section below.
- 21 October, 08:30 UTC. A public write-up now names the bug: a use-after-free in PHP’s session upload-progress code. The PHP team has not confirmed it. See what is disputed .
What is known#
| Item | Status | Source |
|---|---|---|
| CVEs | CVE-2026-109618, CVE-2026-109619 | php.announce #509 |
| Severity | Both CVSS v4.0 9.2, Critical | php.announce #509 |
| Fix release | Wednesday 21 October 2026, 14:00 UTC. PHP may release earlier “should evidence of active exploitation appear in the wild” | php.net news |
| Impact | Unauthenticated remote code execution | CERT-EU 2026-018 |
| Affected versions | Not confirmed. Reports say “all PHP versions from PHP 5 onwards”. Sansec says “PHP 5.4 or later” | CERT-EU , Sansec |
| Fixed versions | Will be named in the release. Only 8.2 and newer get a fix | php.announce #509 , supported versions |
| Affected SAPIs | No source says which. Assume FPM, mod_php, FrankenPHP and CGI are all affected | none |
| Technical details | “No technical details are currently available” | CERT-EU |
| Exploitation in the wild | Yes, early attacks, according to Cloudflare’s CTO on 21 October at 07:43 UTC. Huntress had not seen any on 20 October | Dane Knecht , Huntress |
| Expected attack start | Sansec: “We expect mass attacks to start within 15 minutes of the release” | Sansec |
The leak became public when Kevin Beaumont posted that “Somebody has leaked CVE-2026-109618 and CVE-2026-109619 in PHP before the patch is out.” The Hacker News thread has the community discussion. watchTowr has an FAQ and BleepingComputer has a report . Both block automated requests, so I could not check their text myself.
What is disputed or unverified#
- The leak is reportedly about file uploads. This write-up reports the leak involves “PHP’s session upload-progress feature”. PHP has not confirmed this.
- The leaker claims a second path. The same write-up says the leaker “claims a second route through PHP’s multipart handling that this setting does not cover.” Nobody outside the PHP security team can confirm this yet.
- A public write-up describes the bug in detail. Kevin Beaumont shared it
. It says the bug is a use-after-free in
php_session_rfc1867_callback, the upload-progress handler. That handler keeps two raw pointers into an array, and the write-up says PHP frees that array while the pointers are still in use. The author says the default config is enough to trigger it, and that it was tested in a lab against PHP 8.4.4 and 8.2.34. We checked one part: the handler does store those two pointers (line 3316 and line 3342 ). We did not run the exploit. The author recommends the same two steps as this post. - The write-up may not describe the embargoed bug. One HN commenter says they found two similar bugs and believes the write-up is about a different one.
- A PoC that people are sharing may be broken. One HN commenter who read it says it “assumes PHP frees the upload progress data when it reloads the session”. The commenter says PHP 8.4 does not do that. The same commenter says the ini setting “does not seem to cover every cases”.
- People disagree on whether two ini settings are enough. Another HN comment
quotes posts on X: some say
session.upload_progress.enabledoff plusfile_uploads = Offis enough, and others say “Upload progress can serialize before basic RINT clears BG(serialize)”. I could not verify either claim. - PHP core developer Gina Banyard said : “The only thing I can say is that the only mitigation is patching / upgrading PHP. I sadly know too much.” In a follow-up : “if you can afford turning of a server for a day, that might be the most sensible thing to do.”
What each mitigation covers#
Based on what is reported so far. None of this is confirmed against the real exploit.
| Mitigation | Upload-progress path (reportedly the leaked exploit) | Claimed second multipart path | Side effects |
|---|---|---|---|
session.upload_progress.enabled = 0 | Code path turned off (tested, see proof below). May stop the exploit | Probably not covered, if the leaker is right | Upload progress bars built on this PHP feature stop working |
Block multipart at the web server | Request never reaches PHP. May stop the exploit | May stop it, if the bug is in multipart parsing | All file uploads break, unless you allowlist specific paths |
| Allowlist only some upload paths | Same as above, on all other paths | Same as above, on all other paths | The allowlisted paths stay exposed |
| Install the patched PHP | Fixed, according to PHP | Fixed, according to PHP | none |
These are unlikely to help, based on what is reported:
file_uploads = Off. PHP still parses the multipart body. It only skips the file parts (source ).- Small
post_max_sizeor upload limits. No size is known to be safe. Apost_max_sizeof0means no limit at all. enable_post_data_reading = Off. It breaks normal forms. It also does not coverrequest_parse_body(), which PHP 8.4 added for parsing multipart bodies on other HTTP methods.- Application code: middleware, CSRF checks, login checks, Laravel maintenance mode and WordPress security plugins. All of these run after PHP has already parsed the request body.
- A WAF such as Cloudflare, if the origin server is reachable directly. Attackers send the request to the origin IP and skip the WAF.
Why upload progress matters#
When session.upload_progress.enabled is on (the default), PHP checks every multipart request for a form field named PHP_SESSION_UPLOAD_PROGRESS. When it finds one, PHP opens a session with the session ID from the request cookie and writes progress data into it. All of this happens before your PHP script runs. Your app does not need to use sessions for this to happen.
If the leaked details are right, this is the code the exploit abuses. When the setting is off, PHP skips this session code. See the early return in ext/session/session.c
(PHP-8.4 branch):
if (!PS(rfc1867_enabled)) {
return retval;
}
The setting is PHP_INI_PERDIR
. You can set it in php.ini, in an FPM pool, or in .user.ini/.htaccess, but not with ini_set() at runtime.
Proof: upload progress writes a session for any anonymous request#
This test shows that anyone can reach the upload-progress code path. The script below never calls session_start(). It gets one request. That request has a session cookie the attacker picked, and the PHP_SESSION_UPLOAD_PROGRESS field. PHP then creates sess_attackerchosen. With the setting off, PHP creates nothing. Tested on PHP 8.4.17 (CLI server):
$ mkdir -p www sess && echo '<?php echo "no session used\n";' > www/index.php
$ head -c 300000 /dev/urandom > upload.bin
$ php -n -d session.save_path=$PWD/sess -d session.upload_progress.enabled=1 -S 127.0.0.1:8765 -t www &
$ curl -s -b 'PHPSESSID=attackerchosen' -F PHP_SESSION_UPLOAD_PROGRESS=x -F file=@upload.bin http://127.0.0.1:8765/
no session used
$ ls sess/
sess_attackerchosen
$ kill %1; rm sess/*
$ php -n -d session.save_path=$PWD/sess -d session.upload_progress.enabled=0 -S 127.0.0.1:8765 -t www &
$ curl -s -b 'PHPSESSID=attackerchosen' -F PHP_SESSION_UPLOAD_PROGRESS=x -F file=@upload.bin http://127.0.0.1:8765/
no session used
$ ls sess/
$
With the default session.upload_progress.cleanup=On, the file is still created but is empty after the request ends. I ran the same test on php:8.4-fpm-alpine (PHP 8.4.26) behind nginx, and on FrankenPHP 1.13.1 (PHP 8.4.26). The result was the same. The PHP-FPM and FrankenPHP settings below stopped the session file there too.
One more test result: PHP also parses Content-Type: MULTIPART/FORM-DATA in uppercase. So every web server rule below matches without regard to case.
This proves the code path runs for anonymous requests, and that the setting disables it. It does not prove that the setting stops the actual exploit. Nobody outside the PHP security team has the real details.
Mitigation configs#
Tested versions: nginx 1.29.8, Apache 2.4.69, lighttpd 1.4.85, Caddy 2.10.2, FrankenPHP 1.13.1, PHP 8.4.26. All tests ran in Docker or locally on 21 October 2026. Each block rule returned 403 for multipart requests. This was true for POST and PUT, in lowercase and uppercase. GET, JSON and application/x-www-form-urlencoded requests still returned 200.
php.ini (all SAPIs that read php.ini)#
Add a drop-in file to each SAPI’s scan directory, for example /etc/php/8.4/fpm/conf.d/99-disable-upload-progress.ini on Debian/Ubuntu:
session.upload_progress.enabled = 0
Then reload: systemctl reload php8.4-fpm. Repeat this for every PHP version that has a running FPM service. If you use mod_php, also add the file to apache2/conf.d. Then restart Apache.
Note: an FPM reload kills requests that are still in progress, unless process_control_timeout is set. Expect a short burst of 502s.
PHP-FPM pool (cannot be overridden per directory)#
; /etc/php/8.4/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
Tested end to end: with this line, the session repro above writes no file.
FrankenPHP#
FrankenPHP has PHP built into its binary and often loads no php.ini. Set the value in the global options block of the Caddyfile:
{
frankenphp {
php_ini session.upload_progress.enabled 0
}
}
Tested: ini_get() changed from 1 to 0, frankenphp validate reported a valid configuration, and the session repro wrote no file. If you use Laravel Octane with --caddyfile, put this in that file and restart Octane.
nginx: block all multipart#
# http {} context, e.g. /etc/nginx/conf.d/block-multipart.conf
map $http_content_type $block_multipart {
default 0;
"~*multipart" 1;
}
# inside every server {} that passes requests to PHP, including default/catch-all servers
if ($block_multipart) {
return 403;
}
If an error_page 403 sends the request back to PHP, use return 444; instead.
nginx: allow multipart only on your upload endpoints#
map "$http_content_type|$uri" $block_multipart {
default 0;
"~*multipart.*\|/(avatar/upload|livewire/upload-file)$" 0;
"~*multipart" 1;
}
Replace the paths with your own upload endpoints. Tested results: /login returned 403. /avatar/upload and /livewire/upload-file returned 200. So did /avatar/upload?x=1, because $uri does not include the query string. /x/avatar/upload/y returned 403. The allowlisted endpoints may stay exposed until you patch.
Apache 2.4#
<If "req('Content-Type') =~ /multipart/i">
Require all denied
</If>
Put this in every vhost that serves PHP. Use req('Content-Type'). Do not use %{CONTENT_TYPE}, which this write-up
warns against. Tested with httpd -t (“Syntax OK”) and with requests.
Caddy#
example.com {
@multipart header_regexp Content-Type (?i)multipart
respond @multipart 403
# ... php_fastcgi / php_server as before
}
Use header_regexp with (?i). Tested with caddy validate and with requests.
lighttpd#
server.modules += ( "mod_access" )
$REQUEST_HEADER["Content-Type"] =~ "(?i)multipart" {
url.access-deny = ( "" )
}
Tested with lighttpd -tt and with requests.
Cloudflare WAF#
Cloudflare’s CTO says Cloudflare has deployed WAF rules “for the known exploit vectors in these vulnerabilities”. He says the rules are on every plan, including Free. The WAF changelog lists two emergency releases on 21 October. Both add rules named “Generic Rules - Anomalous Execution”: four rules in the Managed Ruleset and two in the Free Ruleset. Cloudflare also said that “the only way to fully protect your site is to update php once released.”
The rules only help if your origin accepts traffic from Cloudflare’s IPs only. Otherwise attackers send the request straight to the origin.
Cloudflare WAF custom rule (untested)#
Expression, with action Block:
lower(http.request.headers["content-type"][0]) contains "multipart"
Cloudflare keeps the header value’s original case, so the expression uses lower()
. I have not tested this rule. Unlike the managed rules, it also blocks multipart requests that do not match a known exploit.
Limit what a successful exploit can do (untested)#
Kevin Beaumont suggests
AppArmor or SELinux, to stop PHP from running other programs. The public write-up ends by calling system(), so blocking exec from the PHP worker would break that last step. It would not stop the memory bug itself. Code that runs inside the PHP process can still read your files and database credentials. I have not tested this.
Verify the mitigation#
# Effective value for the FPM SAPI (should say Off)
$ php-fpm8.4 -i | grep session.upload_progress.enabled
session.upload_progress.enabled => Off => Off
# From inside the app: the result should be "0"
<?php var_dump(ini_get('session.upload_progress.enabled'));
# Web server block, from outside (should print 403)
$ curl -s -o /dev/null -w '%{http_code}\n' -F a=b https://example.com/
403
To test only the PHP setting, send the session probe from the proof section to a staging copy without the web server block. Then check the session save path (/var/lib/php/sessions on Debian/Ubuntu) for sess_<your id>.
Patch as soon as the release is out#
- Ondřej Surý’s PPA (ppa:ondrej/php
): after the release, run
apt-get update, then upgrade only thephpX.Y-*packages, then restart FPM. - Some third-party mirrors of that PPA lag behind. Check that
apt-cache policy php8.4-fpmshows the new version. If it doesn’t, point your sources atppa.launchpadcontent.net. - Ubuntu and Debian archive packages get backported fixes. The upstream version number may not change. Check the package version or the security advisory instead.
- FrankenPHP and other static builds ship PHP inside the binary, so apt will not patch them. You need a new FrankenPHP release.
- PHP 8.1 and older are end of life and will get no official fix (supported versions ). For these, keep both mitigations in place and move to a supported version. Taking the site offline is the only safe option until then.
- Restart FPM after upgrading. Long-running PHP processes keep the old code in memory until they restart. This includes FPM masters, Octane, queue workers and the Apache module.
After patching, you can leave upload progress off. Few apps use it.