PHP2Shell (CVE-2026-109618 and CVE-2026-109619): Critical PHP RCE – How to Check, Patch and Verify Your Servers Today

On October 21, 2026, the PHP project shipped emergency security releases for two critical vulnerabilities in the PHP engine itself: CVE-2026-109618 and CVE-2026-109619, now widely known as PHP2Shell. Both can lead to unauthenticated remote code execution on a web server running PHP. Not in a plugin, not in a framework. In PHP.

The release was planned for later in the week, but details started leaking on October 20, so the PHP security team pulled it forward to October 21 at 14:00 UTC. The same day, Cloudflare said it was already seeing early attacks.

If you run WordPress, Drupal, Moodle, Nextcloud, a self-hosted portal or anything else on PHP, this one is not “next patch window” material. This post is the practical version: what the two flaws are, which setups are most exposed, how to find out whether your PHP is actually fixed (the version number can lie to you), how to patch and restart properly, and what to do if you can’t patch today.

I patched my own hosting server this evening, and I’ve included what that looked like, including the one thing that tripped me up.

Everything below is based on the PHP security advisories and vendor information as of October 21, 2026.

PHP2Shell at a glance

CVEs CVE-2026-109618 (RCE via OOM corruption), CVE-2026-109619 (RCE via stale global state)
Severity Critical. CVSS v4.0 9.2 for both (PHP security team). Vendors score differently, Red Hat rates it Important (CVSS v3.1 8.1)
Attack Over the network, no authentication, no user interaction
Affected PHP before 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Older branches are affected too
Fixed in PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 (and 8.6.0 RC4)
End-of-life branches 8.1 and older get no official release. Source backport branches for 7.4, 8.0 and 8.1 exist, and some distros/LTS vendors backport
Most exposed Apache with mod_php (full RCE validated). FrankenPHP likely exposed to CVE-2026-109618 by source analysis. PHP-FPM is exposed under specific conditions
Exploitation Early attacks reported by Cloudflare on October 21
Restart needed Yes. Patching the files on disk is not enough

What the two vulnerabilities actually are

You don’t need to understand the internals to patch, but it helps to understand why the advice is “update and restart”, and why configuration tricks only go so far.

CVE-2026-109618 – RCE via OOM corruption. When PHP runs out of memory, it aborts the current request by jumping straight out of whatever it was doing. In some places that happens while request memory is only partly initialized. Request shutdown can then read that half-initialized memory as if it were valid. The advisory describes several multipart form parsing paths that can push a request into out-of-memory while staying inside what look like normal PHP limits. The fix replaces that “jump out” on out-of-memory with a two-step approach: a soft limit that aborts gracefully at the next safe point, and a hard limit that simply exits the worker.

CVE-2026-109619 – RCE via stale global state. PHP keeps some global state (error, session, object and extension state) that can point into memory owned by a request. If a fatal error such as out-of-memory skips the reset, that state survives after the request memory is freed, and a later request on the same worker can use it. That is a use-after-free, and the advisory confirms it can lead to memory disclosure and full RCE.

The important part: an attacker can trigger this with crafted requests, unauthenticated and before your application code runs (the advisory notes it may take several attempts on a busy prefork server). So it does not matter how well your WordPress plugins are maintained.

Which setups are most exposed

The advisories are refreshingly specific about this. Short version:

Setup Exposure according to the advisories
Apache + mod_php Highest. Full RCE validated over HTTP with default memory and input limits on a normal prefork setup. CVE-2026-109619 also confirmed on mod_php
FrankenPHP Likely susceptible to the application-independent attack (CVE-2026-109618), based on source analysis
PHP-FPM (Nginx or Apache in front) Lower, but not safe. No complete FPM exploit demonstrated for CVE-2026-109618, but source analysis supports susceptibility. CVE-2026-109619 is confirmed on FPM through session upload progress, which is enabled by default
LiteSpeed (LSAPI) Behaves like FPM. Application-specific exploits likely but not validated
php-cgi Listed as affected in public reporting. Treat as exposed

So yes, FPM is “better” here. No, that is not a reason to wait. Patch everything that serves web requests.

The timeline

Date (2026) What happened
October 20 CVE IDs start circulating. The PHP security team publishes a pre-announcement for a release on October 21
October 21, ~14:00 UTC The two GitHub advisories go public (13:34 UTC), followed by PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35
October 21 Cloudflare reports early attacks and deploys WAF rules for known exploit vectors, available on all plans including free
October 21 Ubuntu publishes USN-8948-1 and USN-8948-2. Debian has fixed packages for bookworm and trixie. Check your own vendor for the rest

According to watchTowr, the original embargo date was October 22. It didn’t hold.

Step 1: Find every place you run PHP

The servers you know about are easy. The ones that bite you are the forgotten ones: the old intranet box, the marketing site an agency set up, the monitoring tool with a PHP web UI, the container image nobody has rebuilt since spring.

On a Linux host:

# Is PHP installed, and which version is the CLI?
php -v

# Which PHP packages are installed (Debian/Ubuntu)
dpkg -l 'php*' | grep '^ii'

# Which PHP packages are installed (RHEL/Alma/Rocky)
rpm -qa 'php*'

# How is PHP served? mod_php shows up as an Apache module
apache2ctl -M 2>/dev/null | grep -i php      # Debian/Ubuntu
httpd -M 2>/dev/null | grep -i php           # RHEL family

# FPM, LiteSpeed or FrankenPHP running?
systemctl list-units --type=service | grep -Ei 'php|fpm|lsws|litespeed|frankenphp'

If you have a vulnerability scanner, use it as a second opinion, not as your inventory. And don’t forget containers: a patched host does nothing for a container image with its own PHP inside.

Step 2: Check whether your PHP is actually fixed

This is where people get it wrong. If you use distribution packages, php -v does not tell you whether you are patched.

Distributions backport the fix into the PHP version they shipped, and the upstream version number stays the same. On Ubuntu 24.04, for example, the fixed package is 8.3.6-0ubuntu0.24.04.13. php -v still says PHP 8.3.6 before and after the update. If you compare that against “fixed in 8.3.36”, you’ll conclude you are vulnerable forever, or worse, that you can’t do anything until you upgrade PHP.

What to check instead is the package version against your vendor’s advisory:

Distribution Package Fixed version
Ubuntu 24.04 LTS php8.3 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS php8.1 8.1.2-1ubuntu2.28
Ubuntu 20.04 LTS php7.4 7.4.3-4ubuntu2.29+esm5 (Ubuntu Pro)
Ubuntu 26.04 LTS php8.5 8.5.4-0ubuntu1.5
Debian 13 (trixie) php8.4 8.4.27-1~deb13u1 (DSA-6574-1)
Debian 12 (bookworm) php8.2 8.2.35-1~deb12u1 (DLA-4848-1)

For RHEL and its rebuilds, check the Red Hat CVE page for your PHP module stream. At the time of writing it did not list a fixed package yet.

The quickest proof is the package changelog. If the CVE numbers are in it, the fix is in it:

# Debian/Ubuntu - is an update available, and from where?
sudo apt-get update
apt-cache policy php8.3-fpm php8.3-common     # adjust to your PHP version

# Debian/Ubuntu - does the CANDIDATE package mention the CVEs? (may 404 right after a release)
apt-get changelog php8.3-common | grep -m3 -E 'CVE-2026-10961[89]'

# Fallback that always works: pull the .deb and read its changelog without installing
cd /tmp && apt-get download php8.3-common
dpkg-deb --fsys-tarfile php8.3-common_*.deb | tar -xO ./usr/share/doc/php8.3-common/changelog.Debian.gz | zcat | head -20

# After installing: the local changelog
zgrep -m3 -E 'CVE-2026-10961[89]' /usr/share/doc/php8.3-common/changelog.Debian.gz

# RHEL family - after updating
# If nothing matches, compare the package version against the RHSA for your module stream instead
rpm -q --changelog php-common | grep -m3 -E 'CVE-2026-10961[89]'

If you install PHP from a third-party repository, check that repository specifically. At the time of the release, the popular Remi repository for RHEL had no updated packages yet, and the advice was to switch to distribution packages or build from source.

If you build PHP yourself or use the official builds, it is simple: you need 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or newer.

Step 3: Patch

On Debian or Ubuntu, update only the PHP packages that are actually installed:

sudo apt-get update
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' '*php8.3*' \
  | awk '$1=="ii"{print $2}')

The *php8.3* pattern also catches libapache2-mod-php8.3 (mod_php) and libphp8.3-embed, which php8.3-* would miss.

That ii filter is not decoration. When I patched my own server, my first attempt simply passed every package name matching the pattern to apt. That list included php8.3-json, which is known to dpkg but not installed and has no candidate on Ubuntu 24.04. apt refused the whole transaction with E: Package 'php8.3-json' has no installation candidate, and nothing was upgraded. If I hadn’t checked the package versions afterwards, I would have walked away thinking I was patched.

On the RHEL family:

sudo dnf update 'php*'

On Windows with IIS, stop the application pools (or IIS) first, because running php-cgi.exe processes lock the DLLs, then replace the PHP folder with the fixed build for your branch from windows.php.net and start the pools again.

For WordPress on managed hosting: you can’t patch PHP yourself. Check your host’s status page or ask them directly whether the PHP version your site uses has the fix. “We use PHP 8.3” is not an answer.

Step 4: Restart every PHP process

Updated files on disk do nothing for PHP processes that are already running. They keep the old, vulnerable code in memory until they are restarted.

# PHP-FPM (adjust the version)
sudo systemctl restart php8.3-fpm   # Debian/Ubuntu; "php-fpm" on the RHEL family

# Apache with mod_php: PHP lives inside Apache, so restart Apache
sudo systemctl restart apache2     # or httpd on RHEL

# Apache in front of FPM: Apache itself did not change, a reload is optional
sudo systemctl reload apache2

Don’t forget the long-running PHP processes that aren’t web servers: queue workers, Laravel Horizon or Octane, Supervisor-managed workers, Swoole/RoadRunner/FrankenPHP worker mode. And containers need a rebuilt image and a redeploy, not just a restart.

Step 5: Verify

“I ran the update” is not verification. Check these three things:

# 1. Every installed PHP package is on the fixed version
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' '*php8.3*' | awk '$1=="ii"'

# 2. No PHP process is older than the patch
ps -eo pid,lstart,cmd | grep -E 'php|apache2|httpd' | grep -v grep

# 3. FPM started cleanly
sudo journalctl -u php8.3-fpm --since -15min --no-pager | tail   # php-fpm on RHEL

Then test the sites from the outside. A 200 on the front page and a working login is the minimum.

One note: extension packages that come from a different source package than PHP itself (for example php8.3-imagick on Ubuntu) keep their own version number. That’s expected. The fix is in the PHP engine packages (common, cli, fpm, opcache and so on).

Step 6: If you can’t patch today

Be honest about this part: updating is the only complete fix. PHP’s own developers have said so publicly, and both advisories say it explicitly. The settings below reduce risk while you get the update in. They don’t replace it.

Put a WAF in front. If the site is behind Cloudflare, the WAF rules for known exploit vectors are available on all plans, including free. It is a stopgap and only covers traffic that actually goes through Cloudflare, so make sure the origin isn’t reachable directly.

For CVE-2026-109618 (OOM corruption): – Set memory_limit=-1 administratively, so the application can’t override it. In an FPM pool that is php_admin_value[memory_limit] = -1; with mod_php use php_admin_value in the Apache config. Restart all workers and check that nothing restores a finite limit. – Because unlimited PHP memory is a denial-of-service risk on its own, keep post_max_size conservative (8M, or 32M if you need uploads), keep upload_max_filesize below it, and enforce request size limits at the web server or proxy. A container or cgroup memory limit is a partial alternative. – If the application doesn’t use multipart form data, block multipart requests at the frontend. – Do not use RLIMIT_AS or RLIMIT_DATA. The advisory warns they make the underlying allocation fail, which brings back the vulnerable code path.

For CVE-2026-109619 (stale global state): – Set session.upload_progress.enabled=0 administratively (php.ini, FPM pool or server config, not ini_set() or .user.ini) and restart every worker. If you don’t use upload progress, the advisory suggests keeping it off even after patching. – On PHP 8.5, also set fatal_error_backtraces=0 administratively. – Turning off display_errors, lowering error_reporting or using @ does not help. php.net says so explicitly. – Consider pm.max_requests=1 in FPM if you can afford the performance hit. It limits what one request can leave behind for the next.

And if none of that is possible: CERT-EU advised isolating internet-facing PHP applications from public networks until they are fixed, and the UK NCSC says to consider taking a system offline if you can neither patch it nor apply the interim mitigations. That’s an uncomfortable conversation, but a shorter one than the incident report.

What it looked like on my own server

My hosting server runs Ubuntu 24.04 with Apache in front of PHP-FPM, behind Cloudflare. No mod_php, so not the worst case, but FPM with session upload progress on by default is still in scope.

  1. Checked what was installed: PHP 8.3.6 from the Ubuntu archive, package 8.3.6-0ubuntu0.24.04.12, and apt-cache policy showed .13 available from noble-security.
  2. Proved the fix before installing: downloaded php8.3-common and read its changelog. The first entry: SECURITY UPDATE: Remote RCE via state corruption issue – CVE-2026-109618_109619.patch.
  3. Patched: the first run did nothing because of the php8.3-json issue above. The second run, with only installed packages, upgraded 13 packages.
  4. Restarted: php-fpm8.3 -t to test the config, restarted FPM, reloaded Apache.
  5. Verified: all engine packages on .13, no PHP process older than the restart, clean FPM log, and every site returned 200 from the outside.

About ten minutes of actual work. The checking took longer than the patching, and that is how it should be.

PHP2Shell checklist

  • ☐ Inventory every host and container that runs PHP, including the forgotten ones
  • ☐ Note how PHP is served: mod_php, FPM, LiteSpeed, FrankenPHP, CGI
  • ☐ Check the installed package version against your vendor’s advisory, not php -v
  • ☐ Confirm the CVEs appear in the package changelog
  • ☐ Patch, and confirm apt/dnf actually upgraded something
  • ☐ Restart FPM, Apache (if mod_php), workers and supervisors; redeploy containers
  • ☐ Verify: package versions, process start times, logs, sites from the outside
  • ☐ Managed hosting: get a written answer from your host
  • ☐ Can’t patch: WAF, memory_limit and upload progress mitigations, or take it offline
  • ☐ Plan the upgrade off any PHP branch that is end-of-life

Quick answers to the questions everyone asks

php -v says 8.3.6. Am I vulnerable? Not necessarily. If PHP comes from your distribution, check the package version and changelog. Ubuntu’s fixed package for 24.04 is still PHP 8.3.6.

I run PHP-FPM, not mod_php. Can I wait? No. FPM is less exposed to CVE-2026-109618, but CVE-2026-109619 is confirmed on FPM through session upload progress, which is on by default.

My WordPress site is fully updated. Am I safe? No. This is in PHP itself and can be triggered before WordPress code runs. Your plugins don’t matter here.

We’re on PHP 7.4 or 8.1. Is there a fix? No official PHP release. Ubuntu, Debian and other LTS vendors have backports for the versions they ship, and the PHP security team published minimal source backport branches for 7.4, 8.0 and 8.1. The real answer is to get off end-of-life PHP.

Is a restart really necessary? Yes. Running PHP processes keep the vulnerable code in memory until they are restarted.

Is there a public exploit? A working exploit chain has been demonstrated, and code claiming to exploit it is circulating. Don’t run random exploit code from the internet to “test” your servers; unverified exploit code is a well-known way to deliver malware.

Is it in CISA KEV? Not at the time of writing. Don’t wait for it.

My recommended plan, condensed

  • Today: Inventory PHP (Step 1). Patch and restart every internet-facing PHP host, starting with anything on mod_php (Steps 3-4). Verify (Step 5).
  • If something can’t be patched today: WAF plus the mitigations in Step 6, or take it offline.
  • This week: Internal PHP hosts, containers and managed hosting. Get confirmation in writing from hosting providers.
  • After: Put memory_limit back to a real value if you set it to -1 as a stopgap (the patched engine now has a soft OOM plus a hard OOM that exits the worker, so a finite limit is the intended state again). Disable session upload progress where you don’t use it, and put a date on moving off end-of-life PHP branches.

References

Wrapping up

PHP2Shell is about as bad as it gets for a web platform: unauthenticated, before your application code runs, in a runtime that powers most of the web, with early attacks seen on release day.

The good news is that the fix is boring. Update the packages, restart the processes, and verify that both actually happened. The traps are the details: a version number that doesn’t change on distro packages, an apt run that silently does nothing, workers that keep running old code, and the server nobody remembered was there.

Spend the hour today. And if you want a second pair of eyes on your hosting setup, from a quick check of this one patch to a full hardening review, you know where to find me.

Feel free to comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.