PHP2Shell: the critical PHP vulnerability and how to stay safe

Marcos Marcolin • October 27, 2026 • 13 min read

PHP PHP SRC Open Source

Well, wouldn't you know it: PHP is once again one of the most talked-about topics on the internet worldwide. 🐘

Anyone who says PHP is dead must have been surprised: last week it made mainstream news, led European governments to take websites offline, and even the FBI and the NSA issued alerts. Only this time, the reason wasn't a new feature.

PHP shipped an emergency release to fix two vulnerabilities that allow unauthenticated remote code execution, nicknamed PHP2Shell: CVE-2026-109618 and CVE-2026-109619.

If you don't have time to read the rest, take just this: if your server runs PHP handling web requests, update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or later).

Table of contents

First, what is a CVE?

If you've never had to deal with this, three acronyms will show up a lot here:

In other words, when someone says "critical CVE with RCE", they mean: a catalogued flaw, severity at the top of the scale, and the attacker can take over the server.

What happened

The release was scheduled for October 22, but on the 20th the CVE IDs leaked online with the wrong score, and shortly after Calif, the company that reported the flaws, mistakenly published a video of the exploit. PHP moved everything up by a day.

That's when the story left the dev bubble. CERT-EU recommended taking affected sites offline, and governments in Finland, the Netherlands, France and Germany shut down official pages. In the United States, the FBI issued an alert recommending an immediate update and, if that's not possible, taking the system offline. Here in Brazil, Anatel and several courts suspended access to SEI, a government document system, as a precaution. The press covered it everywhere, including g1, Brazil's largest news portal, which reported that the flaw had been discovered two years earlier.

And that's true. According to the PHP Foundation, the first signs of the problem showed up in a 2024 audit, but the fix stalled for months. It only became top priority in August 2026, when Calif and OpenAI reported the same flaw, both using AI in their research. The Foundation itself called the delay "a structural failure", caused by a lack of resources and a slow decision process. I found that very honest of them.

The two flaws

CVE-2026-109618: the OOM that leaves a mess behind

When PHP hits the memory_limit, it uses longjmp() to abandon the request on the spot. The problem is that, at some points, the request memory hasn't been fully initialized yet, and shutdown later reads that memory as if it were valid.

On Apache with mod_php, this happens while the request body is being parsed. In other words, it doesn't matter whether your code is secure: having PHP answer HTTP is enough. Details in advisory GHSA-w8hw-w9fx-j79w.

CVE-2026-109619: the garbage left for the next request

PHP workers handle many requests in the same process. If a fatal error or an OOM interrupts a request, some global pointers aren't reset. The memory is freed, but the next request on the same worker uses that stale pointer. A classic use after free.

The confirmed cases involve PHP 8.5's fatal_error_backtraces and session upload progress, which is enabled by default. And one important detail: using @ or turning off display_errors does not protect you. Details in advisory GHSA-6wmf-4rv5-p3rp.

Who is at risk and how severe is it?

According to PHP, every installation that handles web requests. The risk depends on the SAPI (the way PHP runs on the server) and is highest on mod_php:

SAPI CVE-2026-109618 (OOM) CVE-2026-109619 (global state)
Apache mod_php Exploit confirmed, independent of the application Exploit confirmed
PHP-FPM RCE confirmed, through application code RCE validated via upload progress
FrankenPHP, LiteSpeed, php-cgi Likely or in scope of the advisory Conditional

Both were rated CVSS v4.0 9.2:

It's not a 10 because exploiting it is complex (more on that below). But in practice, I wouldn't give that difference much weight: it's unauthenticated RCE in one of the most widely used languages on the web. And the attack also leaks server memory, which can expose sessions and passwords even when it doesn't get to run code. Exploitation has already been confirmed, and CISA added both to its catalog of known exploited vulnerabilities.

Fake news and exaggerations

How PHP fixed it

I went looking for the PR with the fix and found out that there is no public PR. Security fixes in PHP are developed privately and land directly as a commit on release day. In this case, it was commit 3584a98, "Avoid state corruption via bailout", which closes both CVEs.

And it's not small: more than 300 files and about 10,700 lines added, much of it tests simulating OOM across practically the entire standard library. In practice:

The Foundation also announced more people on the security team, new audits and majority voting instead of consensus, so this doesn't stall again. And it made a request I'll echo here: if your company depends on PHP, take a look at the sponsorship program.

How do I know if I'm vulnerable?

You're protected if you're on one of these versions or later:

Branch Fixed version
8.5 8.5.12
8.4 8.4.27
8.3 8.3.36
8.2 8.2.35
7.4, 8.0 and 8.1 No release, only backport branches to build yourself: 7.4, 8.0 and 8.1
7.3 or older No fix from PHP, only via distros or LTS vendors

If you use Debian or Ubuntu with Ondřej Surý's packages (ppa:ondrej/php or deb.sury.org), he has already backported the fix to 5.6, 7.4, 8.0 and 8.1, for free. For 7.0 to 7.3, there are paid options such as Freexian, TuxCare and Ubuntu Pro (Sansec put together the list). The official php Docker image, on the other hand, gets no fix for 8.1 or older.

To check, create a temporary file on the server, open it in the browser (so it runs on the same SAPI as your site) and delete it right after:

<?php
$fixed = ['8.6' => '8.6.0RC4', '8.5' => '8.5.12', '8.4' => '8.4.27', '8.3' => '8.3.36', '8.2' => '8.2.35'];
$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;

echo 'Version: ', PHP_VERSION, ' | SAPI: ', PHP_SAPI, PHP_EOL;

if (!isset($fixed[$branch])) {
    echo "Unsupported branch: there is no official release with the fix", PHP_EOL;
} elseif (version_compare(PHP_VERSION, $fixed[$branch], '<')) {
    echo "VULNERABLE: update to {$fixed[$branch]} or later", PHP_EOL;
} else {
    echo "Version includes the fix", PHP_EOL;
}

If SAPI shows apache2handler, you're on mod_php, the most exposed setup. To scan many sites at once, ProjectDiscovery published a Nuclei template that only reads the version from the header, without exploiting anything.

A few things to watch out for:

What if I've already been attacked?

Look in your logs for Apache or PHP-FPM spawning sh, curl or wget, User-Agents containing "php2shell" or the CVE number, and spikes of Allowed memory size exhausted. Found something? Treat it as an incident, not just an update.

I can't update right now, what now?

PHP published temporary mitigations. They reduce the risk, but don't solve it:

; php.ini, FPM pool or server config
session.upload_progress.enabled = 0
memory_limit = -1          ; via php_admin_value
post_max_size = 8M         ; 32M if you need uploads
upload_max_filesize = 2M

On PHP 8.5, also set fatal_error_backtraces=0, limit request sizes in Nginx or Apache and restart every worker. Yes, memory_limit = -1 looks strange, but the exploit relies precisely on hitting that limit in a predictable way. It's a stopgap, not a solution.

So is PHP done for? Of course not

With so many headlines, some people are already saying "it's time to drop PHP". Easy there.

This flaw is not easy to exploit. The exploit depends on memory layout, the build and the number of workers. The first confirmed RCE in the wild was in a deliberately favorable environment, with a single worker, and the PoCs that circulated before the fix didn't work. That's no reason to relax, but it's also not a case of "anyone can break into any PHP site with one command".

And it wasn't just PHP. That same week, Node.js fixed Node2Shell (CVE-2026-111497), a flaw in the Brotli decoder that also allows remote code execution. Express 5, Koa and fetch() itself decompress Brotli by default. And since the problem is in Google's library, it also hit Bun, Python, .NET and other tools. Including PHP itself on Windows, which got new builds because of it.

What's happening is bigger than any one language: AI has changed the pace of security, on both sides. It's finding old flaws across the whole ecosystem (PHP has published 15 CVEs since July, and the Node.js flaw was also reported by an AI tool), and it sped up the response: the Foundation now uses AI to validate reports and exploits and to group the ones that share a root cause. But the fix is still human work, in their words, "a person makes every call and works on every patch". And the same AI produced more than 100 invalid or duplicate reports, and it's in attackers' hands too.

A mature language isn't one that never has flaws, it's one that fixes them quickly and transparently when they show up.

Staying up to date is not optional

For me, this is the main lesson. Anyone on a supported branch just had to roll out a patch. Anyone on 8.1 or older got no release: at best a backport branch to build by hand or a maintainer's package, and from 7.3 down, not even that from PHP. And that's not a small group: Mallory counted about 884,000 exposed servers in that situation the day before the fix.

What applies to any team:

Conclusion

PHP2Shell was serious: unauthenticated RCE, a confirmed exploit and government websites offline. And there's no ignoring that the problem had been known since 2024.

At the same time, I liked how the Foundation handled it: it owned the mistake publicly, explained what will change and delivered a fix that genuinely changes how PHP deals with running out of memory. That's the sign of a mature project.

The lesson is the usual one, but it still holds: update, and set up your environment so that updating is easy next time.

See you next time, cheers! 🐘

References:

Share: