Featured image of post The hero gets the bill

The hero gets the bill

AI found a 22-year-old way to own most of the PHP web, and the fix shipped in a day. The bill for being rescued is still landing, and it isn't addressed to the hero.

Last night, at about half six, I’d finished work for the day and was settling in for an hour or two on a new side project (a heads-up display for keeping track of the twenty or so agent sessions I seem to run these days, but that’s a post for another time). My phone went. A friend, a fellow Scout leader and a DevOps sort, had forwarded me an email he’d just had from the Scouts. The UK’s National Cyber Security Centre had put out guidance about a critical flaw affecting some websites, and the Scouts were asking every volunteer who looks after a group, district or county website to check whether it runs PHP and, if it does, to patch it or take it down.

Underneath, he’d written: “I think this is time for thephpboyscout to shine 🙂”

I laughed, told him that, looking at the news, smarter minds than mine had already fixed it, and went back to my side project. A second friend sent me The Register’s write-up a little later and I didn’t even open it. I don’t run PHP in anything these days, it had been a long day, and the shiny new toy was a lot more interesting. My whole reaction was a shrug and a “here we go again, everyone bashing on PHP”.

Then this morning, sat in bed with a coffee and Hailey, doing the morning doom scroll, my feed was wall to wall with it. Reels, TikToks, people talking to camera with that slightly-too-wide-eyed look. And it slowly dawned on me that this wasn’t the usual PHP-shaped news story at all… this was a real one. What stopped me in my tracks was the reaction. Sites were taking themselves offline.

Two bugs and a fire alarm

If you’ve somehow managed to miss it, here’s the short version. Two vulnerabilities, published together on Wednesday as CVE-2026-109618 and CVE-2026-109619, both rated 9.2 out of 10, both allowing someone with no account and no credentials to run their own code on a PHP server. Someone christened them “PHP2Shell”, because of course they did, and they got a logo and a website before most people had read the advisory.

So, PHP lets you cap how much memory a single request can use, the memory_limit setting you’ve almost certainly bumped at some point to stop an import script falling over. When a request goes over that limit, PHP doesn’t hand back a polite error and tidy up, it pulls the fire alarm and legs it out of whatever it was in the middle of (in C terms it uses longjmp, which is about as subtle as it sounds). If it happened to be halfway through building something when the alarm went, that half-built thing gets left lying about, and the clean-up crew that runs at the end of the request later trips over it and treats it as finished. Get the timing right with a carefully stuffed upload and you get to decide what the clean-up crew trips over. The second bug is a cousin of the first: a fatal error leaves a pointer to the previous request’s memory lying around for the next request on the same worker to pick up.

Under Apache’s mod_php the first one fires before a single line of your application runs, with default settings. So it doesn’t matter how good your WordPress plugins are, or how carefully your framework validates input… this lives underneath all of that.

The fix shipped in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, and it’s a big one: 300 files and nearly eleven thousand added lines, getting on for half of them tests, which changes how PHP runs out of memory altogether. And the advice from the PHP project, the agencies and every explainer on my feed boiled down to the same thing: there’s no setting that makes this safe, so upgrade. (If you want to check my working on any of what follows, I’ve put the research notes up alongside this post, warts and all.)

Three facepalms in twenty-four hours

I’d love to tell you the disclosure went smoothly. The PHP Foundation’s own write-up says it went well right up until two days before release, and then the wheels came off one at a time.

On Tuesday afternoon the two CVE numbers started circulating on forums and social media, complete with the wrong release time and a wrong severity score of 10.0. Someone with early access couldn’t keep it to themselves. That’s facepalm number one, and it’s negligence, plain and simple. PHP responded by bringing everything forward a day. That same evening Calif, one of the security firms that had reported the bugs, accidentally published a video of their proof of concept working against a string of real applications (with a dancing anime character in the corner, which one Hacker News commenter assured everyone is “a sign of credibility” in some circles). It was taken down, but copies were doing the rounds soon after. Facepalm number two, and that one’s just sloppy.

And then, at 07:41 UTC on Wednesday, more than six hours before the coordinated release, Chainguard published container images with the fix already built in. Facepalm number three!

That’s the one that really got my blood up when I first read it, so I went digging. The fix commit was written days earlier, but going by the git history it only landed on PHP’s public branches at 14:06 UTC on Wednesday, about half an hour after the advisories went live. (Git timestamps aren’t proof of when something was pushed, so take that as “no sign of it in public any earlier” rather than gospel.) The CVE leak and the video gave away that something was coming and roughly where to look. Chainguard’s images gave away the patched code itself, as binaries anyone can diff against the old ones, six and a half hours before the source was public. With AI in the mix, six hours is an eternity.

Now, in fairness, Chainguard’s CEO has his side of it. He says he made the call once exploits were being reported in the wild, and he told The Register that his team had found the same bugs independently, with an AI harness, and that he wouldn’t do anything differently. I get why. The reports flying about at the time turned out to be reports of attempts (more on that below), but if you really believe people are being hit, sitting on a fix feels indefensible. And later that same evening, an account called “The Chainguard Team” dropped $50,000 into the PHP Foundation’s collective, and that took a fair bit of the wind out of my sails. A case of too little, too late, perhaps, but it’s not nothing.

What still frustrates me is the coordination. If it was right for one distributor to ship early, it was right for all of them, and Docker Hub makes that point better than I can. The official PHP image on Docker Hub has over 1.38 billion pulls (some of them mine). It’s probably the single biggest way PHP gets into the containerised world, and as far as I can tell it wasn’t in the early circle at all. The update was opened by a community contributor ten minutes after the advisories went public, then sat in the usual review and build pipeline, then hit a deploy glitch that left the tags visible on Docker Hub with no amd64 image behind them. People were building their own from source and asking to go to bed. It was close to three hours before most folk could pull a patched image. So the company that sells hardened images got the fix in advance, and the free default that so much of the containerised PHP world builds on got it at the same moment as the attackers did… and then dawdled!

Smarter minds, and not very many of them

When I told my friend that smarter minds than mine had already fixed it, I meant it. I’ve met a fair few of the people who work on PHP over the years at conferences and the like, and they’re far more skilled and capable than I am (I’m not being modest, they really are). My grand contribution to the PHP project is helping write some tests at a couple of hackathons. So the fix being out before I’d even heard of the problem is about the order I’d expect.

What I hadn’t thought about was how few of them there are. The fix commit has five authors. Looking at the history of the engine core itself, about five people account for half of this year’s commits (commits aren’t the same thing as work, I know, but it gives you an idea). The person coordinating the security effort across the wider ecosystem is in a six-month post paid for by a grant. The maintainer of a volunteer package repository that a lot of Red Hat-family servers rely on was on holiday, and php.net’s own advisory had to tell people to switch away from it because it “will not have updates today”. And by teatime Mastodon was busy passing round the Patreon link for the man whose PHP packages a great many Debian and Ubuntu servers pull from.

And the inbox isn’t getting any emptier. PHP’s own security advisories went from 14 in the whole of 2025 to 29 so far this year, with ten weeks still to run, plus over a hundred reports since July that turned out to be duplicates, invalid or impossible to reproduce. Someone from that same handful still had to open and read every one of them.

The Foundation hasn’t hidden any of this. Its write-up says, in as many words, “We won’t pretend otherwise: the delay was a structural failure, caused by both the security team’s decision process and its lack of resources”. It explains that the first signs of the memory problem turned up back in 2024, when a developer the Foundation pays was fixing something else from an external audit, and that the security team, working by consensus, couldn’t agree which of several imperfect fixes to ship until there was a proof that it could be exploited. They’re moving to majority decisions so that doesn’t happen again.

And that brings me to a coincidence I had no hand in at all. This morning another post of mine went out, written weeks ago and sat in the queue, arguing that “blameless” was only ever meant to make it safe to tell the truth, and that the truth usually starts with your own share. I couldn’t have asked for a better worked example if I’d tried. The PHP Foundation named its own failure first, plainly, and then named everyone else’s. That’s a charming coincidence on the scheduling, and it hits home all the same. If you’ve read the comments on a few forums today you’ll know not everyone agrees they handled it well (the engineers’ corners of the internet are a lot harder on them than the sysadmins’), but I think they responded about as well as anyone could have, and they’ve been open about the parts they got wrong.

Every hero gets a bill

There’s a scene early on in The Incredibles that I keep coming back to. The supers are beloved. They stop the robbery, catch the villain, save the day… and then the lawsuits start. A man who was trying to jump off a building sues because he didn’t want to be saved. The train passengers want compensation for their whiplash. The bill for all that heroism lands, the public turns, and the heroes end up in hiding.

My first reaction to the AI part of this story was that AI was the hero, and I still think it is. Calif, OpenAI and Wiz found these flaws with frontier models in a matter of weeks, after people had been reading that code for well over a decade. That’s two critical bugs found and fixed by people on our side of the fence, before (as far as anyone can yet show) anyone used them to do real harm. That’s a rescue.

But the bill came with it. AI made finding these bugs fast, and it made exploiting them fast too. Trail of Bits wrote working exploits for a stock MediaWiki install and their CEO said the first one took about four hours, start to finish, while being clear that it “still requires expertise, even when you’re being aided by an LLM”. What used to be weeks of tedious work by a rare specialist is now an afternoon for a capable one with a model at their elbow. I wrote about curl’s bug bounty back in May and argued that AI had made the cheap half of security work free and left the expensive half where it was. This is the same story a little further down the line. Finding is cheap now, and fixing is still five people and a grant.

That’s also why, a couple of years ago, I’d have called taking your site offline over a PHP bug a massive overreaction, and today I don’t. A bad actor with a model can turn an advisory into a working attack faster than most of us can get a change approved. And a dull-looking site is rarely the target in itself. It’s the first door. Leak a bit of state and get code running, and that site is a foothold into the next system, and the one after that, until you reach something that matters. We’ve already watched a frontier model climb out of what its own makers thought was a closed sandbox to get at what it wanted. Flaws like these make that kind of hopping trivial, and that’s before you get to whatever personal data leaks out along the way and turns up later in a very convincing phishing email.

So I think the governments got this one right, even if it was drastic. The BSI, the Dutch NCSC, France’s ANSSI, the US NSA and our own NCSC (which reached me via the Scouts!) all told people to take sites down if they couldn’t patch. Japan’s prime minister posted an “urgent request” late at night telling organisations to check their sites, and Japanese Wikipedia’s page on PHP went from 66 visits the day before to over a hundred thousand. On one volunteer-run tracker the median site went dark for about four and a half hours, the Dutch parliament and Edinburgh council among them.

And as of this morning, despite one headline talking about millions of exploit attempts, I can’t find anyone who has publicly confirmed a single successful compromise. One threat-intelligence group quietly took its “exploited in the wild” label off the story early this morning, on the grounds that sprays against targets aren’t proof of anyone getting in. That’s good news, but it’s only a day old. The question that bothers me most is the one I don’t think anyone can answer. Amazon’s honeypots caught an exploitation attempt on the 17th, four days before anyone outside the circle was meant to know, and the root of the problem has been documented in public for over twenty years. How many people knew about this, and used it, before any of us did?

Hopes of the father

Whenever PHP has a bad day, the same crowd turns up. PHP is a joke, PHP is dead, just use anything else. You’ve seen the threads, I’m sure. I’ll grant that PHP has made some questionable decisions in its time, and plenty of what people call technical debt in the language is just the scar tissue of it growing up in public. But Rasmus Lerdorf put it together in 1994 to make web pages a bit more interactive, which makes it thirty-two years old… older than some of the people sneering at it! It’s still sat at number twelve on TIOBE this month (one place behind Go, my current favourite, and somewhat bafflingly ahead of COBOL at sixteen, which makes me think I need a better yardstick than TIOBE). And it got me out of a run of humdrum menial jobs and into the career I love. It’s been a long-distance relationship for a while now, but the respect has never gone anywhere.

Some people will call PHP2Shell the sins of the father. I’d call it the hopes of the father. Stefan Esser described this very class of bug in July 2004, interrupting PHP at an unsafe moment by running it out of memory, and rated it critical. In 2019 a closely related bug report came in, and the maintainer looking at it wrote that “Allocation failure strictly speaking is not a crash”, and the fix that followed dealt with that report and moved on. In 2024 the security team saw the first signs again, and with no known path to remote code execution, couldn’t settle on a fix. Each one of those calls was reasonable when it was made. The people who built PHP built it as well as they could, with what they knew, for the world in front of them. Nobody has a crystal ball.

I don’t think any of those calls were wrong about the bug… where they came unstuck was the price. Each one was a bet that turning this into a working exploit was too much effort for too little reward, and for twenty years that bet paid off. The bug didn’t change in 2026, but what it costs to exploit it certainly did.

And that’s the part of this story that isn’t really about PHP at all. I’d wager most codebases that have been around long enough have a drawer of bugs that someone marked as “not exploitable in practice”, and in practice was always a statement about cost. Java, the C underneath half the languages we use, and yes, my own estate. I’ve got private bug reports sat in a tracker right now, some marked exploitable and some marked not, and triaging them is my job and nobody else’s. Reading about PHP’s 2019 bug this morning, I suspect a few of mine need re-reading with fresh eyes, because the price on all of them dropped this year too.

Patch now, they said

So, to the advice everyone’s been shouting for two days, mine included. Upgrade. It’s the right advice, but it’s also advice that a huge chunk of the PHP web can’t simply take.

WordPress publishes its own statistics on which PHP versions its installs run, and as of this morning 35.8% of them are on a version of PHP the PHP project no longer releases fixes for. PHP 7.4 alone is about one in six. Another 23.7% are on 8.2, which stops getting security fixes on the 31st of December. So by New Year, unless an awful lot changes very quickly, nearly sixty percent of WordPress will be running a PHP with no upstream fixes at all. W3Techs’ survey of the wider PHP web lands in the same place by a different route, at about 48%.

The PHP security team did go above and beyond here, publishing minimal source backports for 7.4, 8.0 and 8.1 so that people building from source weren’t left with nothing, and the LTS vendors and Linux distributions covered a lot of servers. Debian and Ubuntu had packages out within half an hour of the advisories, which is what the embargo was for. But for a site two major versions behind on shared hosting, “just upgrade” is a project, and a fiddly one at that: a jump across breaking changes, an audit of plugins that may not have been touched in years, and a theme that might fall over the moment you look at it. And WordPress itself still says it works on 7.4, even while warning that it’s past end of life.

So who runs that project for the Scout group’s website? Or the parish council, or the little charity, or the volunteer who looks after the cricket club on a Tuesday night? Scouting Nederland’s site was dark for an hour and three quarters on Wednesday. The Scouts here were emailing their volunteers asking them to check. I’ve stared, hard, at more than one company still running Java 8 in production, and I’ll keep doing it. But a big company on Java 8 has made a choice, and the volunteer with the group website mostly didn’t know there was one to make.

Time for the PHP Boy Scout to shine

So my friend was half right. It was time for the PHP Boy Scout to shine… I just think it’s a lot less about knowing PHP and a lot more about the old Baden-Powell line this blog’s motto is borrowed from, the one about leaving things a little better than you found them. The smarter minds fixed PHP in a day. The heroes got the applause, and the bill went to five people, a few volunteers on holiday, and everyone who now has to find out what version of PHP their club website is running.

As for me, I’ve a drawer of my own to go through.

Built with Hugo · Theme Stack designed by Jimmy