all systems operationalall operational

apenaloza.cl · bitácora

SCL (UTC−3) · v2026.10

~/blog/php2shell-cve-2026-109618-cve-2026-109619-mitigation.md

PHP2Shell: what CVE-2026-109618 and CVE-2026-109619 are and how to protect your PHP servers

Two critical PHP flaws allow unauthenticated remote code execution, before your application even runs. Which versions are affected, who is exposed, what Cloudflare's WAF covers, how to update and what to mitigate in the meantime.

author
Alejandro Peñaloza
Published
Reading
12 min
Portada de PHP2Shell: elefante de PHP hecho de líneas violetas, partido por una grieta roja con un prompt de terminal verde, junto a los CVE-2026-109618 y CVE-2026-109619
In this article
  1. Quick summary
  2. What PHP2Shell is
  3. Who is exposed
  4. Is it being exploited?
  5. What to do now: update
  6. If your site is behind Cloudflare
  7. Temporary mitigations (if you can't update today)
  8. Detection: what to look for on your servers
  9. Checklist
  10. References

Updated October 21, 2026 (afternoon): PHP published official backports for 7.4, 8.0 and 8.1 (explained below), expanded its mitigations (container memory limits, attacker-reachable fatal errors, requests without Content-Length) and clarified that memory_limit=-1 doesn't fully close the flaw. I also added the exploitation status according to Check Point and Patchstack.

On October 21, 2026, at 14:00 UTC, the PHP team shipped emergency releases fixing two critical vulnerabilities that the community has nicknamed PHP2Shell. Details leaked before the coordinated disclosure, public proofs of concept are circulating, and Cloudflare reports attack traffic against its customers. If you run any PHP server that handles web traffic, this is a today problem.

This post summarizes what official sources say, who is affected, how to update, what Cloudflare's WAF covers, and what to do if you can't update right away.

Quick summary

CVE-2026-109618 CVE-2026-109619
Official name RCE via OOM corruption RCE via stale global state
Severity Critical, CVSS v4.0 9.2 Critical, CVSS v4.0 9.2
Impact Unauthenticated remote code execution Unauthenticated remote code execution
PHP advisory GHSA-w8hw-w9fx-j79w GHSA-6wmf-4rv5-p3rp
NVD CVE-2026-109618 CVE-2026-109619

The leaked posts claimed CVSS 10.0; the official score is 9.2, just as critical in practice.

Fixed versions: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (official announcement).

PHP 8.1, 8.0 and 7.4: no new release, but the PHP security team published minimal source backports. I explain below what that means in practice.

PHP 7.3 and older: PHP's advisory doesn't mention them. Some reports say the flaw affects everything since PHP 5, without official confirmation: assume they are vulnerable and unpatched.

What PHP2Shell is

This is not a framework or library bug: it lives in the PHP engine itself, in how it handles memory and per-request state. That is why it affects Laravel, Symfony, WordPress and plain PHP alike.

CVE-2026-109618: corruption after running out of memory

According to PHP's advisory, when PHP runs out of memory (OOM) it can be interrupted while internal request data is only partially initialized. Request cleanup may then treat that invalid data as valid. Parsing of multipart/form-data requests is named as one of the triggers.

CVE-2026-109619: global state that outlives the request

Some request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it. The confirmed paths go through session.upload_progress and, on PHP 8.5, through fatal error backtraces.

Both were fixed in the core with a single change, described in the changelog as "Avoid state corruption via bailout".

Who is exposed

PHP says the risk applies to every installation serving web requests:

  • PHP-FPM (behind Nginx, Caddy, etc.)
  • Apache with mod_php
  • php-cgi
  • FrankenPHP
  • LiteSpeed

PHP rates the risk highest for mod_php, where there is a demonstrated exploit in which an unauthenticated request triggers the attack before any of your application code runs. In other words, it doesn't matter how well written your code is, or what authentication middleware you have.

Command-line scripts (php CLI, queues, cron) don't serve web requests and are not listed as a vector, but it's the same binary: when you update, update everything.

Is it being exploited?

Sources still don't agree, so here they are as reported:

  • Cloudflare says it observed attack traffic targeting its customers, with attackers mutating the payload to try to bypass its rules (Cloudflare blog).
  • watchTowr reports exploitation observed since October 21 in its honeypot network (FAQ).
  • Patchstack also reported active exploitation, without publishing details.
  • Check Point has no reports of in-the-wild exploitation so far (Check Point blog).
  • NCSC-NL has no indication of active exploitation, but confirms public proofs of concept exist (repository).
  • CISA KEV: at the time of this update, neither CVE is in the catalog.

With a public exploit and a flaw that needs no authentication, assume mass scanning has already started.

What to do now: update

It is the only complete fix. Each release announcement says it plainly: there are no complete mitigations, updating is the only safe choice. First check which version is actually running (CLI and FPM can differ):

php -v
php-fpm -v

Depending on the distro, the binary may be called php-fpm8.3 or similar. In a container, this tells you what is serving web traffic:

docker compose exec app php -r 'echo PHP_VERSION, PHP_EOL;'

You need 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or later within your branch.

Debian and Ubuntu

sudo apt update
sudo apt install --only-upgrade 'php8.3*'
sudo systemctl restart php8.3-fpm

If you use mod_php, restart apache2 too. Linux distributions and LTS vendors received the patches in advance, so their packages should be available. If you use your distribution's packages (rather than Ondřej Surý's PPA or similar), the version number may not match php.net because distros backport the patch onto their own version. In that case, check your distro's security advisory to confirm the installed package covers CVE-2026-109618 and CVE-2026-109619.

RHEL, Rocky, Alma and Fedora with remirepo

If you rely on rpms.remirepo.net, be careful: it is volunteer-maintained and won't have the update on October 21 itself. PHP recommends switching to distribution packages or building from source so you can patch immediately.

Docker

If your image is based on php:8.x-fpm or similar, restarting is not enough: you have to rebuild on the new base image and redeploy. Pin the minimum safe patch instead of a floating tag:

FROM php:8.3.36-fpm
docker compose build --pull --no-cache
docker compose up -d
docker compose exec app php -v

--pull forces Docker to fetch the new base image; without it, Docker may reuse the cached one.

Shared or managed hosting

Check your provider's dashboard or status page: many are already updating, but confirm the version with phpinfo() on a temporary route (and delete it afterwards).

PHP 8.1 or older: what the "backports" mean

PHP's advisory says two things that look contradictory at first:

PHP 8.1 and older no longer receive updates from the PHP project. Some distributions and LTS vendors provide backported fixes, so contact your vendor for details, or upgrade to a supported version.

Given the severity of these vulnerabilities, the PHP security team provided minimal source-backports for PHP 7.4, PHP 8.0, and PHP 8.1 to help people building from source or without access to an LTS vendor.

They don't contradict each other; they talk about different things.

The first sentence says PHP 8.1 and older are end of life: the PHP project won't ship new releases for those branches. There will be no new "PHP 8.1.x" on php.net, no Windows binaries, and no updated official Docker images. For those versions it points you to your distribution or extended support vendor (Debian, Ubuntu Pro, Red Hat, TuxCare, etc.), which may maintain older versions and ship patched packages, or to upgrading to a supported version.

The second sentence is an exception because of the severity. It's not a new release either: it's only the source code patch, the set of lines to change in PHP to fix these two flaws.

  • "Minimal" means it fixes only PHP2Shell. Those versions still carry every other vulnerability discovered since they went end of life.
  • "For people building from source" means it helps if you compile PHP yourself: you download the PHP 8.1 source, apply the patch and build.
  • "Without access to an LTS vendor" means it's meant for people who don't have a vendor building the package for them.

In practice:

If you run PHP 7.4, 8.0 or 8.1… What to do
Installed with your distro's apt or yum Check whether your distro shipped a patched package. If not, PHP's patch won't help you directly.
Official php:8.1 Docker image It won't be updated. Upgrade, or build your own image applying the patch.
Shared hosting It depends on the provider: ask them to update or move you to a newer version.
You compile it yourself Apply the backport and rebuild.

Your options, in order of preference:

  1. Move to a supported branch: that's the real fix.
  2. Use a patched package from your distribution or an extended support vendor, if one exists.
  3. If you build PHP 7.4, 8.0 or 8.1 yourself, apply the minimal backports from the PHP security team.
  4. Apply the mitigations below and isolate the server from the Internet. CERT-EU recommends taking unpatchable internet-facing applications off public networks (advisory 2026-018).

And if you upgrade, aim for 8.3 or later: PHP 8.2 also reaches end of life on December 31, 2026.

If your site is behind Cloudflare

Cloudflare took part in the coordinated disclosure alongside the PHP Foundation, OpenAI and Wiz, and deployed WAF rules about 9 hours before public disclosure. Since October 21 at 03:02 UTC, customers using the WAF, including the free plan, have been protected by default (Cloudflare announcement).

Plan Where the rules live What to do
Free Cloudflare Managed Free Ruleset Nothing, they're on
Pro, Business, Enterprise Cloudflare Managed Ruleset Confirm the deployment is in Block mode
All Cloudflare Managed Response Firewall, rule Default block for High confidence Signatures Nothing, it's on

The rules are named Generic Rules - Anomalous Execution (1 to 7). What's new is that they use full transaction detection: the WAF analyzes both the request and the response of the same HTTP exchange, and on a match it can block your origin's response before it reaches the attacker. According to Cloudflare, this allows more accurate rules with fewer false positives than looking at the request alone.

To check whether you were targeted: Security > Security Analytics > Events, filtering by those rule IDs (listed in the announcement).

Two important caveats:

  • The WAF does not replace the patch. Cloudflare says so explicitly: the flaw is in the PHP runtime and the WAF is an extra layer. Attackers are already mutating payloads.
  • It only protects traffic that goes through Cloudflare. If your origin server accepts direct connections, an attacker who knows its IP bypasses the WAF. Restrict the origin to Cloudflare's IP ranges, or use Cloudflare Tunnel or Authenticated Origin Pulls.

Temporary mitigations (if you can't update today)

PHP's advisory is clear: these measures reduce exposure to known attack paths but do not make an installation safe. Gina Peter Banyard, from the PHP team, stressed the same point: disabling session.upload_progress is a partial mitigation that does not stop exploitation. They buy you hours, not a permanent solution.

Apply everything in php.ini, the FPM pool or the server configuration, never with ini_set() or .user.ini: they must take effect before PHP parses the request. And restart every worker afterwards.

Against CVE-2026-109619

session.upload_progress.enabled = 0

It is on by default and almost no application uses it (it powers session-based upload progress bars). This blocks the confirmed paths, not every variant.

On PHP 8.5, also add:

fatal_error_backtraces = 0

The updated advisory adds two more measures:

  • Remove invalid or deprecated settings applied per request that emit diagnostics before the request finishes initializing. Hiding those diagnostics with error_reporting, display_errors or @ does not help: you have to remove the setting that triggers them.
  • Fix attacker-reachable fatal errors and execution timeouts, including failures in callbacks, upload or import processing, and cleanup.

Against CVE-2026-109618

PHP recommends removing the per-script memory limit to avoid the controllable OOM condition that triggers the flaw, set where applications can't override it. In the FPM pool (for example /etc/php/8.3/fpm/pool.d/www.conf):

php_admin_value[memory_limit] = -1

In Apache with mod_php:

php_admin_value memory_limit -1

Make sure no other pool, directory or application file sets a finite limit again. And keep in mind what the advisory adds: this removes the failure caused by PHP's memory limit, but an underlying system allocation failure can still reach the vulnerable path.

With no limit, a runaway script can also eat all the server's RAM. For that, PHP recommends a container or cgroup memory limit as an availability boundary, ideally with a policy that kills only the affected worker. With Docker Compose, for example:

services:
  app:
    mem_limit: 1g

Combine it with request size limits:

post_max_size = 8M
upload_max_filesize = 6M

Use 8M if you don't accept file uploads and 32M if you do, always keeping upload_max_filesize below post_max_size.

PHP also suggests limiting, at the web server or proxy, the decoded request size, the number of fields and multipart parts, headers and nesting depth. Those limits must also cover requests without a usable Content-Length (for example, chunked transfers). In Nginx, client_max_body_size applies to those requests as well:

client_max_body_size 8m;
large_client_header_buffers 4 8k;

And don't use RLIMIT_AS or RLIMIT_DATA (for example via ulimit or systemd) to contain memory: they can trigger exactly the vulnerable allocation failure.

Check that the mitigation is live

CLI and FPM read different files, so check from the web context. A temporary route with:

<?php
var_dump(
    ini_get('session.upload_progress.enabled'),
    ini_get('memory_limit'),
    ini_get('post_max_size'),
);

Delete it as soon as you've confirmed.

Detection: what to look for on your servers

There are no reliable public signatures or indicators of compromise yet, so detection is behavioral. The suspicious pattern is a PHP process launching other things:

  • Parent processes php-fpm, php-cgi, httpd or apache2 starting sh, bash, python, perl, curl, wget or nc.
  • Command lines containing /dev/tcp/, fsockopen or mkfifo (typical of reverse shells).
  • Reconnaissance commands run by the web server user: whoami, id, uname -a.

To list PHP or Apache children that shouldn't exist:

ps -eo pid,ppid,user,cmd --forest | grep -E -A3 'php-fpm|apache2|httpd' | grep -E 'sh|bash|curl|wget|nc|python|perl'

And new PHP files in the web root over the last 3 days:

find /var/www -name '*.php' -mtime -3 -ls

If you find something: isolate the server, preserve web server and PHP logs before touching anything, and hunt for web shells, new files in the web root, cron jobs and new accounts. In MITRE ATT&CK this maps to T1190 (exploit public-facing application) and T1505.003 (web shell). Updating PHP on an already compromised server does not remove the attacker: if there was a compromise, rebuild from a clean image and rotate credentials.

Checklist

  • Inventory every server, container and hosting account running PHP (including the forgotten ones: staging, demos, the marketing WordPress).
  • Confirm the version serving web traffic, not just the CLI one.
  • Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or your distro's patched package).
  • If you use remirepo, switch to distro packages or build from source.
  • Rebuild Docker images with --pull and pin the minimum patch in the Dockerfile.
  • Restart FPM, Apache and every worker.
  • If you use Cloudflare: rules in Block mode and origin closed to direct connections.
  • Where you can't update today: session.upload_progress.enabled = 0, container memory and request limits, review attacker-reachable fatal errors, and isolate from the Internet.
  • Review PHP child processes and new files in the web root.
  • Plan your exit from PHP 8.2 (end of life December 31, 2026) and from 8.1 or older.

References

Written on October 21, 2026 and updated the same day with the information available. Things are moving fast: check the official advisories before making decisions.

share:linkedinx

Comments

No comments yet. Want to leave the first one?

Leave a comment

Comments are reviewed before they go live. I don’t ask for your email or store your IP.

$ ls ~/blog | grep related

Keep reading