blog

PHP2Shell: when AI writes the exploit

On 21 October, PHP shipped patches for two critical flaws, CVE-2026-109618 and CVE-2026-109619, that let anyone run code on the server without logging in. The news isn’t the bug, which had been there for more than twenty years: it’s that AI found it, and that once it leaked early, rebuilding the exploit took a couple of hours. Patching is still on you, and how far a PHP worker reaches the day someone gets in matters just as much.

What shipped on 21 October

If you run PHP anywhere, a WordPress site, an internal panel, a legacy API, this is one of those weeks you patch without waiting. On 21 October, PHP released 8.5.12, 8.4.27, 8.3.36 and 8.2.35, fixing two flaws: CVE-2026-109618 (“RCE via OOM corruption”) and CVE-2026-109619 (“RCE via stale global state”). Both score 9.2 out of 10 on CVSS 4.0, and both allow code execution with no credentials. They affect any PHP serving web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The worst case is mod_php, where PHP says it demonstrated full remote code execution with the default configuration, before a single line of your application runs. The 8.1 branch and older no longer get patches from the project, though there are minimal backports for 7.4, 8.0 and 8.1. And watch the version number: many distributions patch without bumping it (Ubuntu 24.04 fixes it in 8.3.6-0ubuntu0.24.04.13). Security folks call it php2shell.

A bug more than twenty years old

The bug itself isn’t new. In 2004, Stefan Esser showed that PHP’s memory limit could be used to corrupt memory remotely, and didn’t publish the exploit. In 2024, PHP’s first external security audit in years, funded by the Sovereign Tech Agency, organized through OSTIF and carried out by Quarkslab, ran into a memory-exhaustion problem again. An exploit was theorized, but no concrete vector turned up, so it went unfixed. The security team had several ways to close it and each one broke something (backward compatibility, a fresh denial-of-service risk, or hundreds of third-party extensions), and the decision stalled and restarted several times over months. The PHP Foundation itself has called it “a structural failure,” of its decision process and its lack of resources.

What changed was AI

What turned that theoretical problem into a reliable exploit was AI. Two teams got there separately. Calif, a security firm invited into OpenAI’s “Patch the Planet” initiative, found the first flaw on 1 August using the GPT-5.6 Sol model, and reported it to PHP on the 11th. Wiz found it on its own on 19 August with its Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol, and reported it on the 24th. Wiz puts it this way: one person did in a week what an experienced team would have needed months for, and the models adapted the exploit across applications and configurations in hours. In Calif’s account, the human was still in charge, picking a target they knew and starting from Esser’s old leads, but the slow work of reviewing the memory allocator and chaining the flaws was the agents’. It’s no magic button either: by their own account, no model pulls out “all bugs” from a prompt.

The exploit, a couple of hours away

The second lesson came with the leak. On 20 October, ahead of the agreed date, the CVE identifiers started circulating, with the wrong time and a CVSS of 10.0 that wasn’t the real one, and soon after Calif accidentally published a video of its proof of concept; it took it down, but it was already out. That same night a researcher rebuilt a working exploit for one of the flaws in an hour and a half, starting from the video and leaning on Codex, and confirmed it with AddressSanitizer. That was a researcher, not an attacker, but the lesson holds: with the leads in hand, the exploit is one prompt away.

The numbers from those first days bear that out. Amazon caught an attempt against its MadPot decoys on 17 October, four days before disclosure, from an IP on an anonymization network; on patch day it saw proof-of-concept exploits from 02:00 UTC and concluded it was already being broadly exploited. Cloudflare, which had turned on WAF rules some nine hours before the announcement, counted the jump: from thousands of attempts in the ten hours before to tens of thousands in the next 45 minutes, hundreds of thousands within two hours, and millions after that. As of 22 October no one had confirmed a breach of a real production system. The window between the advisory and a working exploit, though, is now measured in hours.

What about attackers? There’s no public evidence that any of them used AI to build the php2shell exploit. What incident responders do say is that the general pattern has shifted. CJ Moses, Amazon’s security chief, puts it this way: “AI-assisted analysis makes it cheaper to turn a patch diff or advisory into a working exploit.” Mass exploitation used to wait for a capable someone to publish a proof of concept; that dependency is weakening.

How it works, roughly

Without getting into the exploit, the two flaws share one idea. A request can push PHP into a fatal error at a very specific point during startup, say by exhausting the memory limit with a large request that slips under the apparent limits. That interruption leaves the process half-done. In CVE-2026-109618, a partly initialized data structure is left behind, and request cleanup later treats it as valid. In CVE-2026-109619, worker-global state, for sessions or errors, is left pointing at memory that is freed when the request ends. And here is what makes it serious: the worker stays alive and gets reused. In mod_php and PHP-FPM, the next request is handled by the same process, which starts up reading that stale state before reinitializing it. From that kind of access to already-freed memory you get to read other requests’ memory, such as passwords, tokens or keys, and, chained together, to run code.

What that reaches inside a cluster

If your PHP runs on Kubernetes, by the time this is code execution it is no longer just a PHP problem. A shell in a worker is code running as that pod, with everything the pod has within reach: its ServiceAccount token mounted at /var/run/secrets/…, the Secrets you’ve mounted as files or variables, the network to the other services in the namespace and, if you haven’t blocked it, the node’s metadata endpoint. The PHP patch shuts the door; what decides the damage, if someone gets in before you patch, is how far that pod reaches. A ServiceAccount with only the permissions it uses, the minimum Secrets, NetworkPolicies that don’t let it talk to just anywhere, and credentials that expire are worth as much here as patching fast.

What to do

The only thing that closes both flaws is updating: 8.5.12, 8.4.27, 8.3.36 or 8.2.35. If you’re on an unsupported branch, check your distribution’s backport or move versions. And remember to restart the workers afterward, because a process that’s still alive is still vulnerable. The rest are stopgaps, and it’s worth knowing how far they go. A WAF, like Cloudflare’s, which added rules before the announcement, filters the known patterns, but attackers mutate the payload and it doesn’t replace updating. Lowering post_max_size or disabling session.upload_progress closes specific paths, not the flaw. And changing your application code or your plugins fixes nothing, because the flaw is in the PHP engine itself. Anything that has to stay on an old, unpatched version, take it off the network.

The technical details are in PHP’s two advisories (CVE-2026-109618 and CVE-2026-109619) and in the write-ups from Calif and Wiz. Picaporte Labs is one person, in Spain; if this doesn’t match what you’ve got in front of you, write to me.