Full-Stack Development
Patching php2shell: PHP's emergency fix for CVE-2026-109618 and -109619
php2shell explained: the timeline, who is exposed, and how to patch PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 on any stack, then verify it.
By Tanvir Ahamed, published . 8 min read.
On October 21, 2026 the PHP project shipped emergency releases for two flaws that let an unauthenticated attacker run code on a server that does nothing more than serve PHP over HTTP. The bugs, CVE-2026-109618 and CVE-2026-109619, are nicknamed php2shell. They sit in the PHP engine itself, not in a framework or an extension, so no application code has to be vulnerable for a server to be at risk. This article covers what happened and why it matters, then gives a patch runbook for Debian and Ubuntu, RHEL-family systems, Alpine, Docker, Homebrew and managed platforms, plus the stopgaps for hosts that cannot be patched today.
What php2shell is
PHP's security advisory lists two bugs, each with its own GitHub advisory:
- CVE-2026-109618, RCE via out-of-memory corruption (GHSA-w8hw-w9fx-j79w). If PHP runs out of memory while a request's internal data is only partly initialised, the cleanup that follows can treat that half-built data as valid.
- CVE-2026-109619, RCE via stale global state (GHSA-6wmf-4rv5-p3rp). State owned by one request can survive after its memory is freed, for example after a fatal error, and a later request on the same worker can use it. This is a use-after-free, and PHP also lists memory disclosure as a confirmed impact (Tenable).
PHP rates both at 9.2 on CVSS v4.0. The 10.0 score attached to the identifiers that leaked before the release is not PHP's rating, and the PHP Foundation called it incorrect.
The second bug explains why the long-lived worker model matters here. PHP-FPM, mod_php and FrankenPHP keep a process alive across many requests, so leftover state from an attacker's first request can be waiting when the second one arrives on the same worker.
How the disclosure unfolded
The release was planned for October 22. According to Tenable's FAQ, which collects the public timeline, it moved forward a day after the CVE identifiers started circulating:
| Date (2026) | Event |
|---|---|
| October 17 | Amazon Threat Intelligence sees an exploitation attempt against its MadPot honeypots, four days before disclosure. Amazon does not know where the attacker got the details. |
| October 20 | CVE identifiers leak publicly. PHP announces a release for the next day. CERT-EU publishes advisory 2026-018. |
| October 21 | PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35 with its advisory. Cloudflare deploys WAF rules. CISA publishes an alert urging immediate patching. |
| October 22 | Patched official Docker images appear on Docker Hub. |
The research came from LCFR of Calif.io, whose exploit Kirill Firsov showed in a video under the php2shell name. The PHP Foundation also credited OpenAI and Wiz, among others, and both Calif and Wiz said they used AI models in their research (Tenable).
Is it being exploited?
Reports disagree, and the honest summary is "assume yes". CISA said at publication that it had not observed exploitation. Amazon, which saw further attempts from several apparent actors on October 21, describes php2shell as "now being broadly exploited in the wild" and recommends investigating activity back to October 17. Cloudflare's CTO reported early attacks, and watchTowr said it had reproduced both bugs and was seeing attempts against them. Whether any attempt has succeeded against a real system is not established.
Code claiming to exploit these bugs is circulating, but none has been verified, and the PHP Foundation said its review found no functional exploit at release. Do not run an unverified proof of concept on a machine you care about: it may contain malware.
Who is affected
According to CERT-EU, every PHP installation that serves web requests is in scope: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. PHP rates mod_php as the highest risk because an exploit there can fire before any application code runs. The CLI on its own, running scheduled scripts that take no web input, is not the main concern, but the same package update fixes it.
| Branch | Status |
|---|---|
| 8.5 | Fixed in 8.5.12 |
| 8.4 | Fixed in 8.4.27 |
| 8.3 | Fixed in 8.3.36 |
| 8.2 | Fixed in 8.2.35 (end of life on December 31, 2026) |
| 7.4 to 8.1 | No official release; minimal source backports only |
| 5.4 to 7.3 | End of life; no fix from the PHP project |
The earliest affected code dates back to PHP 5.4.0. If a server is on 8.1 or older and cannot move to a supported branch right now, CISA's advice is blunt: take it offline until it is patched or upgraded.
Do not forget PHP that you did not install directly. CISA asks teams to inventory PHP embedded in other software, and CERT-EU points to the NCSC-NL php2shell repository for identifying affected third-party products. Upgrading a framework does not help either: a fresh Laravel 13.36 install is exactly as exposed as the PHP binary underneath it.
Step 1: find the PHP that actually serves traffic
php -v reports the CLI binary, which Qualys warns may belong to a different installation from the one serving web requests. Start by listing every PHP runtime on the host:
# The CLI (may not be what serves HTTP)
php -v
# Long-running PHP servers and the binaries behind them
ps -eo pid,lstart,args | grep -E 'php-fpm: master|php-cgi|frankenphp|lsphp' | grep -v grep
# Apache with mod_php
apachectl -M 2>/dev/null | grep -i php
# Installed PHP packages (Debian/Ubuntu, then RHEL family)
dpkg-query -W -f='${db:Status-Abbrev}${Package} ${Version}\n' 'php*' 2>/dev/null | awk '/^ii/{print $2, $3}'
rpm -qa 'php*' 2>/dev/nullThe lstart column matters later: a worker that started before the upgrade is still running the vulnerable code.
Distribution version numbers can look old
Linux distributions and LTS vendors received the patches in advance, and many backport the fix without moving to the new upstream version. A Debian or RHEL package can be fully patched while still reporting, say, 8.2.x below 8.2.35. Qualys lists the vendor advisories, including Ubuntu USN-8948-1 and USN-8948-2, the RHSA-2026:87070 series, Amazon Linux ALAS2023-2026-3301 to 3304, SUSE, Debian, Alpine 3.24, Arch, Gentoo and Fedora. Check the package changelog for the CVE instead of trusting the version string:
# Debian / Ubuntu
apt-get changelog php8.3-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'
# RHEL, Rocky, Alma, Amazon Linux, Fedora
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'If both CVE IDs appear, that package carries the fix.
Step 2: patch by platform
Debian and Ubuntu
Ubuntu's update needs no reboot, but every service that loads PHP must be restarted (Qualys). This upgrades only the PHP packages already installed:
sudo apt-get update
apt list --upgradable 2>/dev/null | grep -i php
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev}${Package}\n' 'php*' 'libapache2-mod-php*' 2>/dev/null | awk '/^ii/{print $2}')If your PHP comes from the ppa:ondrej/php repository rather than the distribution, run apt-cache policy php8.4-fpm (with your branch) and confirm the candidate version is the fixed upstream release before you move on.
RHEL, Rocky, AlmaLinux, Amazon Linux and Fedora
sudo dnf upgrade --refresh 'php*'
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'One catch from CERT-EU: the Remi repository (rpms.remirepo.net) did not have updates on release day. If your PHP comes from Remi and dnf offers nothing, switch to the distribution's packages or build from source rather than waiting.
Alpine
Alpine 3.24 ships fixed php83, php84 and php85 packages:
apk update
apk upgrade
apk info -v | grep -E '^php8'Docker official images
The official php images were rebuilt on October 22. Pin the exact patched version so a stale cache cannot hand you the old one:
FROM php:8.4.27-fpm-alpineEquivalent tags exist for every fixed branch and variant, such as 8.5.12-fpm, 8.3.36-apache and 8.2.35-fpm-bookworm. Skip the RC1 tags that also match those version numbers; they are release candidates. Then rebuild without the layer cache and check the result:
docker build --pull --no-cache -t myapp:php-8.4.27 .
docker run --rm myapp:php-8.4.27 php -vRebuilding is not enough on its own. Redeploy, and confirm that no running container still uses the old image:
docker ps --format '{{.Names}} {{.Image}} {{.RunningFor}}'Homebrew
Homebrew already ships php 8.5.12, php@8.4 8.4.27, php@8.3 8.3.36 and php@8.2 8.2.35. Use the formula you have installed:
brew update
brew upgrade php@8.4
brew services restart php@8.4Managed platforms
On Google Cloud, Google's guidance splits the work:
- App Engine standard on PHP 8.2 or later is patched automatically.
- Cloud Run services and functions with automatic base image updates are patched automatically. Everything else must be rebuilt and redeployed.
- App Engine flexible, GKE and any image you built yourself are your responsibility.
- Runtimes on PHP 8.1 or older will not be patched and must be upgraded.
For shared hosting and other managed PHP, check the provider's status page for the fixed versions, and ask if it says nothing. A patch you assume your host applied is not a patch.
Step 3: restart every worker and verify
Upgrading the package replaces files on disk. Processes that are already running keep the old code in memory until they restart, and a worker that survives the upgrade can still hold stale state from an earlier request. Restart rather than reload:
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2On RHEL-family systems the units are usually php-fpm and httpd. Then run the Step 1 ps command again: every PHP master process should show a start time after the upgrade, and php-fpm8.3 -v (or your binary's equivalent) should report the fixed version or a build whose changelog lists both CVEs.
If you cannot patch today
Updating is the only complete fix. PHP's advisory lists stopgaps that reduce exposure and states plainly that they do not make an installation safe. Set them where applications cannot override them, then restart every worker. Settings changed with ini_set() or an FPM .user.ini file do not count.
For PHP-FPM, add them to the pool configuration:
; /etc/php/8.3/fpm/pool.d/www.conf
; CVE-2026-109618: no in-PHP memory exhaustion, smaller request bodies
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 2M
; CVE-2026-109619
php_admin_flag[session.upload_progress.enabled] = offUse post_max_size = 32M if the application needs uploads, and keep upload_max_filesize below it. On PHP 8.5, also set fatal_error_backtraces = 0 in php.ini. Qualys additionally lists pm.max_requests = 1 for FPM as an optional measure, which recycles a worker after every request at a real throughput cost.
With memory_limit disabled, put a container or cgroup memory limit around PHP so a runaway request kills only its own worker. Do not use RLIMIT_AS or RLIMIT_DATA for this: they can trigger the very allocation failure CVE-2026-109618 needs.
At the edge:
- Cloudflare's WAF rules for known exploit vectors are available on every plan, including free, but they only cover traffic that goes through Cloudflare.
- On Google Cloud, enable Cloud Armor managed rules, including the file upload and RFI categories, and block request bodies too large to inspect.
- Enforce request size, field and header limits at the web server or proxy.
For anything internet-facing that cannot be patched, CERT-EU recommends isolating it from public networks until the fix is applied and verified.
Look for signs of compromise
Because attempts predate disclosure, patching closes the door but does not tell you whether anyone came in. CISA suggests watching for PHP worker processes that launch shells or download tools. Review web server and FPM logs from October 17 onward for oversized or malformed POST requests, worker crashes and fatal errors. Treat any unexplained child process of php-fpm or httpd as an incident, not a curiosity.
The order is simple. Patch internet-facing mod_php hosts first, then FPM, FrankenPHP and LiteSpeed. Restart everything and confirm the start times. Then decide what to do about the 8.1 and older servers that will never get an official release. PHP 8.2 reaches end of life on December 31, so if you are moving off an old branch anyway, plan for 8.4 or 8.5 rather than another short stop.
Sources
- PHP security advisory: critical remote code execution vulnerabilities
- GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (CVE-2026-109618)
- GHSA-6wmf-4rv5-p3rp: RCE via stale global state (CVE-2026-109619)
- CISA: Critical vulnerabilities in PHP require immediate patching
- CERT-EU security advisory 2026-018
- Tenable: php2shell frequently asked questions
- Qualys: PHP2Shell, PHP patches two critical RCE vulnerabilities
- Google Cloud: Responding to core PHP vulnerabilities
- Official php images on Docker Hub
- PHP
- Security
- php2shell
- CVE-2026-109618
- CVE-2026-109619
Related articles
What changed in Laravel 13.36: DB::olap() and fluent rule builders
Adopt DB::olap() for large PlanetScale and Vitess queries and replace string validation rules with fluent Rule builders in Laravel 13.36.
Full-Stack Development, 7 min read
Upgrading from Next.js 15 to 16.4 before end of life
Migrate from Next.js 15 to 16.4.2, apply October 2026 security patches, update async request APIs, and adopt Partial Prefetching.
Full-Stack Development, 9 min read
Shipping a static Next.js site on Firebase Hosting
How this site is built: a Next.js static export served from Firebase Hosting, with URLs, caching and SEO metadata that agree with each other.
Full-Stack Development, 4 min read