PHP Ships Two Critical Fixes Tomorrow. Here's the Night Before.

PHP has released 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to fix two critical remote code execution vulnerabilities. What the advisories confirm, where distribution packages stand, and how to patch, verify and review.

Update, October 21, after the release: This article was written the night before PHP's scheduled security release. The original title and URL are preserved, and the rest of the post now reflects PHP's published advisories. The short version: the patches are out, there is no complete workaround, and you should update now.

The Patches Are Out

PHP released 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on October 21. They fix two critical vulnerabilities, both rated 9.2 on CVSS v4.0, that can allow unauthenticated remote code execution:

  • CVE-2026-109618, RCE via OOM corruption. PHP's out-of-memory handling can interrupt a request while internal data is only partly initialized, and request cleanup can then treat that data as valid.
  • CVE-2026-109619, RCE via stale global state. State owned by one request can outlive its memory, and a later request on the same worker can use it before normal initialization resets it.

PHP says every installation serving web requests is at risk: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. It considers mod_php the highest risk, because there the attack was demonstrated with an unauthenticated request before any application code ran. PHP's own words on workarounds: "There are no complete mitigations, updating is the only safe choice."

If you're on 8.1 or older, PHP no longer ships releases for you. It did publish minimal source backports for 7.4, 8.0 and 8.1 for people who build from source, and it's pointing everyone else at their distribution or LTS vendor.

What I Got Right, and What I Didn't

The night before, the strongest public read was that the bugs lived in session upload progress and multipart handling. I passed that along as a hypothesis and told you to turn upload progress off. That call was part right and incomplete.

Session upload progress turned out to be one of two confirmed ways into CVE-2026-109619. Multipart parsing turned out to be one of the ways to drive CVE-2026-109618. But neither bug is limited to upload progress. Turning it off blocks confirmed session paths for CVE-2026-109619; it does not fix that vulnerability or touch the broader OOM problem. That's the line.

A few things that circulated before the release still aren't confirmed by PHP, including the claim that every version back to PHP 5.4 is affected. I'd stop repeating that until PHP says it.

Where Your Packages Stand

PHP says the Linux distributions and LTS vendors got the patches in advance. That doesn't mean your package is out yet. Distribution packages also tend to backport fixes without changing to the upstream version number, so look up the CVE in your vendor's tracker instead of eyeballing the version string.

Here's where things stood when I checked every vendor's own tracker or repository, between 14:20 and 14:35 UTC on October 21:

Vendor Status
Ubuntu 22.04, 24.04, 26.04, 26.10 Fixed (USN-8948-1), backported without changing to the upstream version
Ubuntu 20.04 and older (ESM) Needs evaluation
Debian 11, 12, 13 Nothing published yet
Sury's Debian repo Fixed upstream versions published
Sury's Ubuntu PPA Not yet
Red Hat 8, 9, 10 Nothing published yet
AlmaLinux, Rocky Linux Nothing published yet
Oracle Linux Couldn't verify
Amazon Linux 2 and 2023 Nothing published yet
SUSE Nothing published yet
Fedora 43, 44 Not yet
Remi In progress; fixed builds are appearing, but my EL8 box wasn't offered one yet
Alpine Nothing published yet
Arch Fixed (php 8.5.12-1)
Docker official php image Fix merged; new tags not on Docker Hub yet
Windows builds from php.net Fixed builds posted
Homebrew Update pending
cPanel EasyApache 4 Nothing published yet
Plesk Fixed for Linux PHP 8.2 to 8.5; 7.4 to 8.1 and all Windows pending
CloudLinux alt-php No fixed packages yet; Imunify360 rules announced
LiteSpeed lsphp No fixed packages yet

"Nothing published yet" means exactly that at the time I checked. It does not mean unaffected. This will change by the hour, so check your vendor before you trust this table.

Patch, Then Prove It

Patching PHP is easy. Finding all of it is not.

PHP isn't just the language your web team writes in. It ships inside things. It's under WordPress, Magento, WooCommerce, Drupal, Nextcloud and Moodle. It runs the admin console on a surprising number of appliances, monitoring tools and helpdesk platforms. It's on the shared hosting account marketing signed up for with a company card. It's on the intranet timesheet app a contractor built in 2014 that still runs on a VM nobody has logged into since the contractor left.

That last one is the one that gets you. Not the production site with a deployment pipeline and an owner. The thing with no owner.

Here's the order I'd work in:

  • Inventory from the outside in. Pull every internet-facing host from your attack surface data, DNS, firewall NAT rules, WAF and CDN, then search your RMM, EDR or software inventory for php, php-fpm, lsphp and httpd with mod_php. Don't trust X-Powered-By; plenty of sites suppress it.
  • Patch internet-facing systems first. Update the PHP that actually serves requests, which means the FPM, mod_php or LiteSpeed package, not just the command-line binary.
  • Replace the workers. Installing a package doesn't change processes that are already running. Restart the service, then confirm the running workers are on the fixed build through an existing authenticated health or configuration endpoint, or a controlled server-side check. Don't stand up a phpinfo() page to do it.
  • Chase the vendors. For appliances, control panels and managed hosting, you can't patch it yourself. Get a date in writing, and if the answer is "we're evaluating," treat the box as exposed.
  • Deal with end-of-life branches honestly. If it's on 8.1 or older, the options are a vendor or distribution backport, building from PHP's source backports, an emergency upgrade, or taking it off the internet until one of those happens. "We'll leave it and see" is also a choice. It's just the one the attackers are hoping you make.

If You Can't Patch Today

PHP published partial measures for systems that can't take the update immediately. They reduce exposure to specific attack paths. They do not make an installation safe.

  • Keep session.upload_progress.enabled = Off in php.ini, an FPM pool or server configuration, and restart every worker. PHP suggests leaving it off even after patching if you don't use the feature, and I've left it off on everything I run.
  • Set memory_limit = -1 where applications can't override it, and pair it with a container or cgroup limit so one request can't take the whole box down. Think hard before doing this on a small server.
  • Lower post_max_size (PHP suggests 8M without uploads, 32M with them), and at the proxy or WAF, limit request size, field counts, multipart parts, headers and nesting, including for requests without a usable Content-Length. If an application doesn't take uploads, block multipart requests to it.
  • On PHP 8.5, set fatal_error_backtraces = 0 and restart every worker.
  • Clean out invalid or deprecated per-request PHP settings that throw warnings, and fix attacker-reachable fatal errors and timeouts. Hiding the warnings doesn't help.

If something still can't be patched promptly, take it off the internet or restrict who can reach it until it can be. CERT-EU recommended the same before the release, and it still applies.

Is Anyone Getting Hit?

Here's what's public. Cloudflare's CTO said on October 21 that they were seeing early attacks emerging, and Cloudflare deployed WAF rules for the known vectors on every plan, including free, as a stopgap. Those rules only help if your origin accepts traffic exclusively from Cloudflare. Patchstack reported multiple active exploitation incidents from hosting partners, while its own telemetry shortly after the release showed only probing, with no hard evidence of breaches yet.

On October 20 I said I had credible private indications that this was already being exploited. I'm still not publishing locations, evidence or details. Those belong with the people handling the incidents.

What nobody can say yet is how many systems were actually compromised through these CVEs. Probing and attempts are confirmed. A body count isn't. Plan as if you could be in it.

What I Would Hunt For

Patch first. Then answer the question that patching can't: did somebody already touch us?

The advisories confirm attack paths that may leave no upload-progress or multipart trace at all, so behavior after the fact matters more than request shape. Look for PHP workers spawning shells, interpreters or download tools, worker crashes and restarts, clusters of 5xx errors, repeated out-of-memory and fatal errors on reachable routes, new or modified files in the web root, and outbound connections from the PHP service account that shouldn't exist. None of these proves anything alone. Correlate them.

I put together a small YARA set before the release. It hunts the publicly circulated research artifacts, upload-progress session records, retained multipart request bodies and command-payload strings in session files. It does not comprehensively detect either CVE. PHP has confirmed other paths that may leave none of these artifacts.

The confidence levels matter. The exact hash is high-confidence, but only for one unchanged archive. The unpacked-tooling strings are medium-confidence leads, since documentation and copied snippets can match them too. The session and request-body rules are low-confidence signals that cover part of one CVE's surface. And strings like system(, passthru or /bin/sh in a session file are potential command-payload residue: they may be an attempted or staged payload that never ran. Scan copied session storage, retained and decrypted request bodies and backups, not live session files, and exclude your rule and documentation folders so you don't alert on your own copies.

Preserve reverse-proxy, WAF, web-server, PHP-FPM and host telemetry, plus process trees and worker crash history. Those records are how you answer whether this touched your environment.

These rules cover the published artifacts and a few useful upload-progress and multipart traces. They do not cover every way either bug can be reached. A clean scan does not clear the host, and a match does not prove code ran.

Download the ruleset (php-upload-progress-prepatch-defense.yar)

/*
 * Defensive telemetry originally published before the 2026 PHP emergency release.
 * Post-release advisories confirm broader OOM and stale-state paths that these
 * rules do not comprehensively detect.
 *
 * Scope:
 * - Scan UNPACKED files and retained, decrypted HTTP request bodies.
 * - These rules are alerts/hunts, not a compensating control for a PHP-core flaw.
 * - Do not scan live session files in place if the scanner can lock or alter them.
 */

import "hash"

rule PHPUP_Staged_Bundle_Exact_Hash
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "high"
        purpose = "Detect the exact staged archive identified in public reporting"
        limitation = "Hash only; renamed or modified variants will not match"
    condition:
        filesize > 30MB and filesize < 50MB and
        hash.sha256(0, filesize) == "0c254d547cac650bfbcb2275e30c2083737520a51a0511646f1fbb7ed60a0d63"
}

rule PHPUP_Staged_Tooling_Unpacked
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "medium"
        purpose = "Hunt unpacked staged-tooling files or documentation"
        limitation = "Compressed tarballs must be unpacked before string scanning"
    strings:
        $file1 = "php_up_framework.py" ascii wide
        $file2 = "poc0_109618.py" ascii wide
        $file3 = "poc1_109619.py" ascii wide
        $file4 = "poc_trigger.py" ascii wide
        $mark1 = "DELTAS_LSphp" ascii wide nocase
        $mark2 = "PHPUP_FILES" ascii wide
        $mark3 = "php_session_rfc1867_callback" ascii wide
    condition:
        filesize < 10MB and (2 of ($file*) or (1 of ($file*) and 1 of ($mark*)))
}

rule PHPUP_Session_Progress_Record
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "informational"
        purpose = "Locate persisted upload-progress records for incident scoping"
        limitation = "Matches normal use of the feature; supports common PHP serializers only"
    strings:
        $php_session = /upload_progress_[^|]{0,256}\|a:[0-9]{1,10}:\{/ ascii
        $php_serialize = /s:[0-9]{1,10}:"upload_progress_[^"]{0,256}";a:[0-9]{1,10}:\{/ ascii
        $field1 = "bytes_processed" ascii
        $field2 = "content_length" ascii
        $field3 = "start_time" ascii
        $field4 = "files" ascii
    condition:
        filesize < 4MB and (any of ($php_*) and 2 of ($field*))
}

rule PHPUP_Session_Progress_Potential_PostCompromise
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "medium"
        purpose = "Triage session artifacts that contain progress data and command-payload strings"
        limitation = "May be an attempted or staged payload; a match does not prove execution or compromise"
    strings:
        $progress = "upload_progress_" ascii
        $cmd1 = "system(" ascii nocase
        $cmd2 = "shell_exec" ascii nocase
        $cmd3 = "proc_open" ascii nocase
        $cmd4 = "passthru" ascii nocase
        $cmd5 = "/bin/sh" ascii
        $cmd6 = "/dev/tcp/" ascii
        $cmd7 = /(curl|wget)\s+-?[a-zA-Z]*\s*https?:\/\// ascii
    condition:
        filesize < 4MB and $progress and 1 of ($cmd*)
}

rule PHPUP_Multipart_Progress_Small_File_Hunt
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "low"
        purpose = "Prioritize retained multipart requests that exercised upload progress with a very small file part"
        limitation = "Use for hunt/correlation only; legitimate upload-progress forms can match"
    strings:
        $disposition = "Content-Disposition: form-data" ascii nocase
        $progress = "name=\"PHP_SESSION_UPLOAD_PROGRESS\"" ascii nocase
        $file_part = "filename=" ascii nocase
    condition:
        filesize < 4096 and all of them
}

rule PHPUP_Multipart_Progress_Any
{
    meta:
        author = "Cody Kretsinger"
        date = "2026-10-20"
        confidence = "informational"
        purpose = "Baseline all retained use of PHP upload progress"
        limitation = "Never use alone as a block or high-severity alert"
    strings:
        $progress = "PHP_SESSION_UPLOAD_PROGRESS" ascii nocase
    condition:
        $progress
}

How It Unfolded

All times UTC.

  • October 20: PHP announces a critical security release for the next day at 14:00 UTC. The list announcement goes out at 19:33. BleepingComputer reports the CVE identifiers at 20:56, with watchTowr saying it believed it had reproduced one of the bugs and hadn't seen exploitation. I post the first version of this article that night.
  • October 21, 02:13: PHP core developer Gina Peter Banyard says the only mitigation is patching.
  • 07:34: Patchstack reports active exploitation incidents from hosting partners.
  • 07:43: Cloudflare's CTO reports early attacks and deploys WAF rules for all plans.
  • 13:34: PHP publishes the two security advisories.
  • About 13:52: PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Arch and Sury's Debian repository ship fixed packages within minutes.
  • 13:57: Patchstack says its own telemetry shows probing but no hard evidence of breaches yet.
  • 14:09: Plesk marks its Linux PHP 8.2 to 8.5 packages patched. Ubuntu publishes USN-8948-1 the same day.
  • 14:17: NVD publishes both CVEs. As of 14:20, neither is in CISA's Known Exploited Vulnerabilities catalog.

PHP runs a big chunk of the web, and most organizations still couldn't tell you where all of theirs is. The patches are out, and the probing already started. Go find your PHP.

Sources: PHP: Security advisory, update now · PHP: CVE-2026-109618 advisory · PHP: CVE-2026-109619 advisory · PHP: Upcoming Critical PHP Security Release · Cloudflare CTO Dane Knecht on X · Patchstack: PHP major vulnerability alert · Huntress: Rapid Response, php2shell · CERT-EU Security Advisory 2026-018 · Gina Peter Banyard (PHP core developer) on Mastodon · BleepingComputer: PHP warns admins of two critical flaws · Sansec: Critical PHP RCE · @acd421 on X: php-up-2026 root-cause hypothesis

SHARE
FOLLOW ON LINKEDIN RSS