DEV Community

Andre Burgstahler
Andre Burgstahler

Posted on Originally published at Medium

PHP2Shell: Patch Today, Then Find the PHP You Forgot

This article first appeared on Medium: https://medium.com/@r3dtuxedo/php2shell-patch-today-then-find-the-php-you-forgot-dd590977c842

Abstract illustration: on a dark navy background, a stack of off-white server slabs with one amber slab in the middle showing a thin crack; a teal line runs in from the left, wraps around the cracked slab like a patch, and continues to the right, with faint dot grids of other servers in the background

One engine, a lot of servers. The fix is out; the work now is finding them all.

TL;DR

• PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical bugs in PHP itself (CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell). They can allow unauthenticated remote code execution on servers that handle web requests. Your own code doesn’t need to be buggy. Update, then restart every PHP worker.

• php -v doesn't settle it. Distros backport fixes into old version numbers, the CLI may not be what serves your site, and a container is only patched after you rebuild it.

• Most public proof-of-concept repos don’t work, and running strangers’ exploit code is a risk in itself. Check versions and packages instead. A coding agent is good at that: it can list every PHP version across your repos, Dockerfiles and CI, and open the patch PRs for you to review

On Wednesday 21 October, at 14:00 UTC, the PHP project shipped emergency releases for every supported branch. The two bugs behind them sit in how PHP cleans up memory when a request goes wrong. That makes them unusual: this isn’t a WordPress plugin or a framework bug. It’s the engine underneath all of them.

CISA put out an alert the same day, and the UK’s NCSC told organisations to update or take affected systems offline. AWS says the bugs are now “broadly exploited in the wild”. So this is a patch-today week, not a read-about-it-later week.

What broke, and who’s affected

Both bugs come from how PHP handles fatal errors, especially running out of memory, partway through a request. The request ends, but some of PHP’s internal state is left behind in a broken form. A later request on the same worker can then pick that state up, which can lead to memory corruption and code execution. php.net calls the first “RCE via OOM corruption” (CVE-2026-109618) and the second “RCE via stale global state” (CVE-2026-109619). Wiz adds that the same family of bugs can also leak a worker’s memory, including other requests’ passwords and tokens.

php.net’s advisory says who is at risk: “all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed.” It rates mod_php as the highest risk, because there the attack can run before any application code does. Wiz, one of the teams that reported the bugs, says it ran code without logging in on the stock WordPress, Drupal and Joomla container images, and that “even a simple ‘Hello World’ application running on an affected PHP version can be vulnerable”.

This isn’t a bug in your app. It’s in the engine underneath all of them, so your code being clean doesn’t help

The PHP project scores both at 9.2 on CVSS v4. NVD has since added its own CVSS 3.1 score of 9.8. Here is what to run, by branch:

Table. Fixed releases by branch, per php.net, 21 Oct 2026. 8.5 fixed in 8.5.12, 8.4 in 8.4.27, 8.3 in 8.3.36, 8.2 in 8.2.35 (ends 31 Dec 2026). 8.1, 8.0, 7.4: end of life, no PHP release, minimal source backports; use a vendor build or upgrade, CISA says take offline. 7.3 and older: nothing from PHP.

Fixed releases by branch, per php.net, 21 Oct 2026. Older branches depend on your vendor.

NVD’s record only lists the supported branches, but Wiz and Docker both say every version back to 5.4 is affected by at least one of the bugs. If you’re on something old, assume you’re in.

Timeline, UTC. 2024: out-of-memory problem spotted during an audit; fix stalls. 11 Aug 2026: Calif reports remote code execution, then OpenAI and Wiz, all AI-assisted. 17 Oct: AWS honeypot sees an exploitation attempt. 20 Oct 15:00: CVE IDs leak; release moves up a day. 21 Oct 03:02: Cloudflare WAF rules on. 07:41: Chainguard ships fixed images early. 14:00: PHP releases fixes. 21 to 22 Oct: AWS says broadly exploited; NVD scores 9.8.

Two years of a stalled fix, then five days of leaks, rules and releases.

Patch now, and check it actually took

The question that matters is who builds the PHP you run. Only PHP built from php.net source reliably reports one of the fixed version numbers. Everyone else may be fixed under a different number.

Four ways to check. Managed host: status page or ask support for the build or date. Distro packages: list with dpkg or rpm, update, compare with the distro tracker, since 8.3.6 can be fixed. Containers: run php -v in the image, rebuild with — pull, redeploy. Source or php.net builds: php -v must read 8.5.12, 8.4.27, 8.3.36, 8.2.35 or later. Then restart every PHP worker and check the version the web server runs, not just the CLI.

Start with who builds your PHP. The version string only means one thing for php.net builds.

These are the read-only checks. None of them touch anything:

php -v                         # the CLI, which may not be what serves your site  
php-fpm8.3 -v                  # or php-fpm -v; the binary name varies by distro  
dpkg -l 'php*' | grep '^ii'    # Debian, Ubuntu  
rpm -qa 'php*'                 # RHEL, Fedora, SUSE  
apk info -v | grep '^php'      # Alpine  
docker run --rm your/image:tag php -v
Enter fullscreen mode Exit fullscreen mode

On distros, the version number can look old and still be fixed. Ubuntu 24.04’s fix is php8.3 8.3.6-0ubuntu0.24.04.13, which php -v reports as 8.3.6. So compare the full package version with your distro's tracker:

Table. Distro; Package; Fixed build; php -v shows. Ubuntu 24.04, php8.3, 8.3.6–0ubuntu0.24.04.13, 8.3.6. Ubuntu 22.04, php8.1, 8.1.2–1ubuntu2.28, 8.1.2. Debian 12, php8.2, 8.2.35–1~deb12u1, 8.2.35. Debian 13, php8.4, 8.4.27–1~deb13u1, 8.4.27.

Fixed distro builds, per the Ubuntu and Debian trackers on 22 Oct 2026. The version string alone can look unpatched.

After the update, restart every worker. A running PHP-FPM pool or Apache process keeps the old code in memory until it’s restarted: sudo systemctl restart php8.3-fpm (or apache2, httpd, frankenphp, lsws). Then check that the master process started after the update, for example with ps -o lstart,cmd -C php-fpm8.3.

Containers. The Docker Official Images had the fixed tags on Docker Hub by the evening of 21 October (UTC). That doesn’t help a running container. Rebuild with docker build --pull, redeploy, and check the image you actually run, not the one you meant to run. Fixed upstream tags only reach you through a rebuild.

Managed hosting. If someone else runs PHP for you, it’s their patch. On 22 October a third-party tracker of host status pages listed Kinsta, Cloudways and Pantheon as patched and several others as still rolling out. Check yours, and ask for a date or a build, not just “you’re safe”.

If you can’t update today. php.net lists partial measures, and is clear that none of them make you safe. They include session.upload_progress.enabled=0 set in php.ini or the FPM pool (not via ini_set()), a low post_max_size, and on 8.5, fatal_error_backtraces=0. Restart every worker after any change. Hiding errors does nothing. A WAF in front helps: Cloudflare turned on rules for all plans, Free included, early on 21 October. CISA's advice for anything that can't be patched, including 8.1 and older, is blunter: take it offline until it can be.

How AI found it, and what that changes

The first sign of the out-of-memory problem appeared in 2024, during an external audit funded by Germany’s Sovereign Tech Agency. A PHP core developer noticed it, but nobody found a way to exploit it, and the competing fixes all broke something. The PHP Foundation’s own post calls the resulting two-year delay “a structural failure”.

What changed this year was AI-assisted research. According to the PHP Foundation, Calif reported an unconditional remote code execution on 11 August as part of OpenAI’s Patch the Planet programme. OpenAI says Calif used GPT-5.6 Sol, and OpenAI reported the same root cause independently. Wiz reported further paths on 24 August; it says it used Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol, with Argon contributing most. On the receiving end, the PHP Foundation says it used AI models from Anthropic and OpenAI to help validate the reports.

When three teams with AI help find the same bug in a few weeks, assume someone else can too

Three things follow from that.

Finding is getting cheap; fixing isn’t. The PHP security team says it has published 15 CVEs since 1 July and worked through more than 100 reports that turned out to be invalid, duplicates or not reproducible. The bottleneck is a small group of people who understand the engine well enough to choose a fix.

Embargoes get shorter and leakier. This one leaked in stages. CVE IDs circulated on 20 October with a wrong score, so PHP moved the release forward a day. Calif posted a proof-of-concept video by mistake. Then, per the PHP Foundation, Chainguard shipped fixed images more than six hours before the coordinated release. Chainguard’s CEO told The Register that his team acted on reports of active exploitation, and that they had independently rediscovered the bug starting from “a basic prompt”. AWS, meanwhile, saw an exploitation attempt in a honeypot on 17 October, before any of the public leaks.

Disclosure needs more planning, not less. OpenAI, which helped coordinate the response with CISA and other agencies, used the case to propose a five-stage framework for disclosing AI-found bugs at this scale. Whatever you think of it, the practical lesson for anyone running PHP is the same: the gap between “fix exists” and “attackers have it” is now measured in hours.

Why most public “exploits” are junk, and why you shouldn’t run them

Within hours of the release, GitHub filled up with repositories claiming to exploit PHP2Shell. The PHP security team says it inspected the proof-of-concepts that appeared after the leak and “did not identify any functional exploits at the time of release”. Elias Taylor tested the most-starred public ones and found they don’t work against a plain PHP install. Many quietly depend on a separate file-inclusion bug in the target app, and some AI-generated ones change the target server rather than prove anything.

That doesn’t make the threat fake. AWS says the bugs are now being exploited broadly, though it’s unclear how many attempts succeed. Both things are true: the repos with the most stars are mostly noise, and real attackers have something that works.

You don’t need an exploit to know whether you’re exposed. A version check answers that

There are two reasons not to “just try” one against your own server. First, a fake that fails tells you nothing, and you may conclude you’re safe when you’re not. Second, the repo itself can be the attack. A 2022 Leiden University study of GitHub proof-of-concept repositories found 899 of 47,285 that showed signs of being malicious. Running one on a laptop with your SSH keys and cloud credentials hands them over. For the same reason, I’m not linking any of them here.

What to do with your coding agent this week

This is where Claude Code, Codex or OpenCode earn their keep. The hard part of PHP2Shell isn’t the patch. It’s knowing where PHP lives: the old Dockerfile in a side project, the CI matrix still testing on 8.1, the Ansible variable nobody has looked at since 2023.

Start with AGENTS.md. Add a baseline so every future session knows it, and so the agent doesn’t helpfully reintroduce an old tag next month. Claude Code reads CLAUDE.md, which can point to AGENTS.md:

## Security baselines  
- PHP in production images and CI: 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or later (PHP2Shell, CVE-2026-109618 and CVE-2026-109619). Never pin 8.1 or older.
Enter fullscreen mode Exit fullscreen mode

Then have it take inventory. Versions hide in more places than the Dockerfile:

Where PHP versions hide in a repo. Production: Dockerfile FROM lines and package installs, compose and Kubernetes image lines, Ansible and Terraform variables. CI: GitHub workflow setup-php versions and matrices, GitLab CI images, .tool-versions and .php-version files. Claims: the composer.json php constraint, config.platform.php and composer.lock. composer.json says which PHP the code accepts, not which PHP runs.

Only the first row decides what serves traffic. The other two are where old versions creep back in.

I wrote a small read-only script that lists all of these. It only searches files; it doesn’t build, pull or send anything. Run it yourself, or let the agent run it as step one:

Then let it open the patch PRs. This prompt keeps the agent read-only until the last step, sorts what it finds into patched, floating, vulnerable, end-of-life and unknown, and opens one PR per repo with a deploy checklist. It also tells the agent not to fetch any exploit or “scanner” from the internet, and to treat what it reads as data rather than instructions:

If you use Codex, OpenAI has also published a PHP version checker as a Codex skill. It checks a URL’s headers or a local php -v. Its README warns that distro backports may not change the version string and that "UNKNOWN does not mean the target is patched", which is the same trap as above.

Two things the agent can’t do for you. A merged PR doesn’t patch a server: someone still has to rebuild, deploy and restart workers. And for hosts you don’t control, the answer is still a support ticket.

What this signals

PHP2Shell is a memory-safety bug in a memory-managed language. PHP developers never touch a pointer, but the engine underneath is C. Every high-level runtime has that layer, and AI-assisted research is now reading it closely and quickly.

The good news is that defenders got the same tools, and this time had about two months’ warning. The bad news is that the warning period leaked, and the next one may be shorter. The most useful thing you can do this week is boring: patch, restart, and leave behind an inventory and an AGENTS.md line so the next emergency takes an hour instead of a day.

Sources


If this was useful, subscribe to Agents in Practice for one practical note on AI coding agents a week. Helping your team adopt Claude Code or Codex safely and without the surprise bill? I consult on exactly that: https://andre-burgstahler-9n5.pages.dev/.

This article was written with AI assistance and reviewed by the author.

Top comments (0)