PHP2Shell: Scary Name, Hard Exploit, Slow Patching
What a PHP core RCE looked like from the detection side: how we narrowed things down, what the patch curve looked like, and a few lessons along the way.

What a PHP core RCE looked like from the detection side: how we narrowed things down, what the patch curve looked like, and a few lessons along the way.
On 20 October 2026, two bugs in the PHP core leaked before their fix: CVE-2026-109618 and CVE-2026-109619, now known as PHP2Shell, rated CVSS v4 9.2. This time it was not a plugin or a CMS, but PHP itself, from 5.4 all the way to 8.5.
That means everything built on PHP is potentially exposed, whatever its own version: CMS like WordPress, Drupal, Joomla and TYPO3, e-commerce like Magento and PrestaShop, everyday tools like phpMyAdmin, Roundcube, Nextcloud, GLPI, Moodle and MediaWiki, and every in-house app built with Laravel or Symfony. Keeping those up to date did not help this time: the bug sits underneath them.
The fix landed on 21 October in 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Branches 8.1 and older will not get one.
My side of the job is detection: helping our clients find their exposed servers quickly, and keeping that picture up to date as things move. Here is how those first days went for us.
From where I sit, this is one of the most striking incidents we have seen, and one of the widest in impact, since Heartbleed in 2014 and Log4Shell in 2021. Same feeling of a building block everyone depends on suddenly massively vulnerable, with a huge part of the web in scope overnight.
One big difference this time: AI. Back in 2014 and 2021, nobody had it in their toolbox. This time, within hours of the leak, there were exploits, write-ups and scanners everywhere. Many were generated, and most were never tested. We had to sort it all out fast, and the only way to do that was to automate as much as we could.
Here is what that looked like from our side: how we went from suspects to a handful of proven cases, how fast servers actually got patched, how hard the exploit really is, and what is (and isn’t) happening in the wild.

Days 0 and 1: a lot of noise
It started with a simple heads-up. The PHP Foundation announced that a critical security release was coming, with no technical details:
The PHP Foundation announcing the security release on X
Only 30 minutes later, a first video claiming a working exploit was already going around :
The first video claiming a working exploit, on X
From there, things caught fire. Within hours there were GitHub repos, one-click scanners, RCE screenshots and “how to exploit” videos everywhere. Nobody outside the PHP team knew what the bugs actually were. Many of these were AI-generated, most were never tested, and some were simply fake.
The PHP Foundation reviewed what appeared after the leak and found no functional exploit at release time. JFrog even spotted repos published as pranks.
That noise was not harmless. These are memory-corruption bugs. A working exploit is tuned to one specific PHP build, and it breaks as soon as the version, the distribution or the compiler changes. On the wrong server, you don’t get a shell, you get a crashed PHP worker. Try it a few times and the site goes down. So running random public code against production “just to check” was a real risk.
Even mainstream tooling got caught up in it. The official PHP2Shell template in the main branch of the Nuclei templates is an “Active Crash Detection” check: it deliberately pushes PHP into memory exhaustion to see how the server reacts.
It is tagged intrusive, but that tag is not excluded by default, so it runs as part of an ordinary scan. A pull request to make it opt-in is still waiting. Think of everyone running Nuclei on a list of targets without reading each template. That is a lot of PHP workers at risk, from people who only meant to check.
On client assets, we set one simple rule from the start: no exploit code, and nothing that could affect availability.
How hard is it, really?
On our side, we reproduced the full chain in an isolated lab, up to code execution on a stock PHP 8.4.26 with Apache. Our technical analysis sums it up in one line: “The hard part is not the vulnerability. It is the target.”
- A crash is easy. Code execution is not. Taking a PHP worker down is reproducible. Getting a shell takes much more work.
- Not one bug, a chain. A memory leak first, to get past address randomisation. Then a second bug to take control. Neither is enough on its own.
- Specific to each server. The attack has to be tuned to the exact PHP build, the distribution and the way PHP runs (mod_php or PHP-FPM). A version shown in a header makes that tuning much easier.
- Configuration matters. Some setups add friction. None of it is a wall, but each one means more tries.
Our conclusion at the time: the realistic threat is targeted attacks, not one universal exploit that sweeps the internet.
A few days later, a public challenge put that reading to the test. Someone put a server online running a vulnerable PHP 8.4.26, with about $2,000 in bitcoin on it. Steal the key, keep the money. The page was everywhere: Hacker News, security Twitter, YouTube. Plenty of people tried.

The bitcoin was gone after 19.28 hours, on 25 October. The winner then published a detailed write-up, and it matches our analysis point by point:
- One exact build. The attack is calibrated on one PHP build, on one distribution, with one server setup. The author warns that a Debian build reporting the same version “will never match”.
- Several stages. Two memory leaks before the final request can do anything.
- Not reliable. In the author’s own lab, with nothing else talking to the server, it worked 7 times out of 10.
- Noisy. Each attempt pushes PHP workers into out-of-memory errors. On a real site, that shows up in the logs, and possibly as errors for real users.
So yes, it is doable. A skilled person did it in less than a day. But that was against one known target, with a known build, a public bounty and a crowd of other people hammering the same server. Turning that into a mass, quiet attack across thousands of different servers is another story. Every new build needs new tuning, and every failed try makes noise.
That is the real picture of PHP2Shell today: targeted, possible, and hard to scale without being seen. Not a reason to relax. AI is making each of those stages faster to build.
The funnel
We approached it as a funnel: start wide while little is known, then narrow down as better signals become available. Each step ran automatically across all client perimeters.

Day 0: everything that runs PHP
With no details yet, the safest assumption was that anything running PHP could be concerned: mod_php, PHP-FPM, and the CMS and frameworks built on top. Since we already fingerprint every monitored asset, building that first list only took minutes: 3,745 assets, across 132 client alerts.
That list was intentionally broad, because on day 0 a false positive costs less than a miss.

Day 0: the version
Quicly after that, the question became: which PHP version? Many servers still advertise it, either in X-Powered-By: PHP/8.1.2 or, more discreetly, in the Server header, like Apache/2.4.6 (CentOS) PHP/5.4.16.
- 739 assets exposed at 77 clients, each with its own finding, evidence and target version.
- 1,958 under watch: PHP confirmed, version hidden.
A version in a header places a server in the affected range, but it does not prove the exploit would work there. We made that clear in every finding.
Day 1: re-check every hour
Every finding then got a retest that our automation runs every hour. When a fixed version shows up, the finding closes on its own. When it is still vulnerable, it stays open with fresh evidence. Nobody has to chase anything by hand.
We also gave three tricky cases their own status, so clients know exactly where they stand: the site answers but no longer shows a version, the site does not answer, or the version has no patch number. We never treat silence as “fixed”.
Day 2: version hidden, watch the behaviour
That still left about 2,000 PHP assets with a hidden version. For those, we looked at how PHP behaves rather than what it says about itself.
The fix also slightly changed how PHP reacts in the code it touched. Given the same harmless request, a vulnerable build returns a diagnostic message that a patched build no longer produces. It is one ordinary request, with no payload and no memory pressure. And it works both ways: if the message appears, the server is exposed; if it disappears while PHP still reports other messages, the server is fixed. If nothing comes back at all, we simply don’t conclude.
One word of caution about some public tools: a server that opens a PHP session tells you it runs PHP, and not much more. One public scanner flagged 356 of our hosts as “likely vulnerable” on that basis. They do run PHP, but whether they are patched is unknown, so we kept them under watch rather than calling them exposed.
The patch curve

Here we count servers rather than hostnames, since ~680 findings sit on ~370 servers. 18 hours after the fix, 15% were patched, 27% after 48 hours, and 31% after five days. The curve rises quickly, then levels off: the easy servers go first, and what remains are older branches, frozen systems and applications that cannot easily move to a new PHP.

In the meantime, the share of vulnerable servers hiding their version grew from 20% to about a third in two days, without a fixed version appearing. Five days after the fix, it is still at 31%. On 22 October, 13 servers of a single client, hosting 99 sites on PHP 5.3.3, stopped showing their version within the same hour, most likely through one configuration change. For a branch that old, the real answer is a migration.
Hiding the version is good hygiene, and harden this exploit, but it is not a patch. It makes verification harder for us, while an attacker can still try without knowing for sure.
Why the curve goes flat
Patching PHP is rarely just “apt upgrade”. I tried it on a my small personal server: an Ubuntu release out of support, so no update was coming, then a full OS upgrade, a broken PHP-FPM, missing extensions and a site down until everything was rebuilt. On one box I know well, that took an evening.
Multiply it by hundreds of servers, business apps tied to one PHP version and hosting providers in the loop, and the flat curve makes sense. For IT teams everywhere, the job is the same: update to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or take your distribution’s backport (a version like 8.5.4 on Ubuntu can be fixed, check the changelog), then restart every PHP process.
And for PHP 8.1 and older, which will never get a fix, there is no patch to wait for: put the mitigations below in place now, get a backport from your vendor if one exists, and plan the migration, or take the server off the internet.
PHP2Shell in a few questions
Am I affected?
If you run PHP older than 8.2.35, 8.3.36, 8.4.27 or 8.5.12, without a backport from your distribution, yes. PHP 8.1 and older will never get a fix. The CMS or framework on top changes nothing: the bug is in PHP itself.
Can someone hack my server with one click?
Very unlikely. Exploitation is complex. The attacker has to chain several bugs, make the server leak memory addresses first, and tune the whole thing to your exact PHP build, distribution and setup. A chain built for one server does not work on the next one. There is no universal exploit to spray across the internet.
So what does a real attack look like?
A targeted one. Someone picks your server, studies it, and tries again and again until the tuning is right. Every failed try pushes PHP workers into memory errors, so it makes noise: errors in your logs, crashed workers, sometimes a slower site. Think of a burglar working on one door for hours, not of a worm. That is also why the crash risk is real even when the takeover fails.
Is it exploited in the wild?
Attacks, yes. Proven compromises, almost none so far. More on that below.
We hid the PHP version. Are we good?
No. It makes the attacker’s tuning harder, and our checks too. But the server is just as vulnerable.
We can’t upgrade right now. What helps?
- Disable session.upload_progress.enabled and restart every PHP process. It closes one path, not all of them.
- Filter multipart/form-data at the proxy or WAF: block it where there’s no upload, cap the size.
- Turn on your CDN/WAF emergency rules, and make sure the origin isn’t reachable directly.
- Watch your logs for bursts of PHP memory errors (Error 50X): that is what an attempt looks like and there is no way an exploit will work without many crashes or error 50X on a server.
- Put staging and pre-prod behind a VPN.
- Plan the migration, or get a backport from your vendor or distro.
Exploited in the wild? Not so fast
Our analysis said the realistic threat was targeted attacks, on servers that match a precise setup. Six days after the leak, the public evidence still points that way. There are many attempts, but very little that shows real exploitation in the real world.
- Attempts, yes. Amazon saw a first one in its honeypots on 17 October, before disclosure, then more from 21 October. But Amazon also says “it’s unclear if any of these attempts would have succeeded”. Cloudflare reports early attacks too, seen at its edge.
- Shells, but on bait. The first solid proof of code execution in the wild came on a honeypot, on 24 October. A honeypot is built to be hit: vulnerable on purpose, no WAF, and here running Apache mod_php with PHP 8.4.26, the exact build the public chain was tuned for. The attacker reused that chain as it was. It proves the exploit works on the right recipe. It says nothing about the thousands of production servers running other builds, distro backports or PHP-FPM behind a proxy.
- Zero Wordpress. Wordfence protects more than 5 million PHP sites. When a major exploitable bug hits, they always see a wave of hacked sites to clean. For PHP2Shell, Mark Maunder counted exactly zero: “Draw your own conclusions.”
- Tooling that finds nothing to hit. SOCRadar found a lab-tested exploit chain on an attacker’s server. Its preconditions are narrow, and none of the sites they screened met them.
- The one real victim was withdrawn. On 25 October, Unit 42 retracted its link between an attack on a European government agency and PHP2Shell: they are “unable to confirm that initial access is associated with the specific PHP2Shell CVEs.” Some advisories still cite that attribution.
- Not in CISA KEV. Neither CVE is in the catalog as of today.
We saw the same thing on our side. On 23 October, pages printing NONAME9619 started to circulate as "the first PHP2Shell victims". The case we examined was an ordinary WordPress webshell, dropped on 19 October, the day before the leak.

None of this means the risk is low. A chain exists, it works on the right target, and AI makes it faster to adapt. It means that, today, PHP2Shell looks like a targeted threat rather than a wave. That is good news for prioritising: patch the servers that show their version first, then the rest.
Takeaways
- Start wide, then rely on evidence. Some noise is fine on day 0. From day 1, each alert should rest on proof.
- Automate the re-check, not only the detection. Hourly retests turned 680 findings into a live view of patching.
- Hiding the version is not patching. The share of silent vulnerable servers went from 20% to about a third, with no fix behind it.
- Be clear about what each check can and can’t prove. And say it in the finding.
- Avoid causing the outage you’re trying to prevent. No destructive checks on production.
- Attempts are not compromises. Lots of attacks, very few proven successes. Even Unit 42 had to walk back its attribution.
- Complex is not impossible. 19 hours for one known target. Much harder at scale, and never quiet.
A dark week, and only the beginning

PHP2Shell did not come alone. In the same few days:
- GitLab (CVE-2026-110264, CVSS 10.0): unauthenticated code execution on self-managed instances, patched on 21 October.
- Node.js and Brotli (CVE-2026-111497, “Node2Shell”): a memory bug in the Brotli decoder, the compression format behind a lot of web traffic. It started as a Node.js issue. It turned out to be in the Brotli library itself, so Bun, Python HTTP clients, curl, Envoy and Istio are in scope too. Many teams who “don’t run Node” discovered they were concerned.
- eMudhra: the private keys of eight intermediate certificate authorities were trivially weak. Browsers blocked them within days. Java trust stores and other forgotten places are another story.
- LiteLLM: a compromised release shipped a credential stealer to anyone who installed it during a two-hour window.
- Plex2Shell: a chain of Plex Media Server bugs, this one actively exploited and added to CISA KEV.
Each one came with its own detection to build, its own alerts and its own round of questions from clients. And this is not a bad week by accident. It is where the numbers are heading.

By 25 October, 86,532 CVEs had been published in 2026. Over the same period of 2025, there were 38,707. That is 2.2 times more, and already 1.8 times all of 2025. Ten years ago, a whole year was about 6,500. October alone is on track for more than 15,000.
AI is a big part of it. It finds bugs faster, it writes exploits faster, and it also floods the space with fake ones. That will not slow down.
So a thought for every CISO and every security team who spent this week juggling all of that. It is not going to get easier. The only way through is to keep a cool head and to prioritise, every time:
- What is actually exposed on the internet, and where?
- Is there proof it is exploitable, or just a version number and a scary name?
- What can be fixed today, and what needs a mitigation while the real fix is planned?
Nobody patches 86,000 CVEs a year. You patch the few that matter to you, quickly, and you keep an eye on the rest. And it is not your job to dig through every advisory, every CVE database and every version table to figure out which of your servers might be affected.
It is not your job either to cross your asset inventory or your EASM tool with those tables, by hand, every time a new CVE comes out. An inventory tells you what you run. It does not tell you what is really exposed, or what an attacker can actually reach.
That is ours. Our job is to cut through the noise: confirm what is really exposed, drop what isn’t, and not raise the alarm on a version number, a scary name or a scanner’s guess. You get a short list of real problems, in the right order, and help to fix them.
If you are still working through your last PHP servers, you are in good company: that is the flat part of the curve, and most teams are there with you.

Sources
- Florent Montel, PHP2Shell: Scary Name, Hard Exploit, Slow Patching, original article on Medium
- Patrowl, PHP2Shell: 2 Critical RCE Flaws in PHP (CVSS 9.2)
- PHP advisories: GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp
- PHP security releases, 21 October 2026
- PHP Foundation, Today’s PHP security release
- JFrog, PHP2Shell: all you need to know
- Amazon Threat Intelligence, exploitation prior to disclosure
- Unit 42, Threat brief: CVE-2026-109618 and CVE-2026-109619 (update of 25 October)
- Steal My Bitcoin, the challenge, and the winner’s write-up
- Beazley Security, BSL-A1233 (update of 25 October)
- SOCRadar, PHP2Shell: affected versions and how to fix
- CVE volume: cve.0xosprey.com, built on CVEProject/cvelistV5
- CISA, Critical vulnerabilities in PHP.
Originally published on medium.com.
Is this exposed on your attack surface?
Patrowl maps your internet-facing assets, fingerprints the versions they run and flags the vulnerable ones, validated by a pentester.
Talk to an expert