PHP2Shell (CVE-2026-109618 & CVE-2026-109619): Shielding PHP Apps While You Patch
Two critical PHP core RCE flaws (PHP2Shell) hit every default install since PHP 5.4. Here is what they do and how to virtual-patch with a WAF before you upgrade.

If you run PHP in production, 21 October 2026 probably wasn't a great day. That's when the PHP team shipped emergency releases for two bugs the researchers nicknamed PHP2Shell — CVE-2026-109618 and CVE-2026-109619, both rated 9.2, both leading to remote code execution with no login required.
The part that makes this one sting: the bugs are in PHP itself, not in a plugin or a framework you can swap out. Your code can be spotless and you're still exposed. Patched WordPress, Drupal, Joomla and recent Laravel builds were all popped in testing.
So how bad is it, really?
Bad enough that it affects basically every way people serve PHP — PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed. The worst case is mod_php, where one unauthenticated request can get code running. And because the vulnerable code goes all the way back to PHP 5.4, a stock install from the last decade is very likely vulnerable out of the box.
What actually happens under the hood
Both bugs are use-after-free, and they share a theme. A request hits memory_limit or trips a fatal error, which yanks PHP's native code out mid-flight before it tidies up after itself. The next request that the same worker handles then reaches into memory that was already freed. From there an attacker can read secrets left over from other requests (that's 109618) or corrupt long-lived state like the session upload-progress handler, fatal-error backtraces, or OPcache (109619).
One detail worth holding onto: every known way to trigger this is a multipart/form-data request. PHP chews on that body during request startup, before a single line of your application runs — which is exactly why it fires even on apps that never call session_start().
The real fix is boring: upgrade
There's no clever workaround that makes this go away. Patch PHP and restart every worker. The fixed releases are:
- PHP 8.5.12
- PHP 8.4.27
- PHP 8.3.36
- PHP 8.2.35
On 8.1 or older? There's no official release for you; lean on a distribution backport if one exists. And take the advisory at its word — config tweaks lower your exposure, they don't make you safe. If you set session.upload_progress.enabled = 0 (plus fatal_error_backtraces = 0 on 8.5), keep post_max_size tight, and cap decoded size, field count, part count and nesting depth at the edge, you've bought time. That's all. Upgrading is the fix.
Buying that time with a WAF
Here's the honest situation most teams are in: you can't patch a whole fleet in an afternoon, but you need cover today. A WAF is good at exactly this. The trigger is specific — a multipart part named PHP_SESSION_UPLOAD_PROGRESS — so the rule can be just as specific.
In WAFio it's a single Custom Rule on the affected app: a Match & Act rule, action Block, with one condition — the multipart part PHP_SESSION_UPLOAD_PROGRESS exists. WAFio reads multipart part names, so the rule settles the request before it ever reaches the detection engines, and it doesn't touch your normal file uploads. While you're there, set a strict per-path size limit to match the post_max_size advice, and flip on_uninspected to block if you want requests with absurd field counts or nesting rejected outright.
Don't fool yourself about the stopgap
A WAF parses multipart bodies differently from PHP. That gap is why this is a bridge and not a cure — a patient attacker may find an encoding the filter doesn't expect. The bug itself runs deep inside PHP, before most of what a WAF can even see. Treat the rule as cover, not closure, and take it down once every worker is on a patched build.
A sane order of operations
- Find every host and image serving PHP, and write down its version. You can't fix what you can't see.
- Drop the WAFio custom rule on each public app right now, for cover.
- Roll out the patched release, restart workers, and confirm the running version with a real request — not the CLI, which can lie about what FPM is actually running.
- Pull the stopgap only when the fleet reports a fixed build.
This is the kind of day WAFio's semantic WAF and custom rules were built for. Poke at it in the live demo, or read the docs to wire the rule into your own project.