PHP2Shell: the evidence log

The research notes behind The hero gets the bill: sources, counts, timings and the claims I chose not to make. A working document, published as-is, and it may well be wrong in places.

What this is, and what it isn’t. These are the research notes behind The hero gets the bill, my reaction piece on PHP2Shell. They were gathered on 21 and 22 October 2026, within about a day of the release, with the help of AI research agents working under my direction, and then checked and cut down by me. They are a working document, not a report, and they may be flawed. Counts are snapshots taken at a particular hour and will have moved since. Sentiment figures come from coding public posts, a method with real limits that are spelled out below. Where something is second-hand or unverified it says so. If you spot something wrong, I’d like to know.

All times are UTC unless stated. “Primary” means I (or an agent I was supervising) read the source itself; “secondary” means it is someone else’s report of it.

The bugs and the fix

  • Two advisories, published together at 13:34:25 UTC on 21 October 2026 (primary): GHSA-w8hw-w9fx-j79w / CVE-2026-109618, “RCE via OOM corruption”, and GHSA-6wmf-4rv5-p3rp / CVE-2026-109619, “RCE via stale global state”. Both CVSS v4.0 9.2.
  • Mechanism, in the advisories’ terms. PHP enforces memory_limit with longjmp(). If an allocation fails while request memory is only partly initialised, request shutdown later reads that invalid state. Under Apache mod_php this was validated as unauthenticated RCE with default settings, before application code runs. The second advisory covers global state from a previous request (after a fatal error) being used by the next request on the same worker.
  • Fixed versions: 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (php.net, 21 Oct), plus minimal source backports for 7.4, 8.0 and 8.1 published by the PHP security team. php.net: “There are no complete mitigations, updating is the only safe choice.”
  • The fix commit (3584a98, “Avoid state corruption via bailout”, five authors): 300 files, +10,759 / −988 lines, of which 4,819 added lines are tests (primary, GitHub API).

The history

  • July 2004. Stefan Esser’s e-matters advisory describes remotely triggering memory_limit termination “in places where an interruption is unsafe”, rated critical, affecting PHP 4.3.7 and 5.0 RC3 (bugtraq, primary).
  • November 2019. Bug #78876, long multipart variables causing OOM (later CVE-2019-11048). The maintainer’s comment: “Allocation failure strictly speaking is not a crash, but not cleaning up temp file may be indeed bad.” (primary)
  • 2024. The Sovereign Tech Agency-funded audit (OSTIF / Quarkslab). The published audit results do not mention memory_limit, OOM or bailout. Per the Foundation’s own post-mortem, the first signs of the OOM problem were found by a Foundation-contracted developer while fixing one of the audit’s findings (primary, both Foundation posts). So the commonly repeated line that the team “sat on the audit” is wrong in detail.
  • 2026. Calif (within OpenAI’s Patch the Planet) and OpenAI reported the root cause on 11 August; Wiz reported further vectors on 24 August; OpenAI more on 18 September (Foundation post-mortem, primary). Calif’s own account is that a researcher steered a model from the 2004 primitive, which models “do not reliably begin with” on their own.

The Foundation’s own words (primary)

  • “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.”
  • “The PHP security team, operating on consensus, struggled to choose between these and feared the repercussions of shipping the wrong choice.”
  • The team is changing its processes “so that decisions can be made by majority and compromises are encouraged.” As of 22 October I could find no corresponding change in PHP’s public policies repository; the security team’s internal process may live elsewhere, so that shows only that nothing public had changed yet.
  • The post was edited once after publication (a comma and “stale state” → “stale global state”); the paragraph about Chainguard was not changed.

The disclosure, hour by hour

When (UTC)WhatSource
17 OctAWS honeypots record an exploitation attempt, four days before disclosureAWS, primary
19 OctRelease tags cut (8.2.35 at 07:30 … 8.5.12 at 17:04), on commits not on the public development branchesgit metadata, primary
20 Oct ~15:00CVE IDs circulate with a wrong release time and a wrong CVSS of 10.0Foundation post-mortem
20 Oct 19:30Pre-announcement published early; release moved forward a dayphp.net, Foundation
20 Oct ~20:00Calif mistakenly publishes a video of its PoC; copies circulateFoundation post-mortem
20 Oct ~23:00Mitigation advice given to some infrastructure providers may also have leakedFoundation post-mortem
21 Oct 03:02Cloudflare WAF rules live for all customersCloudflare
21 Oct 07:41Chainguard publishes container images with the fixFoundation post-mortem
21 Oct 13:34Advisories publicGitHub, primary
21 Oct 13:45Debian DSA-6574-1primary
21 Oct 13:56Ubuntu USN-8948-1primary
21 Oct 14:06Fix commit’s committer timestamp on the public PHP-8.2 branch; merges up the branches 14:12–14:33git metadata, primary
21 Oct ~16:40Official Docker PHP image usable for amd64 after a deploy glitchGitHub, primary
21 Oct 18:59–22:10Remaining official Docker tags last pushedDocker Hub API, primary

On the git timing. Git timestamps are set by the committer’s machine and do not prove when something was pushed. The GitHub event log I could query only reached back to 19:14 on 21 Oct, and the public GH Archive dumps for that day looked incomplete. So: no sign of the fix in the public repository before disclosure, not proof that it wasn’t there. If the source was indeed private until 14:06, Chainguard’s images at 07:41 were the first public copy of the fixed code.

Chainguard’s side. Its CEO says he shipped once exploitation was being reported (HN comment), told The Register his team had found the same bugs independently with an AI harness, and called it “the new normal”. The “basic prompt” wording attributed to him comes from The Register’s report of a LinkedIn post I have not seen.

The official Docker image (1.38 billion pulls). The update was opened by a community contributor at 13:44, ten minutes after disclosure, so nothing appears to have been staged in advance. php.net says only that “Linux distributions and LTS vendors received the patches in advance”; the Foundation describes Chainguard as one of those distributors. The official-images PR merged at 15:16; tags then appeared without an amd64 image (issue) until a maintainer posted “Sorry, had a deploy glitch” (discussion). Whether Docker was offered early access and declined, or never asked, I don’t know.

Who does the fixing

  • php-src security advisories by year (GitHub API, de-duplicated): 2023: 5, 2024: 18, 2025: 14, 2026 to 22 Oct: 29. Since 1 July 2026: 19. The Foundation separately reports more than 100 invalid, duplicate or non-reproducible reports since July.
  • Engine-core concentration (non-merge commits under Zend/, 12 months to 22 Oct): 612 commits from 74 authors, of which the top five wrote 51%. Commits are not a measure of effort; this is only an indication of how concentrated the work is.
  • The Foundation’s Ecosystem AI Security Engineer in Residence is a six-month, grant-funded post (Alpha-Omega). The start date isn’t stated.
  • php.net’s advisory told users of the volunteer-run rpms.remirepo.net that it “will not have updates today” and to switch to distribution packages or build from source.
  • The Foundation’s 2025 accounts (report): $730,534 donated, $784,376 spent, 11 contracted developers, 536 sponsors (“substantially less than the previous year”).

Who can’t simply upgrade

  • WordPress (api.wordpress.org/stats/php, 22 Oct): 35.8% of installs on PHP below 8.2 (7.4 alone 15.9%), and 23.7% on 8.2, whose security support ends 31 December 2026. Together 59.5% on unsupported versions by New Year unless they move.
  • W3Techs (PHP usage, 22 Oct): PHP on 69.3% of sites with a known server-side language; of those, about 48% on versions with no official upstream release (7.x, 5.x and 8.0/8.1). W3Techs only sees sites that disclose a version.
  • WordPress’s requirements page recommends PHP 8.3+ and says it “also works with PHP 7.4+”, while warning those versions are end of life.

Response and harm

  • Agencies advising taking sites offline if they couldn’t patch: the UK NCSC, BSI (warning level 3/Orange per dpa), the Dutch NCSC, France’s ANSSI (via Interior Ministry statements reported by the press), and the US NSA (per the BBC; I didn’t see the original). Japan’s prime minister posted an “urgent request” late on 21 Oct (ITmedia and other national press; I didn’t see the post itself).
  • Sites dark: a volunteer tracker listed 50 (17 government), with a median of 4.6 hours offline. Press reports add Dutch hospitals, all French prefecture sites, Charité’s website in Berlin, Wikimedia systems and Edinburgh council, among others. Scouting Nederland’s site was dark for 1h44 on the tracker.
  • Confirmed compromises: as of the morning of 22 Oct I could find none publicly confirmed. CISA, Expel, SentinelOne and Huntress reported none; one threat-intelligence group withdrew its “exploited in the wild” tag because sprays against targets aren’t proof of successful exploitation. Several vendors reported attempts; a Reuters headline spoke of “millions” of them (headline only, body not read).

Attention

  • Wikipedia, “PHP” article, views on 20 Oct → 21 Oct: English ~900 → 87,141; German 175 → 26,205; Dutch 22 → 14,566; Japanese 66 → 103,076; Finnish 5 → 2,862. The new “PHP2Shell” article had 238,212 English views on its first day (Wikimedia pageviews API, user agents only).
  • Hacker News: 77 matching stories, 4,233 points, 1,571 comments (Algolia, 06:24 on 22 Oct).
  • GitHub: about 350 repositories matching “php2shell” within a day of release, one of them created before the patch existed. (I deliberately don’t link proof-of-concept code.)
  • Lobsters: one consolidated story, 185 points, 127 comments.

Sentiment, and why to treat it gently

I coded public comments and posts for their stance on how the PHP project handled the release. In short, most people didn’t take a stance on the Foundation at all, and those who did split by audience.

  • A targeted set of 300 items about the project’s handling (137 Hacker News, 96 Bluesky, 67 Mastodon), coded independently twice. Agreed counts, supportive : critical: Hacker News 14 : 41, Mastodon 16 : 5, Bluesky 5 : 3 (too few to call).
  • Big caveat: both coders were instances of the same AI model with the same codebook. 97% agreement shows consistency, not independent human judgement. The samples are also skewed: the Bluesky set is a capped search sample starting around 18:00 on 21 Oct, and the Mastodon set comes from hashtag timelines that over-represent sysadmins and the PHP community.
  • A broader single-coder pass (995 Hacker News comments; 1,277 Bluesky and Mastodon posts) found ops and patching talk dominating. On Hacker News, blame for the leakers outnumbered criticism of the Foundation, and after the post-mortem went up embargo talk jumped from 7 comments to 118 while Foundation criticism did not soften. A second coder agreed with the theme coding (kappa about 0.72 to 0.75) but not with an early claim that Bluesky “focused on blaming Chainguard”, which I withdrew.
  • About a third of the Bluesky and Mastodon posts weren’t in English (German and Japanese most of all), which matches where the outages were.

Money

  • OpenCollective (transactions), 21 Oct 00:00 to 22 Oct ~06:00: 29 credits, $69,927 in all. Of that, $50,000 at 19:13 on 21 Oct from an account named “The Chainguard Team” (motive unknown) and $17,956 that is a sponsorship invoice dated 13 Oct (not a reaction). Accounts typed as individuals: 23 donations, $711. A normal day before the incident was $5 to $125.
  • This measures one platform for about 30 hours. A lot of the gratitude went to individual packagers’ Patreon and GitHub Sponsors pages, which I couldn’t measure before and after.

Claims I chose not to make

A hostile review of my own draft theses, run before writing, caught these, so the post avoids them:

  • Comparing $711 in a day to OpenSSL’s pre-Heartbleed ~$2,000 a year (unlike periods).
  • “Nobody was hacked” (a day-one absence of evidence, not evidence of absence).
  • “Half the web had nothing to install” (backports, LTS vendors and distributions covered many).
  • “AI found it on its own” (a human steered the model to a 2004 primitive).
  • Hard sentiment ratios as headline facts (see the caveats above).
  • OpenAI’s $1B “Daybreak” commitment as a contrast to donations (it is product credit, announced separately, not cash for maintainers).

Still unverified

  • The body of the Reuters “millions of exploit attempts” report.
  • The White House statement and the NSA’s post (BBC only).
  • Censys’s figure of about 1.5 million unpatched hosts (second-hand only).
  • Whether Docker was offered early access.
  • Exactly when the fix’s source first became publicly visible (see the git timing note).
Built with Hugo · Theme Stack designed by Jimmy