PHP2Shell: Inside CVE-2026-109618 and CVE-2026-109619, and How to Patch Your Servers

A pair of critical PHP vulnerabilities, collectively known as the PHP2Shell vulnerability, leaked ahead of their coordinated disclosure this morning — if you run anything on PHP, this one belongs at the top of your patch queue.

What happened in the PHP2Shell vulnerability

On October 21, 2026, the PHP Foundation disclosed two critical vulnerabilities behind the PHP2Shell vulnerability:

  • CVE-2026-109618
  • CVE-2026-109619

Both are heap use-after-free bugs rated CVSS 9.2 (one security vendor, watchTowr, scored it a full 10.0). Disclosure leaked roughly 26 minutes before the planned release window, and researchers report active exploitation attempts beginning within hours.

Root cause

Both CVEs share a single underlying mechanism. When PHP hits a fatal condition — exhausting memory_limit, exceeding max_execution_time, or losing the client connection — it doesn’t unwind the call stack cleanly. Instead it raises a fatal error and calls longjmp() to abandon the current execution context outright. Any C function caught mid-operation while building a data structure is simply discarded, leaving corrupted memory behind.

Because this corruption happens while PHP is still assembling internal request variables — before extension initialization and before any application code runs — every reachable PHP endpoint is attack surface, including static forms, login pages, and health checks. The most actively exploited path is session.upload_progress.

Who is affected

  • All PHP installations that serve web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed.
  • A working exploit exists against mod_php configurations where an unauthenticated request can trigger the bug before application code executes.
  • End-of-life branches (PHP 8.1 and earlier, including 5.6, 7.x) will not receive an official fix. If you’re still running one of these, read the mitigation section below — it’s your only option until you migrate.

Patched versions (upstream PHP)

BranchPatched version
8.58.5.12
8.48.4.27
8.38.3.36
8.28.2.35

PHP’s own security team additionally published minimal source backports for the already-EOL 7.4, 8.0, and 8.1 branches — no official packaged release exists for them, so distro vendors (below) are the ones shipping a usable fix. Ondřej Surý’s Debian/Ubuntu PPA builds and Remi’s RHEL-family repository (see below) are currently the two most complete sources for those EOL backports; both describe theirs as a minimal mitigation patch — it disables upload-progress tracking and removes the hard memory_limit ceiling at the C level — rather than a full fix of the underlying use-after-free.

Important: most enterprise and LTS distributions backport the security fix into their existing package version rather than bumping to the upstream version number above. That means php -v may keep showing an older string (e.g. 8.2.24 or 8.1.2-1ubuntu2.21) even after you’re fully patched. Don’t use the version string to judge patch status — check your distro’s advisory/changelog instead.

How to update, by distribution

RHEL, AlmaLinux, Rocky Linux (8, 9, 10)

sudo dnf clean metadata
sudo dnf upgrade 'php*'
sudo systemctl restart php-fpm httpd

AlmaLinux shipped patched packages to its production repos (no testing repo needed) within hours of disclosure. Rocky Linux and Oracle Linux (via ELSA errata) are expected to follow the same backport pattern; check each project’s errata page directly, since the exact advisory ID wasn’t published at the time of writing — dnf updateinfo list security (AlmaLinux/RHEL) will show it once available.

If you installed PHP from Remi’s repository (common on RHEL-family boxes for non-default PHP versions), the package names are prefixed per version, e.g. php84-php-fpm:

sudo dnf upgrade 'php84-*'
sudo systemctl restart php84-php-fpm

Remi ships the same upstream 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 for those branches. For the EOL branches, Remi backported the mitigation patch into the existing package version rather than bumping it, so php -v will not show a new release number. Confirmed fixed Remi builds at the time of writing:

  • PHP 8.1 — 8.1.34-6
  • PHP 8.0 — 8.0.30-21
  • PHP 7.4 — 7.4.33-31

Remi has stated 7.2 and 7.3 backports are forthcoming. Verify any Remi-sourced package directly from the changelog rather than trusting the version string:

rpm -q --changelog php-common | grep CVE-2026-109618

Native Red Hat Software Collections / AppStream module builds (i.e. PHP installed from Red Hat’s own channels rather than Remi, EPEL, or SCL) are a separate question from Remi’s packages: at the time of writing, confirm your specific stream (7.4 on RHEL 8, 8.0/8.2/8.3 on RHEL 9) against Red Hat’s own errata before assuming it is covered, since not every native module had a published backport yet.

Debian

sudo apt update
sudo apt install php
sudo systemctl restart php-fpm apache2
  • Debian 12 (bookworm), branch 8.2 — fixed in 8.2.35-1~deb12u1 (DLA-4848-1)
  • Debian 13 (trixie), branch 8.4 — fixed in 8.4.27-1~deb13u1 (DSA-6574-1)

Ubuntu

sudo apt update
sudo apt upgrade
sudo systemctl restart php-fpm apache2

Per Ubuntu advisory USN-8948-1:

  • 14.04 (Trusty, ESM) — php5 fixed in 5.5.9+dfsg-1ubuntu4.29+esm18
  • 16.04 (Xenial, ESM) — php7.0 fixed in 7.0.33-0ubuntu0.16.04.16+esm22
  • 22.04 (Jammy) — php8.1 fixed in 8.1.2-1ubuntu2.28
  • 24.04 (Noble) — php8.3 fixed in 8.3.6-0ubuntu0.24.04.13

ESM-covered Trusty/Xenial fixes require an active Ubuntu Pro subscription with esm-infra enabled.

Mitigating the PHP2Shell vulnerability if you can’t patch immediately

The PHP project’s own guidance for the PHP2Shell vulnerability, and the approach administrators are using on unsupported hosts, comes down to two configuration changes.

1. Disable session upload progress

This closes off the most actively exploited path.

session.upload_progress.enabled = Off

2. Remove the attacker-controlled failure trigger

Force an unbounded memory limit so the longjmp()-on-OOM path can’t be forced by a crafted request. This must be set administratively so no vhost, .htaccess, or ini_set() call can override it back to a finite value.

Apache with mod_php — add to /etc/httpd/conf.d/php.conf:

php_admin_value memory_limit -1
php_admin_flag session.upload_progress.enabled Off

PHP-FPM — add to the relevant pool file (e.g. /etc/php-fpm.d/www.conf):

php_admin_value[memory_limit] = -1
php_admin_flag[session.upload_progress.enabled] = off

After editing either, restart the service so every worker picks up the change:

sudo systemctl restart httpd
# or
sudo systemctl restart php-fpm

Audit every pool, vhost, and application config for an overriding memory_limit value before considering this done — a single permissive override reopens the hole.

3. Disable fatal-error backtraces (PHP 8.5 only)

PHP 8.5’s fatal_error_backtraces feature can trigger the same use-after-free independently of upload progress. Turn it off administratively alongside the settings above:

zend.exception_ignore_args = On
fatal_error_backtraces = Off

4. Cap other parser-driven memory growth

memory_limit = -1 only removes the per-request ceiling exploited in the confirmed proof-of-concept; it does not stop a crafted request from exhausting system memory by other means. Tighten the request parser instead of leaving it unbounded:

max_input_vars = 1000
max_file_uploads = 20
max_input_nesting_level = 64

Do not substitute an OS-level cgroup or ulimit -v (RLIMIT_AS/RLIMIT_DATA) memory cap for the above. Those force the same allocation-failure-and-longjmp() path the vulnerability already relies on; an external memory ceiling can trigger the bug rather than prevent it.

Why the mitigations are not a substitute for patching

The configuration changes above are what the PHP project itself calls a stopgap, not a fix, and treating them as equivalent to patching is the most common mistake administrators are making with this incident:

  • They cover known paths, not the root cause. The underlying bug is memory corruption inside PHP’s own fatal-error teardown path (longjmp() abandoning a half-built C data structure). Disabling session.upload_progress and fatal-error backtraces closes the exploitation techniques that have been publicly demonstrated so far — it does not patch the use-after-free itself. PHP core developer Gina Peter Banyard has stated publicly that no combination of mitigations fully blocks the underlying flaw, and that the security releases are the only permanent fix.
  • Information disclosure survives every mitigation above. Independent of RCE, both CVEs can leak adjacent process memory — session tokens, API keys, database credentials, TLS private key material — without executing any attacker code. None of the configuration mitigations in this post address that leak; only the patched PHP binary does.
  • Setting memory_limit = -1 trades one incident for another. An unbounded memory limit removes the trigger condition today, but it also removes PHP’s own defense against a single runaway or malicious request consuming all available RAM. Once you’ve patched, remove the override and restore a normal, finite memory_limit — don’t leave -1 in place indefinitely.
  • The mitigation set grows as more exploitation techniques are confirmed. This post already lists four separate settings (upload progress, fatal-error backtraces, memory limit, parser limits) because researchers kept finding additional ways to reach the same corrupted-state bug after the first mitigation shipped. There is no guarantee the list is complete; a configuration-only defense is chasing a moving target.
  • Mitigations are per-host manual configuration; patches are not. A missed vhost, an .htaccess override, or a pool file that didn’t get the admin-value treatment silently reopens the hole with no error or warning. A package upgrade is idempotent and auditable with rpm -q --changelog or apt changelog in a way that five scattered ini directives are not.

Use the mitigations to buy time, confirmed by testing them against the specific exploitation techniques above — then patch on the same timeline you’d use for any other actively-exploited critical RCE.

If you’re on an unsupported PHP branch

PHP 5.6 and other end-of-life branches will never receive an official patch for this issue. The configuration mitigations above are a stopgap, not a fix. Treat this incident as the forcing function to plan migration to a supported branch (8.2.35+, 8.3.36+, 8.4.27+, or 8.5.12+).

Summary: closing out the PHP2Shell vulnerability

  • Patch to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 if you’re on a supported branch — see our guide to keeping PHP up to date for a repeatable patch workflow.
  • If you can’t patch yet, set session.upload_progress.enabled = Off, force memory_limit = -1, disable fatal_error_backtraces, and cap parser limits administratively, then restart your PHP workers — and treat that as a stopgap, not a fix.
  • Mitigations do not stop the information-disclosure path, do not cover every exploitation technique researchers have found, and memory_limit = -1 must be reverted once you patch.
  • EOL branches get no official fix for the PHP2Shell vulnerability — mitigations only buy time until you migrate.

For background on why end-of-life PHP keeps resurfacing in incidents like this, see our post on PHP end-of-life risk.

Let us know in the comments if you need help auditing your PHP fleet for this one.

Sources

Leave a Comment