新用戶新用戶即享 DNS 與 CDN 免費方案。建立帳號

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.

PHP2Shell attack chains: an OOM bailout that leaves half-initialized memory trusted, and stale global state reused across requests as a use-after-free
Both bugs live in the Zend engine's request lifecycle, not in a single extension.

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, and fatal_error_backtraces on PHP 8.5.
  • Consumers (requests that read it): INI diagnostics applied before request startup, and session.upload_progress handling, 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

BranchAffectedFixed in
PHP 8.5< 8.5.128.5.12
PHP 8.4< 8.4.278.4.27
PHP 8.3< 8.3.368.3.36
PHP 8.2< 8.2.358.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.
Yeap CDN edge protection layers: TLS termination and hidden origin, PHP2Shell virtual patch, OWASP CRS managed rules, rate limiting and blocklists
Exploit attempts stop at the edge. Normal traffic goes on to your origin.

From advisory to protection on every node

Timeline: advisories published October 21, rules validated and live on all edge nodes at 23:34 the same day, shipped with the OWASP CRS v4.30.0 bundle on October 22

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:

  1. 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.
  2. 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
    
  3. 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.
  4. 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.
  5. 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

在 Yeap Cloud 上打造

新用戶即享 DNS 與 CDN 免費方案。

免費註冊