PHP2Shell (CVE-2026-109618/109619): How We Virtual-Patched It at the Edge
Two critical PHP advisories allow unauthenticated RCE through an OOM bailout and stale global state. Here is how the bugs work, what Yeap WAF blocks at the edge on day one, and what you still need to fix on your origin.

On October 21, 2026, the PHP project published two Critical security advisories at once: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619), both scored 9.2 on CVSS v4. Each one allows remote code execution without authentication. The pair has become known as PHP2Shell.
All four maintained branches (PHP 8.2, 8.3, 8.4 and 8.5) are affected, and the PHP team confirmed that mod_php is exploitable with default settings. For any site running WordPress, Laravel, or any other PHP application, a single anonymous POST request could be enough to take over the server.
The same day the advisories went out, we pushed a virtual patch for the public attack path to every Yeap CDN edge node. This post explains how the bugs work, what our rules block, what they cannot block, and what you still need to do on your origin.

What the vulnerabilities are
CVE-2026-109618: memory corruption from an OOM bailout
When PHP runs out of memory, it calls longjmp() to abort the current request. In some places, request memory is already reachable before it is fully initialized. If an allocation fails at exactly that moment, the early exit leaves invalid memory behind, and request shutdown later reads it and branches on it.
An attacker can trigger the OOM reliably within normal PHP limits using a malformed multipart/form-data request: no usable content length, a part with an enormous number of headers, empty file controls that slip past max_file_uploads, or deeply nested field names. The official fix removes longjmp() for OOM entirely. PHP now exits gracefully at the next safe point, and calls _exit() immediately if memory use crosses a higher threshold.
CVE-2026-109619: stale global state across requests
Global state that belongs to a request (error, session, object and extension state) can survive in the worker process after that request's memory has been freed. Some code reads this state before the next request is properly initialized, and so it follows a pointer into freed memory: a use-after-free.
- Producers (requests that leave stale state behind): fatal errors, execution timeouts, a failed
require, out-of-memory, andfatal_error_backtraceson PHP 8.5. - Consumers (requests that read it): INI diagnostics applied before request startup, and
session.upload_progresshandling, which is on by default.
When a producer and a consumer land on the same worker, an attacker can shape the heap to turn the use-after-free into code execution. The PHP team validated the upload-progress path against both mod_php and PHP-FPM.
Affected versions
| Branch | Affected | Fixed in |
|---|---|---|
| PHP 8.5 | < 8.5.12 | 8.5.12 |
| PHP 8.4 | < 8.4.27 | 8.4.27 |
| PHP 8.3 | < 8.3.36 | 8.3.36 |
| PHP 8.2 | < 8.2.35 | 8.2.35 |
mod_php is confirmed exploitable. Based on source analysis, FPM, FrankenPHP and LSAPI are susceptible too. End-of-life versions will not get an official patch, which makes protection in front of them even more important.
What a WAF can block
The publicly known exploit is, at its core, one unauthenticated multipart POST. It carries a PHP_SESSION_UPLOAD_PROGRESS field and a file upload to reach the upload-progress handler, plus a large number of form fields that spray the heap and reclaim the freed memory. That shape is visible at the HTTP layer, so it can be stopped before the request ever reaches PHP.
We added a set of standalone virtual-patch rules to the OWASP CRS engine in Yeap WAF. They recognize the combination of the upload-progress trigger and the heap-spray pattern the exploit depends on, and return 403 before PHP ever parses the request.
- They block on their own. They do not depend on anomaly scoring, so a site's paranoia level or score threshold does not weaken them.
- They look at behavior, not a fixed signature. Renaming fields or reshaping the payload does not get around them.
- They leave real uploads alone. Legitimate upload forms, including ones with progress bars, keep working.
- They run at the edge. Nothing to install on your origin, not a single config line to change.

From advisory to protection on every node

Before release, we validated the rules against the attack traffic shapes and several kinds of legitimate uploads: every attack shape was blocked and every legitimate upload went through. The rules were then compiled into our CRS bundle as a plugin and went live on all edge nodes at 23:34 (UTC+8) on October 21. In the early hours of October 22 we upgraded the whole bundle to OWASP CRS v4.30.0, and the virtual patch shipped with it.
That speed comes from the Yeap WAF update pipeline. A new bundle is compiled and checked in the background, then goes to canary nodes, where we watch load status and block rate. Only a healthy bundle rolls out everywhere. Edge nodes swap rules atomically: the old rules keep running until the new ones are ready, so there is never a gap in protection, and we can roll back to the last good version with one click.
A WAF is not a patch: what you still need to do
To be clear, a WAF can only stop the part of the attack that travels in the HTTP request body. CVE-2026-109619 also has producers and consumers that never touch multipart, such as fatal_error_backtraces on PHP 8.5 or INI warnings at startup, and the free and the use can be split across two requests that each look perfectly normal. Those can only be fixed inside the PHP engine. Timing-based variants such as slow POSTs are also not something a content WAF can see.
Treat the edge rules as a first line of defense that buys you time, and work through this list as soon as you can:
- Upgrade PHP to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 or later. According to the advisories, this is the only complete fix.
- Tighten php.ini before you upgrade, then restart every PHP worker:
; Turn this off if you don't use upload progress bars ; (the advisory recommends keeping it off even after patching) session.upload_progress.enabled = 0 ; PHP 8.5: removes the confirmed RCE producer fatal_error_backtraces = 0 ; Conservative body and input limits post_max_size = 8M max_input_vars = 1000 max_input_nesting_level = 16 - On PHP-FPM, consider
pm.max_requests = 1. Each worker is recycled after one request, which breaks the cross-request reuse. It costs performance, so use it as a short-term emergency measure. - Reject multipart where you don't need uploads. In the Yeap CDN console, go to Firewall → Custom rules and build a block rule from "method is POST", "request header Content-Type contains multipart/form-data" and your paths, allowing only the endpoints that really accept uploads.
- Rate-limit POST requests to slow down scanning and repeated probing.
To check your environment:
php -v
php -i | grep -E "session.upload_progress.enabled|fatal_error_backtraces"
The CLI and the web server may load different php.ini files. The most reliable check is a temporary phpinfo() page on the site itself. Delete it as soon as you've read it.
Protect your PHP sites with Yeap CDN and WAF
PHP2Shell won't be the last critical PHP bug. There is always a window between an advisory going public and every server being patched, and attackers usually start scanning within hours. Put your site behind Yeap CDN and we hold that window for you at the edge:
- Managed rules that stay current: the full OWASP CRS v4 rule set across 14 categories, including SQL injection, XSS, remote command execution, PHP injection and webshells. You switch categories on and off instead of tuning individual rules, and new upstream releases are built, canaried and rolled out automatically.
- Virtual patches for critical bugs: like this time, targeted rules go live at the edge on the day of the advisory, with zero changes on your origin.
- A hidden origin: attackers only ever reach the edge. Allow only CDN traffic to your origin and the direct attack surface shrinks to almost nothing.
- Custom rules and rate limiting: combine IP, country, path, header and method to block, challenge or rate-limit traffic.
- Free HTTPS and global acceleration: certificates are issued and renewed automatically, and edge nodes across multiple regions answer close to your visitors.
Onboarding takes a few minutes: add your domain, point its DNS record at the CNAME Yeap CDN gives you, and turn on protection in the domain's WAF settings. See Put Your Website Behind Yeap CDN with Free HTTPS in 10 Minutes for a step-by-step guide, and Yeap CDN and Security for product details.
We are enabling the OWASP CRS managed rules engine domain by domain. If you want this PHP2Shell virtual patch on your site right away, open a support ticket in the console and we'll turn it on for your domain.
References
Build on Yeap Cloud
Free DNS and CDN plans for every new account.
Keep reading
How Our DNS Picks Nameservers for Your Zone
Every zone hosted on Yeap DNS gets a pair of nameservers. That pair has to be stable, spread out, and actually able to answer. How our hash-based allocation works, and why "status is intent, heartbeat is fact".
TLS Session Resumption and HTTP/3 on the Edge
Session resumption lets returning visitors skip a full TLS handshake. Why ticket keys have to survive restarts, how we rotate them safely, and how HTTP/3 on our edge reuses the same request pipeline.
A Practical Guide to Cache Purge: From Cache Keys to Variant-Level Purging
Purged the cache but still getting hits? Most of the time the answer lies in the cache key. How Yeap CDN builds cache keys, how query-string modes work, and exactly what a URL purge removes with and without a query string.