PHP2Shell: How the PHP Team Contained a Critical RCE, and How the Fix Reached Your Server
Direct answer: PHP2Shell is the nickname for two critical (CVSS 9.2) flaws in PHP's core runtime, CVE-2026-109618 and CVE-2026-109619, that could let an unauthenticated attacker run code on servers handling web requests. PHP shipped fixes on October 21, 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Upgrade, then restart every PHP process. Upgrading the package on disk is not enough on its own.
For us at MarginTop Solutions, the fixed releases landed at 7:45 PM Nepal time on October 21: the main Tika day of Bada Dashain, the busiest day of Nepal's biggest festival, when most of the country is at home with family. We still had every project patched within 24 hours.
This post covers what PHP2Shell was, how the PHP project and the wider ecosystem contained it so quickly, how a PHP fix actually travels from a commit to your server, and what we did on Tika night. I'm not publishing exploit details. Everything here is about understanding the bug and defending against it.
What PHP2Shell actually was#
Both bugs live in PHP's C engine, not in frameworks or application code. That's why they were so serious: Laravel, WordPress, Symfony and plain PHP sites were all in scope, no matter how carefully the application itself was written.
| CVE | Official name | What went wrong, in plain English |
|---|---|---|
| CVE-2026-109618 | RCE via OOM corruption | When PHP runs out of memory, it aborts the request by jumping straight out of the code it was running. In a few places, request memory was already reachable before it had been fully set up. If the out-of-memory jump happened at exactly that moment, the shutdown code later trusted half-built data. |
| CVE-2026-109619 | RCE via stale global state | PHP keeps some global state (errors, sessions, extensions) that points at memory owned by a request. A fatal error or a late warning could leave that state un-reset. A few operations run before the next request initialises, so the same worker could read the stale pointer (a use-after-free). |
One detail is worth sitting with. According to the advisory, the strongest confirmed trigger for the second bug was PHP 8.5's new fatal_error_backtraces feature, which could leave backtrace state un-reset. A feature added to make debugging easier became part of the attack chain. New features are new attack surface, even in a mature runtime.
Who was exposed#
- Versions: PHP 5.4 through 8.5.11, in other words practically every PHP install in production.
- Server setups: anything serving web requests: Apache
mod_php, PHP-FPM,php-cgi, FrankenPHP and LiteSpeed. - Worst case: Apache
mod_php, where remote code execution was validated over HTTP with default limits, before your application code even runs. - End-of-life versions: no official releases for PHP 8.1 and older. The project published minimal source backports for 7.4, 8.0 and 8.1 for people who build from source, and nothing at all for 7.3 and earlier.
Timeline: from private report to patched servers#
| When (2026) | What happened |
|---|---|
| Before October | Researchers report the bugs privately to the PHP security team. Fixes are developed under embargo, and Linux distributions and LTS vendors receive them early. |
| Oct 17 | Amazon's MadPot honeypot logs an exploitation attempt four days before public disclosure, according to Help Net Security. How the attacker got the details is unknown. |
| Oct 20 | Details leak on social media ahead of the planned October 22 disclosure. PHP announces an emergency release. |
| Oct 21, 14:00 UTC | Advisories and four patched releases go out at once. Cloudflare ships WAF rules for known exploit vectors, including on free plans. Google Cloud automatically patches App Engine Standard and Cloud Run services that have automatic base-image updates enabled. |
| Oct 22 | Searchlight Cyber releases an open-source scanner. Our patched public Docker image goes live at 07:37 UTC (more below). |
| Oct 26 | CISA adds CVE-2026-109618 to its Known Exploited Vulnerabilities catalog, confirming active exploitation. |
The leak is the part of the story that should worry everyone. The careful plan had a fixed date, and that date got taken away. PHP's answer was to move the release forward by a day instead of letting a public bug sit unpatched. That decision mattered more than anything else in this incident.
The people who fixed it#
Open source security runs on named people. The official advisories credit:
- Reporters: lcfr-hax, sagitzadik-wiz, tomash-areion and amyb-oai.
- Remediation developers: arnaud-lb, iluuu1994, bukka (who also published the advisories) and ndossche.
The fixes weren't band-aids. For the out-of-memory bug, the team removed the "jump out on OOM" behaviour entirely and replaced it with a soft limit that stops at the next safe point, plus a hard exit at a higher threshold. Patching this class of bug properly, across four release branches, in an emergency, is hard engineering.
How a PHP fix actually reaches your server#
A patched release on php.net doesn't mean your server is patched. The best explanation of this I've read is From Commit to Production: How a PHP Fix Actually Reaches You by dams at Exakat, published the day after the release and using it as the case study. His model has six layers, roughly:
| Layer | Who acts | Typical delay |
|---|---|---|
| 0. Merge | PHP security team merges the fix into php-src and every supported branch | Hours (under embargo) |
| 1. Release | Release managers tag versions and publish source tarballs and Windows builds | Days |
| 2. Packagers | Linux distros, independent packagers, Homebrew, official Docker images, static builds | Days to weeks |
| 3. Rebuilds of rebuilds | RHEL derivatives, vendor and CI images, hosting control panels, local dev stacks | Another cycle, sometimes months |
| 4. Managed platforms | PaaS, serverless, managed WordPress and Laravel hosts | Their schedule, not yours |
| 5. Your pipeline | You pull the new base, rebuild, run CI and deploy to production, staging and dev machines | As fast as your team moves |
A few of his points changed how I think about patching:
- Backported fixes are "present, but invisible." Stable distributions patch the code without changing the version number, so
php -vcan show an "old" version on a fixed server. Check the package changelog for the CVE IDs instead of auditing version strings. - On an end-of-life branch, the fix never arrives. No layer above you will save you.
- Managed hosting is "a risk you are renting." You get convenience, and your patch window becomes your host's patch window.
- "Preprod is part of the attack surface", and staging servers often hold copies of production data.
- Shortcuts exist. Building directly on an official image like
FROM php:8.5skips several of the slow layers.
His summary of why this release went well is the best one-line description of the whole response: "It is quiet coordination, and it worked." Four branches were patched within hours, and the embargo gave packagers time to compile, test and stage binaries before the public announcement.
Why the response worked#
- Coordinated disclosure. The researchers reported privately and the fix was prepared under embargo, so downstream packagers weren't starting from zero on disclosure day.
- Pre-staged downstream. Distros and LTS vendors had patches before the public release, which collapsed the slowest layers of the chain.
- Pulling the date forward after the leak. Waiting for the planned date would have left a publicly known bug unpatched for two more days.
- Honest advisories. The advisories said exactly which setups were validated and which were only suspected, and labelled workarounds as partial. In the advisory's words, updating "is the only complete mitigation."
- Edge and cloud vendors moving the same day. WAF rules and automatic platform patching protected a lot of sites whose owners hadn't even read the news yet.
It wasn't flawless. Some third-party package repositories lagged behind the coordinated release, a pre-patch workaround circulated as if it were a full fix when it wasn't, and anyone still on PHP 8.1 or older was left with source backports or nothing. The response was fast. Being on a supported version is what let you benefit from it.
What we did on Dashain Tika night#
Dashain is when Nepal stops. Offices close, people travel home to their families, and Tika day is when you visit elders to receive blessings, not when you read security advisories. The fixed releases arrived that evening.
Our projects run in Docker, and our Laravel Octane + Swoole services sit on our own runtime image. We also run server-management tools that are themselves PHP applications. Here's what we did:
- Rebuilt our Docker infrastructure. Every project's image was rebuilt on a patched PHP base and redeployed. Because everything is containerised, this was a rebuild-and-roll-out job rather than a server-by-server hunt.
- Rebuilt and republished our public image. roshankc1234/optimized-laravel-runtime was rebuilt on PHP 8.5.12 and published as
php8.5.12-swoole6.2.3-iouring(multi-arch, plus dedicated-amd64and-arm64tags) at 07:37 UTC on October 22, about 17.5 hours after PHP's release. If you use it, see the updated image post. - Updated the server-management tools that run on PHP. These are the ones teams forget, because they aren't "the app". They often sit on classic
mod_phpor FPM setups with admin-level access to everything else. - All projects patched within 24 hours.
One honest note on Swoole. The advisories list mod_php, FPM, php-cgi, FrankenPHP and LiteSpeed as the affected server setups. Swoole and Octane run PHP differently and weren't on that list. We didn't treat that as a reason to wait. The advisories said updating was the only complete fix, so everything got updated.
The lesson for us wasn't heroics. It was that boring infrastructure decisions made months earlier (one base image, reproducible builds, deploys we trust) are what made a festival-night emergency a rebuild instead of a crisis. We wrote about that mindset in security basics every startup should get right.
Checklist: are you actually patched?#
- Check the real version or the changelog. You want 8.5.12, 8.4.27, 8.3.36, 8.2.35 or later, or a distro package whose changelog mentions the CVEs:
php -v apt-get changelog php-fpm | grep -E "CVE-2026-10961[89]" rpm -q --changelog php-fpm | grep -E "CVE-2026-10961[89]" - Restart every PHP process. Running
php-fpm, Apache, FrankenPHP or LiteSpeed workers keep the old code in memory until they restart. Long-running Octane workers need a fresh deploy too. - Rebuild containers from a fresh base. Pull the patched base image and rebuild without cache (
docker build --pull --no-cache). An old cached layer is an old PHP. - Don't forget the quiet places: staging, preprod, CI runners, cron and queue boxes, hosting panels and internal admin tools.
- If you truly can't patch today, the advisories list partial mitigations: set
session.upload_progress.enabled = 0, setfatal_error_backtraces = 0on PHP 8.5, avoid memory limits viaulimit -v/RLIMIT_AS(they trigger the vulnerable path), and put a WAF in front. The UK NCSC's advice is to take unpatchable systems offline. These are stopgaps, not fixes. - On PHP 8.1 or older: treat this as the push to upgrade. There is no official fix coming.
- Look for signs of compromise. Google's guidance highlights shells or tools like
curlspawned by PHP processes, spikes in crashing workers, and unusually long session cookies. If you suspect a host, capture worker memory before restarting it, because a restart destroys the evidence.
Key takeaways#
- PHP2Shell (CVE-2026-109618 and CVE-2026-109619) was a pair of critical core-runtime bugs affecting essentially every PHP version serving web traffic. The fixed releases are 8.5.12, 8.4.27, 8.3.36 and 8.2.35.
- The PHP project contained it fast: private disclosure, an embargo that pre-staged downstream packagers, four branches patched at once, and a release pulled forward the moment details leaked.
- A fix on php.net is only the first of several layers. Backports hide behind unchanged version numbers, managed hosts patch on their own schedule, and end-of-life versions never get the fix.
- Upgrading isn't patching until processes restart and containers are rebuilt from a fresh base.
- Simple, reproducible infrastructure is a security feature. It's what let a small team in Kathmandu patch everything within 24 hours on the busiest night of Dashain.
Sources and further reading#
- PHP security advisories: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- dams, Exakat: From Commit to Production: How a PHP Fix Actually Reaches You
- CISA: Critical Vulnerabilities in PHP Require Immediate Patching
- Google Cloud: Responding to core PHP vulnerabilities
- UK NCSC: Critical security issue affecting PHP
- watchTowr: CVE-2026-109618 FAQ
- Help Net Security: PHP2Shell: Critical PHP vulnerabilities patched
Running PHP in production and want a second pair of eyes on your patching pipeline? Get in touch. We've been through this one the hard way, on a festival night.