Skip to main content

PHP2Shell, from someone who mostly writes JavaScript

Two critical PHP bugs were patched yesterday after a messy disclosure, and even if you never touch PHP, JavaScript developers should look.

Updated 7 min readMarkdown
PHP2Shell, from someone who mostly writes JavaScript

I don't write PHP these days. Almost everything I run is Node and Go. But yesterday most of my feeds were about PHP, and so was a good part of my day, so these are my notes while it's all still fresh.

The two vulnerabilities are CVE-2026-109618 and CVE-2026-109619, and people have started calling them PHP2Shell. The PHP team rated both 9.2 on CVSS v4, and watchTowr went further and scored it 10. Both can lead to remote code execution without any authentication, and the reported range of affected versions is wide, PHP 5.4 all the way to 8.5.

Branch Fixed in
8.5 8.5.12
8.4 8.4.27
8.3 8.3.36
8.2 8.2.35
7.4, 8.0, 8.1 source-only backports, no official binaries

If you're on a distribution package, the version number may not match those, because distros backport fixes into their own builds. Check your vendor's advisory rather than just php -v.

What the two bugs are

The GitHub advisories are titled "RCE via OOM corruption" and "RCE via stale global state", and I'll stay at the level they describe.

CVE-2026-109618 is about what happens when PHP runs out of memory partway through certain work. The operation stops in the middle, some request data is left half set up, and the cleanup code then runs over that broken state. According to the reports this path was spotted in a 2024 audit and never fixed, because nobody could agree on the fix and nobody had shown it was exploitable. I find that oddly relatable. Plenty of bugs in my own work have survived for exactly that reason.

CVE-2026-109619 is about state leaking between requests. A PHP worker handles many requests over its life, and if references to one request's data survive after that memory has been freed, a later request on the same worker can touch memory that now belongs to something else, which is a use-after-free. The advisories also say researchers showed it could disclose worker memory without authentication, which means things like session tokens and passwords from other people's requests.

Both affect the usual ways PHP is served: Apache mod_php (called out as the most urgent), PHP-FPM, php-cgi, FrankenPHP and LiteSpeed.

A messy week for a disclosure

The plan was a coordinated release. PHP announced on 20 October that fixes were coming, and the advisories went up on GitHub on 21 October at 13:34 UTC, about 26 minutes ahead of the announced time. Before that, though, things leaked. It surfaced on Hacker News early, a proof-of-concept video was released by mistake, and Chainguard shipped patched builds ahead of the release, which meant anyone could diff the old and new code to find where the bugs were. Amazon reported seeing an attempt in its honeypots on 17 October, four days before the advisories went up.

Reports on exploitation don't agree with each other. CISA said on 21 October that it hadn't observed any, and told organisations to patch immediately or take PHP systems offline if they couldn't. watchTowr said the same day that it was seeing exploitation in its own honeypots. Cloudflare pushed temporary WAF rules, and a lot of public sector sites in Europe went offline as a precaution, which tells you how seriously people were taking it.

The bugs were found independently by Ryan Austin at Calif and by Wiz, and both used AI models as part of the work. I suspect that's what people will remember about this one after the patch is old news. PHP's core is decades of C with a lot of eyes on it, and if models can now walk through old C and find memory bugs faster than maintainers can review fixes, we'll see more weeks like this, in projects that often have one or two volunteers looking after them. The finding has sped up a lot, and the fixing still depends on a small number of people having the time.

Why it felt familiar

Ten months ago it was React2Shell for those of us on the JavaScript side. Different language and a different kind of bug, but a similar shape: in both cases the dangerous code was in the runtime or framework layer that turns an incoming request into something your code can use. You could have written perfect application code and still been exposed.

The stale state bug also maps quite neatly onto Node. A PHP-FPM worker carrying something from request A into request B is, conceptually, the same mistake as a Node server keeping per-user data in module scope, which is the kind of thing that turns up in code review now and then:

let currentUser: User | null = null;

export async function handler(req: Request) {
  currentUser = await getUser(req); // shared by every concurrent request
  return render(currentUser);
}

A Node process is long-lived by design, so anything at module level is shared across every request it serves. PHP's model was meant to make that kind of leak hard, because every request starts fresh, and this bug sat in the machinery that does the starting fresh.

What to do about it

Update to the fixed version for your branch. That's the actual fix, and the PHP team were clear that the stopgaps aren't complete. If you really can't update today, these were published as temporary measures:

  • Set session.upload_progress.enabled=0 in php.ini, the FPM pool or the server config, not with ini_set() or .user.ini, which apply too late, and restart the workers.
  • On PHP 8.5, set fatal_error_backtraces=0 the same way.
  • Keep post_max_size sensible (8M with no uploads, 32M if you take them), with upload_max_filesize below it, and limit request sizes at the web server or proxy.
  • Use container or cgroup memory limits.
  • On PHP-FPM, pm.max_requests=1 was mentioned as optional. It costs performance and doesn't replace patching.
; FPM pool, as a stopgap until you can upgrade
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M

If a server was exposed and unpatched during the window, I'd also think about rotating anything that lived in worker memory, like session keys, API tokens and database passwords, given the memory disclosure part.

On my own box I was fairly sure nothing was PHP, but fairly sure isn't sure, so:

docker ps --format '{{.Image}}\t{{.Names}}' | grep -i php
docker images --format '{{.Repository}}:{{.Tag}}' | grep -iE 'php|wordpress|nextcloud'

Nothing came back. It's all Node, Go, Postgres and the Supabase stack. I did nudge a couple of friends who run WordPress on shared hosting to check what PHP version their host was on, since that's about all you can do on shared hosting.

Node isn't immune to any of this. We have our own C++ runtime underneath, our own long-lived processes and our own framework serialisation layers, and Node's security release came out the same day with a Brotli decoder bug rated high (I wrote that one up separately). The thing I keep coming back to is how quickly you can get a fixed runtime into production on a random Wednesday: knowing which versions you run, having a build that pulls the new base image, and being able to redeploy in minutes without a ceremony. That part's in our hands whatever language we write.


First published on my blog. Cover photo: Charles J. Sharp, CC BY-SA 4.0, via Wikimedia Commons (source).

About the author

  • Full stack engineer in London with 5+ years on production web platforms. I lead AI-assisted modernisation at Xanda: React and Go for the UK energy market, a government claims portal, and moving old Java and C++ systems onto the cloud. Outside work I run a small home server behind a Cloudflare Tunnel with a handful of public tools on it: Doorstep (rental alerts across Rightmove, OnTheMarket and OpenRent), SeenSooner (NHS A&E and planned-care waits), GridPulse (live GB grid carbon), Sift (LLM ticket triage that survives model failures) and AI Radar (a news digest written hourly by an n8n workflow). I write here about React and Next.js upgrades, Node, coding with AI agents, and the CVEs I've had to patch. Originals live at blogs.kishankanejiiya.tech.

No comments yet. Be the first to comment.