PHP2Shell, and Other Things Nobody Could Possibly Have Seen Coming

I'm shocked. Truly. A thirty-year-old language with a runtime written in C, running about seven in ten websites whose server-side language you can see, turned out to have memory-safety bugs. And AI found them. Who could have possibly predicted that, apart from everyone?

Get the fainting couch out of storage, because this is the week enterprise discovered that PHP is software.

What actually happened

On October 21 the PHP project shipped emergency releases (8.5.12, 8.4.27, 8.3.36 and 8.2.35) for two critical flaws, CVE-2026-109618 and CVE-2026-109619, now known together as PHP2Shell. Both are unauthenticated remote code execution. Both score 9.2 on CVSS 4.0. Between them they cover every PHP release from 5.4 through 8.5, which is a polite way of saying "all of it, for about a decade."

The technical write-up from Wiz is worth reading if you like your horror detailed. The short version: when PHP hits an out-of-memory fatal error at just the wrong moment, it tears down the request's memory but leaves pointers to it lying around in global state. The next request on the same worker picks them up. From there you get use-after-free, a memory leak that hands your process memory back to an attacker inside a PHPSESSID cookie, and type confusion in OPcache's shared memory. The researchers chained those into working, unauthenticated RCE against WordPress, Drupal and Joomla. The application never had to call session_start(). It didn't have to do anything. It only had to be PHP.

Who found them? Two teams, independently. Ryan Austin at Calif used OpenAI's GPT-5.6 Sol as part of OpenAI's "Patch the Planet" project. Wiz used Gemini 4 Argon, GPT-5.6 Sol and its own tooling. They reported in August. The fix shipped in October.

Then everything happened at once:

And my favorite part: the out-of-memory problem behind one of these flaws was spotted by a PHP core developer during a 2024 audit. It wasn't fixed, because nobody could agree on the fix and nobody had shown it could be exploited. Then a model showed it could be. Wiz had working RCE against Drupal the day after it found its first bug.

The part where everyone acts surprised

The PHP Foundation put it more calmly than I would have: "Tools now find bugs faster than ever, for defenders and attackers alike, and the number of reports will keep growing."

That isn't a prediction. It's a description of the last twelve months. Models read code faster than people, they don't get bored, and they're very good at the exact kind of bug that hides in a twenty-year-old C codebase: the error path nobody tests, the global nobody resets, the "that can't happen in practice" that happens in practice.

So every time this happens, and it will keep happening, I'd like us to skip the bit where a CISO goes on a news site and says nobody could have anticipated it. You could have anticipated it. You had a decade of PHP CVEs and a Log4Shell to anticipate it with.

It's not the PHP you wrote. It's the C underneath it.

To be fair to PHP developers, this wasn't your code. Your WordPress plugin is guilty of plenty, but not this. PHP2Shell lives in the engine: hand-managed memory in C, freed and then used again. That's a whole class of bug, and the industry has known how to stop writing it for years.

Rust makes use-after-free a compile error. Go has a garbage collector and no pointer arithmetic, so a dangling pointer to a freed request heap isn't something you can write by accident. Neither one makes you immune to bad logic, but each takes the single biggest category of critical CVE off the table. Every serious security body has been telling us to move to memory-safe languages for years. We nodded and renewed the support contract.

The stateless joke

Here's the irony that should keep architects up at night. PHP's best selling point was always its "shared-nothing" model: every request starts clean and dies clean, and nothing carries over. Stateless before stateless was cool.

PHP2Shell is entirely about things that carried over. Stale global state, pointers left from the previous request, shared OPcache memory that one worker thought it owned. The model was stateless on the slide deck and stateful in the process.

That's the lesson, and it isn't really about PHP. Long-lived workers and app servers that pile up state between requests are attack surface. The more your service remembers, the more there is to corrupt. Design services that hold nothing between requests, keep state in a database that was built to hold it, and replace instances instead of nursing them. Then a bug like this has nowhere to live.

Java, holding PHP's beer

Java didn't get left out, either. Also on October 21, Oracle shipped an out-of-band Java update for CVE-2026-91403, a 9.1 in the JSSE TLS stack that covers Java 8 through 27. To be fair, that one is about trusting compromised certificate authority certificates, not memory corruption, and it's a lot less dramatic than PHP2Shell. But it means a lot of enterprises spent Wednesday emergency-patching both of the platforms their whole business runs on.

Java has its own history here, and it fits in one word: Log4Shell. Nobody who was on call in December 2021 has forgotten it. Java is memory-safe for your code, but the enterprise Java estate is a stack of app servers, decades of transitive dependencies and frameworks that deserialize whatever you hand them. It's stateful and huge. The bill always comes due on a Friday.

Sunk cost isn't a strategy

So why are PHP and Java still everywhere in enterprise? Not because anybody sat down in 2026 and chose them. It's because of what's already been spent: the codebase, the team, the vendor contract, the twelve-year-old app nobody understands but everybody depends on. The money's gone. Nobody can get it back by staying.

The sunk cost fallacy says, "We've put too much into this to walk away." The honest version says, "We've put too much into this to keep finding out the hard way." The cost of staying used to be a slow trickle of CVEs your team could patch on a schedule. The cost of staying now is a model that reads your stack faster than your vendor does, and attackers who don't have to wait for a press release.

What I'd actually do

Not a big-bang rewrite. Those fail too, and then you're stuck with two legacy systems. Here's the less exciting plan that works:

  • New services in memory-safe languages. Go for network services and tooling: it's boring in the best way, compiles to one static binary and has a standard library that already does HTTP properly. Rust where performance or safety really matter: parsers, proxies, anything handling untrusted input at scale. TypeScript for the front end and the glue, with strict types turned on so the compiler catches what a code review misses.
  • Strangle the old stuff, don't storm it. Put a proxy in front of the legacy app and move one route at a time to the new service. Every route you move is attack surface you delete.
  • Stateless by default. No session data in worker memory, no sticky sessions and no "just cache it in a global." Short-lived containers built from known images, replaced rather than patched in place.
  • Build what you can ahead of time. A lot of what runs on PHP doesn't need to run at all. Marketing sites, docs and blogs can be static files. No code running on a request means no request-time RCE.
  • Budget for the migration like you budget for the breach. Because one of those numbers is going to be paid either way.

The fine print

I'm semi-sarcastic, not unhinged, so a few honest caveats. Go and Rust don't fix logic bugs, injection or bad authentication. Unsafe Rust exists. Node runs on V8, which is C++ under the hood and has had its own bad days. A memory-safe language shrinks the blast radius. It doesn't excuse you from patching, reviewing or thinking.

And the people who maintain PHP aren't the villains here. It's a volunteer-heavy project with a nonprofit foundation behind it, and it shipped fixes for four bugs across four supported branches, plus source patches for three end-of-life ones, while half the internet watched. Enterprises that built billion-dollar businesses on PHP and never funded it don't get to act betrayed when it needs help.

For the record

This site ran on PHP until October 13. It's now plain files, built ahead of time by HotDog CMS, which is written in Go, and served with no code running on a request. That wasn't foresight. Eight days later, when PHP2Shell landed, there was simply nothing here to patch.

That's the whole argument, really. The best patch is the code you're not running.