ba-cybersecurity.com

PHP's two critical RCEs (CVE-2026-109618, CVE-2026-109619): what to do tonight, and what a WAF can and cannot do

On 21 October 2026 the PHP project released PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. They fix two vulnerabilities that the project rates critical (CVSS v4.0 9.2) and that "can allow unauthenticated remote code execution". Details had leaked the day before, so many teams have already been waiting for it. I run WAFs in front of PHP applications, and this note is what I am doing tonight and what I am telling the people who own those applications. It is written from the official advisories as published at 13:34 UTC; if they change, I will update this page.

What the advisories say

The PHP project's own wording is the place to start: "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed" are at risk, and the risk is "highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs."

  • CVE-2026-109618, RCE via OOM corruption. When PHP runs out of memory while it is still setting up a request, it can leave request data half-initialized, and the cleanup that runs afterwards treats that data as valid. The advisory lists ways an attacker can force the out-of-memory condition with a multipart request that stays within the configured limits: a body without a usable Content-Length, a part with a very large number of headers, and many empty file fields with nested names. With mod_php this all happens during request startup, before your code is loaded. Full RCE was validated against mod_php with default limits. For FPM the project says source analysis supports susceptibility, though no complete FPM exploit was demonstrated.
  • CVE-2026-109619, RCE via stale global state. A request that ends in a fatal error can leave a worker holding pointers into memory that has already been freed. A later request to the same worker can use that stale state before PHP resets it. Two early "consumers" are confirmed: warnings raised while applying per-request INI settings, and session upload progress, which is enabled by default and does not need the application to call session_start(). RCE was validated against mod_php, and the session path also against FPM.

Both advisories say the same thing in bold: updating is the only complete mitigation.

Step 1: find every PHP that answers web requests

This is where I expect most of the work to be, not in the update itself.

  • Do not trust php -v alone. It reports the command-line binary. The PHP that serves the site can be a different package, a different version, or inside a container image. Check what the web server actually loads (the Server API and version on a phpinfo() page you then delete, the FPM binary path, the image tag).
  • Note the SAPI. mod_php is the highest-risk case in the advisory. If you still run Apache with mod_php, put that host first. Installed is not the same as serving: a host can have both Apache and PHP installed without loading the PHP module. httpd -M (or apachectl -M) shows which one it is.
  • Distribution packages may keep their old version number. The advisory says Linux distributions and LTS vendors received the patches in advance. A backported package can fix the bugs without showing 8.x.y in its version string, so check your vendor's advisory rather than comparing version numbers.
  • If you install from rpms.remirepo.net, plan for a delay. The advisory says that repository "will not have updates today" and urges its users to switch to distribution packages or build from source.
  • PHP 8.1 and older get no official release. The PHP security team published minimal source backports for 7.4, 8.0 and 8.1 as a stopgap. For 7.3 and older there is neither a release nor a backport, and a third-party repository that has stopped publishing for an end-of-life operating system will not deliver one either. If you find PHP 5.x or 7.0 to 7.3 serving web requests, the settings below and an upgrade plan are what you have.
  • Count the PHP you did not install. Appliances, hosting control panels and vendor products embed PHP. Ask the vendor.

Step 2: update, then restart every worker

Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your vendor's fixed package. Then restart every PHP worker. Both bugs live in long-running worker processes, and a worker that started before the update is still running the old code.

If you cannot update tonight

The advisories list configuration changes that "reduce exposure to specific attack paths but do not make an installation safe". Set them in php.ini, the FPM pool or the server configuration, not with ini_set() or .user.ini, and restart every worker.

SettingWhat it blocksWhat it does not block
session.upload_progress.enabled=0The confirmed session path of CVE-2026-109619Other stale-state variants
fatal_error_backtraces=0 (PHP 8.5)The fatal-backtrace path used in the confirmed mod_php RCEOther request state
memory_limit=-1, enforced so applications cannot override itThe controllable out-of-memory trigger of CVE-2026-109618Underlying allocation failures
Low post_max_size (8M without uploads, 32M with)Large request bodies reaching PHPRequests within the limit; a multipart body without a usable Content-Length can pass the initial check

Two warnings from the advisories are easy to miss. First, hiding errors does not help: error_reporting, display_errors and @ are checked after the vulnerable state is already touched. Second, do not use RLIMIT_AS or RLIMIT_DATA as a memory cap; they cause exactly the allocation failure that CVE-2026-109618 needs. I would also keep session.upload_progress.enabled=0 after patching if nothing uses it; the advisory itself suggests this.

Removing memory_limit trades one risk for another: a single request can now use much more memory. Put a container or cgroup memory limit around the workers, as the advisory suggests, and remove the setting once you have updated.

What a WAF can do here, and what it cannot

People ask me whether the WAF covers this. My honest answer is that it can narrow the paths, but it cannot fix a memory-safety bug in the interpreter.

What it cannot do. CVE-2026-109619 needs a fatal error in one request and a trigger in the next. The fatal error can come from any part of the application, and the requests involved can look ordinary. There is no request pattern that means "this will run out of memory in PHP". Also, a WAF only sees part of the body. AWS WAF inspects the first 8 KB of a request body behind an Application Load Balancer, and 16 KB by default (up to 64 KB) behind CloudFront and API Gateway. The documentation is explicit that when a request is allowed, "the entire web request is sent, including any contents that are outside of the count and size limits". The advisory's out-of-memory strategies depend on bodies that become large inside PHP, so the interesting part is likely to be beyond what the WAF reads.

What it can do. The advisory gives frontend measures, and some of them fit a WAF well:

  • Block multipart requests where the application takes no uploads. The advisory says: "If the application does not use multipart form data, block such requests at the frontend before they reach PHP." In a WAF this is a rule on the Content-Type header (lowercase it first, then match multipart/form-data), scoped to the paths that do not accept uploads. It is cheap and closes the confirmed pre-application path for those paths. Decide which paths those are from your logs, not from the page design: JavaScript that posts a FormData object sends multipart/form-data even when no file is attached, so search, list and report endpoints can receive multipart requests too.
  • Block oversize bodies where large bodies are not expected. AWS WAF lets you block requests whose body is larger than the inspection limit (a size constraint with oversize handling set to Match). This breaks uploads, so scope it the same way.
  • Watch, then block. I put new rules in Count first, read what they would have caught, then switch them to Block. For a change tonight I would keep that step short, but I would not skip it on paths that take uploads.

What belongs in front of the WAF or behind it. The advisory also asks for limits on decoded request size, the number of input fields, multipart parts, headers per part and nesting depth, including for requests without a usable Content-Length. A managed WAF that reads only the first kilobytes cannot enforce these. They belong in the reverse proxy or web server that actually reads the whole request.

If your WAF vendor ships a rule for these CVEs, check which mode it is in and which paths it covers before you count on it. I track vendor rule publications at WAF Rule Watch.

What to look for in the logs

The CVSS vector rates attack complexity as high, and the advisory notes that other workers and traffic "increase the required number of attempts". An attempt is therefore likely to be noisy. What I look for:

  • Bursts of multipart POST requests from one source, especially to paths that never take uploads.
  • PHP fatal errors, in particular Allowed memory size ... exhausted and Maximum execution time exceeded, and PHP workers that crash or are restarted.
  • Responses that end in a 5xx or a reset connection right after such requests.

The advisory adds a caution I agree with: a clean error log is not proof of safety, because a failure can happen before PHP writes the message.

Update, 22 October: a reproducibility gap I have not fully explained

Since publishing this, I tested CVE-2026-109618's confirmed OOM trigger myself: a POST with no Content-Length header, sent as Transfer-Encoding: chunked, carrying a single multipart field large enough to pass post_max_size and exhaust memory_limit.

Against a bare PHP install — mod_php or PHP-FPM, no application in front — this reproduces every time. The worker dies with Allowed memory size ... exhausted before any application code runs, exactly as the advisory describes.

Against several real applications sitting behind a CDN and a load balancer — their normal production path, not a bare install — the same request, byte for byte, did not reproduce it. The body arrived at PHP intact and the application answered normally; no crash, no 5xx, no interruption to the next request. I tried this against more than one application and confirmed it repeats.

I do not have a confirmed explanation, and I want to be clear about what I am not claiming. My best guess is that the CDN or load balancer buffers the chunked body and forwards it to the origin with a computed Content-Length, which would remove the condition this particular technique depends on. But I checked the published documentation for the CDN and load balancer behaviour involved and could not find it stated outright, and a quick attempt to isolate which layer was responsible (hitting the load balancer directly, bypassing the CDN) gave an inconsistent result on one of the two applications I tried it against, so I am not confident I have pinned down the mechanism, let alone that it is reliable on purpose rather than as a side effect of some other limit (body size, timeout) that a different request shape could evade.

What I would not conclude from this: that a CDN or load balancer in front of your PHP is a mitigation. I checked one of the advisory's OOM strategies, against a handful of applications, over a short window. The advisory lists others I did not retest this way, CVE-2026-109619 is a different bug with a different trigger, and the advisory's own position stands: updating is the only complete fix. If you run PHP behind a CDN or load balancer and want to know whether this applies to you, the honest answer from what I have seen is "maybe, and I could not tell you why" — which is not a basis to skip patching or the settings above. I am noting it because an unexplained gap between a lab result and a production path is worth knowing about, not because it changes what to do tonight.

Exploitation status

Update, 22 October, about an hour later: several vendors now report attack traffic, but I still cannot point to a single independently verified, successful compromise.

  • watchTowr says its own honeypot network (Attacker Eye) observed exploitation attempts against both CVEs as of 21 October. A separate watchTowr-attributed page says the opposite, that it has not observed exploitation — the two statements from the same vendor do not agree, which I mention because it is a sign of how fast and thin the reporting is right now, not because I know which one is right.
  • Hadrian reports attacks "at scale," mostly through the session-upload-progress path confirmed in the CVE-2026-109619 advisory.
  • Patchstack and Aikido both say they have confirmed active exploitation incidents, without publishing technical detail.
  • Cloudflare says it has observed attack traffic against its customers; it does not claim any customer was compromised.
  • Against that: PHP's own advisory does not claim in-the-wild exploitation. CISA's alert on this pair says it has not observed exploitation as of publication, and neither CVE is in CISA's Known Exploited Vulnerabilities catalog. A proof-of-concept and a tool calling itself "PHP2Shell" are circulating, with unverified claims of working against PHP 5.4 and later.

Read "exploitation attempts reported" and "systems compromised" as two different claims. Right now I only have good sourcing for the first one. Do not run any of the circulating exploit code against your own servers to test this; unverified code from an unknown source can carry its own payload. Patch on the advisory's own terms, not on how loud the exploitation reporting is.

The short version

Update now and restart the workers. Find the PHP you did not know about. Where an update is not immediately possible, apply the settings above and treat them as a stopgap. Check whether your WAF vendor has shipped a rule for these CVEs and apply it, and put the size and count limits in the proxy that reads the whole request.

Disclosure

This article was drafted with an AI assistant (Claude). The practices described are my own, from running these systems in production. Product statements were checked against the documentation listed under References. I reviewed and approved the final text and am responsible for it.

References

  1. PHP, Security advisory: Critical remote code execution vulnerabilities, update now (2026-10-21): https://www.php.net/archive/2026.php#2026-10-21-5
  2. PHP, GHSA-w8hw-w9fx-j79w, RCE via OOM corruption (CVE-2026-109618): https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
  3. PHP, GHSA-6wmf-4rv5-p3rp, RCE via stale global state (CVE-2026-109619): https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
  4. Tenable, php2shell (CVE-2026-109618, CVE-2026-109619): Frequently asked questions: https://www.tenable.com/blog/php2shell-cve-2026-109618-cve-2026-109619-frequently-asked-questions-about-critical-php-remote
  5. AWS WAF Developer Guide, Oversize web request components: https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-oversize-handling.html
  6. PHP Manual, Session Upload Progress: https://www.php.net/manual/en/session.upload-progress.php