跪拜 Guibai
← Back to the summary

PHP Ships Patches for Two Unauthenticated RCEs as OpenAI Coordinates Multi-Vendor Disclosure

Affecting About 70% of Websites: OpenAI Leads Global Discovery and Fix Coordination for Critical PHP Vulnerabilities

On October 21 at 14:00 UTC, PHP released four security versions at once: 8.5.12, 8.4.27, 8.3.36, and 8.2.35. The same day, OpenAI published a blog post with a restrained title but unrestrained content: it led a multi-organization coordinated vulnerability disclosure for two unauthenticated RCEs in PHP, both rated CVSS 9.2.

Image: OpenAI official announcement card 'Patch the Planet', the official release image for this global coordinated PHP vulnerability disclosure

Figure: OpenAI's official announcement card 'Patch the Planet', the official release image for this global coordinated disclosure of the PHP vulnerabilities

What stands out more is the timing. According to the timeline Sansec compiled, six hours before the patches shipped, Cloudflare had already observed real attacks targeting these vulnerabilities. A day earlier, the details had leaked in security circles, and watchTowr confirmed that RCE was exploitable. In other words, by the time the disclosure went public, someone was already hitting real systems with it.

The 70% figure needs unpacking

ITHome's October 22 report used the headline 'Affecting about 70% of websites: PHP high-risk vulnerabilities fixed; OpenAI coordinates global discovery and fixes'. Most Chinese coverage attached the 'about 70% of websites' phrase directly to the vulnerability sentence, reading as if 70 percent of websites were hit by these two bugs. That reading is wrong.

The 70% is about PHP's own footprint. In W3Techs' October 22 statistics, among websites using known server-side languages, PHP accounts for 69.3%. Second-place JavaScript has only 8.1%, Ruby 7.1%, Java 5.4%, Scala 5.0%, ASP.NET 4.2%, Python 1.1%. What the vulnerabilities actually affect is sites running an affected PHP version; the scope stays inside the PHP ecosystem.

Those two facts should not be merged. One is ecosystem size, the other is this specific set of bugs. Mixed together, the number inflates into a nonexistent panic; the same conflation also lets other readers think it has nothing to do with them, when anyone running PHP is very much affected.

The bugs themselves: two use-after-free flaws, not in your code

Several online analyses have described these as 'a URL parser flaw that can lead to SSRF or bypass domain verification'. That characterization appears nowhere in the primary sources: the OpenAI post, the php.net advisories, or Calif's technical writeup. Repeating it would be a mistake.

The official php.net advisories list two engine-level memory safety issues:

Both are CVSS v4.0 9.2.

Figure: cover of Wiz Research's PHP2Shell paper, directly labeling CVE-2026-109618 and CVE-2026-109619

Figure: The cover of Wiz Research's PHP2Shell research paper, directly labeling CVE-2026-109618 and CVE-2026-109619

Figure: Wiz Research's PHP2Shell attack chain diagram; a crafted HTTP request triggers RCE on WordPress, Drupal, Joomla and other PHP applications

php.net states the exposure plainly: every PHP installation that serves web requests is in scope, PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, and LiteSpeed all included. mod_php carries the highest risk; the official text says an exploitation path has been demonstrated and that a single unauthenticated request can trigger the attack before any application code executes.

'Before any application code executes' is the worst part of this. No matter how clean the business code is, no matter how strict the parameter filtering, there is no escaping it. The bug is in the engine; the request is compromised at the front door. Calif tested and confirmed pre-auth RCE on eleven applications: WordPress 7.1.2, Drupal 11.4.3, Joomla 6.1.4, MediaWiki 1.46.2, Roundcube 1.7.4, Magento 2.4.9, phpMyAdmin 5.2.3, Nextcloud 35.0.1, Laravel 13.11, Symfony 8.1, WHMCS. The writeup adds a harsher line: the earliest affected code path goes back to PHP 5.4.0, released in 2012, and remains exploitable in default configurations. mod_php and PHP-FPM are both affected.

Figure: Wiz Research's PHP2Shell attack chain diagram: a crafted HTTP request can trigger RCE on WordPress, Drupal, Joomla and other PHP applications

Figure: Wiz Research's PHP2Shell attack chain diagram; a crafted HTTP request can trigger RCE on WordPress, Drupal, Joomla and other PHP applications

The exploit chain: first get past two resource gates

Calif's writeup covers the technical details thoroughly and they deserve expansion. The goal of the whole chain is to get a controlled fatal; to do that, an attacker first bypasses two resource limits.

The first is post_max_size. PHP decides whether a request body exceeds the limit by comparing the client-declared Content-Length with the post_max_size configuration; it does not verify how many bytes were actually consumed. That leaves two openings. Under Apache, an HTTP/1.1 request with chunked encoding and an HTTP/2 multipart request without a declared length both bypass the comparison. With an input decompression filter configured, the compressed length and the decoded stream length also do not match.

The second is memory_limit. Keeping the total request under post_max_size, an attacker repeatedly stacks empty parts with filename="" under the RFC1867 upload protocol. Such parts carry no file data but still consume memory, and enough of them reaches the threshold.

Only after obtaining a controlled fatal does the real work begin. Calif found a combination of 2 fatal producers plus 3 interruptible points, turning the dangling memory on the exception cleanup path into a readable/writable primitive and finally achieving memory disclosure and RCE. The accompanying exploit tool, PHP2Shell, has been released.

Following this chain shows exactly where the model's participation shows up. OpenAI's account says Calif, with assistance from GPT-5.6 Sol, discovered one of the critical vulnerabilities. In the writeup, the part that best demonstrates that assistance value is the input construction in those two steps above: enumerating combination spaces like HTTP methods, compression filters, and empty parts, which is exactly the kind of exhaustive labor people dislike and models are good at spreading out.

Image: repository card for the open-source PHP version self-check tool released by OpenAI

In practice: check yourself, stop the bleeding, upgrade

This section is for ops and backend engineers; follow along. The procedure follows Sansec's PHP2Shell research report and the php.net advisories.

Step 1: confirm the version

php -v

The php -v output is the CLI binary's version. Web requests go through FPM or mod_php, and the two can differ. A single machine often has several PHP installs, so check them separately:

# the fpm one; replace with your actual version
php-fpm8.2 -v

# Debian/Ubuntu package view
dpkg -l | grep -E '^ii  php'

# RHEL family
rpm -qa | grep php

The patched versions to match are 8.5.12, 8.4.27, 8.3.36, and 8.2.35. Anything below these needs action. PHP 7.4, 8.0, and 8.1 have ended regular official updates; this time a minimal source backport was also provided, so even older self-compiled versions can get the fix.

Walk a real case: an nginx + PHP-FPM 8.2 WordPress machine.

$ php-fpm8.2 -v
PHP 8.2.28 (fpm-fcgi) ...

8.2.28 is below 8.2.35, so it is vulnerable. WordPress is also on Calif's list of 11 pre-auth RCE apps, exploitable in default configuration; there is no point arguing about whether file uploads are enabled.

Step 2: upgrade and restart

Before upgrading, check whether the distro repo has a fix. Distro package versions often do not match upstream:

apt changelog php8.2-fpm | head -30

Security updates can be delayed in repos; if they are not there yet, go straight to the Step 3 stopgap config instead of waiting.

sudo apt update
sudo apt install --only-upgrade 'php8.2-*'
sudo systemctl restart php8.2-fpm

Adjust package names to your distro repo. The restart is not optional. FPM is a long-running process: if the binary is replaced but the process in memory is still running old code, not restarting means the upgrade has not happened.

Verify:

$ php-fpm8.2 -v
PHP 8.2.35 (fpm-fcgi)

For mod_php, run the equivalent sudo systemctl restart apache2.

At this point the case concludes: the machine was running an affected version and exposed to pre-auth RCE before the patch; after upgrading and restarting FPM, the exposure is gone. While you are in the logs, treat any POST from an unknown source with a multipart Content-Type as probing.

Step 3: if the upgrade cannot happen yet, stop the bleeding

Sansec offers three temporary mitigations, ordered from least to most intrusive.

Block multipart requests directly at nginx:

if ($http_content_type ~* "multipart/form-data") {
    return 403;
}

The cost: all file uploads break, including WordPress admin image uploads. This only works for crossing the upgrade window.

In php.ini, disable upload progress and release the memory cap:

session.upload_progress.enabled = 0
memory_limit = -1

The memory_limit = -1 setting needs thought. Its logic is that an OOM fatal no longer appears, which removes the trigger from the exploit chain. But with no memory cap, a genuinely large request can exhaust memory, the OOM killer steps in, and it may take other services on the same machine down with it. Use this only if the machine has memory to spare.

The bluntest option is to allow only GET/HEAD at the entry point and reject all POSTs. API sites can tolerate it; content sites cannot.

Three more pitfalls. If you use rpms.remirepo.net, note that this volunteer-maintained repo had no updates on patch day; php.net's official advice is to switch to distro packages or compile yourself. With Docker, base images like php:8.2-fpm need a fresh pull and container rebuild; the image cache still holds the old version, and these are the easiest assets to miss. With many machines, do not forget the ones hidden behind a load balancer; they are often the problem.

Step 4: when the fleet is large, borrow the tool

OpenAI also shipped a PHP version checker in the form of a Codex skill. The idea is to encode the affected version ranges into the skill and let Codex check each machine's SAPI versions one by one. The entry point is in a link from the OpenAI post; the name is whatever the official page says. Inventorying several hundred machines by hand is genuinely unsustainable.

Figure: repository card of the open-source PHP version self-check tool that OpenAI released alongside the announcement

Figure: repository card of the open-source PHP version self-check tool that OpenAI released alongside the announcement

How the coordinated disclosure ran

The disclosure process itself is worth a look; its scale is rare.

Timeline (compiled by Sansec, UTC): August 1, Calif confirms multiple RCE paths. August 11, reports to PHP; coordinated disclosure begins. August 24, Wiz Research reports independently. October 20, details leak; watchTowr confirms RCE. October 21, 07:43, Cloudflare reports early attacks. 14:00, PHP releases fixed versions; Calif, Wiz, and OpenAI publish simultaneously.

Details leaked one day before the patch, and attacks were observed six hours before the patch. That ordering basically closed off any room to delay further.

Division of labor: under the Patch the Planet program, Calif used GPT-5.6 Sol to find one of the critical vulnerabilities, and Wiz independently found and reported both. PHP maintainers led verification, fixes, and release. OpenAI, Wiz, and Cloudflare supplied research, technical support, and cross-organization coordination. OpenAI calls this one of the most consequential security issues in recent years and introduces a concept, the defender window: as far as they know, frontier AI found these vulnerabilities earlier than attackers did.

The supporting effort reached government level. OpenAI lays out a five-phase framework: verify and fix the upstream vulnerability; give affected organizations time to act; coordinate with governments, reaching out to the White House Superintelligence Working Group, ONCD, CISA, and international partners; prepare updates for affected products; and after the patch, publish guidance and support deployment. The Netherlands moved fastest: Tweakers, the Dutch House of Representatives, NCSC, and Security.NL went offline preemptively on October 21.

How much did AI actually contribute

This is the part of the whole event I most want to untangle.

OpenAI's narrative is that AI stayed a step ahead of attackers. In the discoverer Calif's own writeup there is a dampening passage: 'The initial vulnerability discovery did not use a special harness, nor a model unavailable to the public. I focused on targets where I have background and know the vulnerability history well.' It then adds another cut: 'No single model can find all vulnerabilities from simple prompts. At least not yet.'

He also points to an inherent bias in how LLMs do variant analysis: they default to starting from the newest, best-known vulnerability and expand toward adjacent code; they do not proactively begin from an obscure exploit primitive from twenty years ago. Someone on Hacker News (id 50181175) used this to question whether OpenAI was over-crediting AI.

The two accounts do not actually conflict; put together they form the complete picture. Choosing targets and judging where to dig is human work. Calif's accumulation of PHP memory-vulnerability history is a continuation of a line from 2004. The model's contribution is the input construction that bypasses the two resource limits, a matter of combinatorial search and grunt work, exactly the zone models are good at. 'Frontier AI found it earlier than attackers' is not a lie; it just omits the subject. It was AI directed by a researcher carrying twenty years of domain knowledge, and that qualifier cannot be dropped.

An old debt that dragged for 22 years

Calif's writeup traces a longer line, and it carries more information than the incident itself.

In 2004, Stefan Esser found PHP's first memory_limit-related RCE. That year he chose not to disclose an exploit; CORE and Immunity both held private versions. In 2020, PHP bug #78876 reported that temporary files were not cleaned after OOM. In 2024, OSTIF and Quarkslab ran an audit and the same OOM issue surfaced again, but was not fixed because it was 'not proven exploitable'.

Then on July 30, 2026, OpenAI invited Calif to join Patch the Planet; Esser connected him with PHP Foundation maintainer Volker Dusch, and results were produced by August 1. A problem that auditors had seen and judged non-threatening sat for two years and was finally closed by a newly assembled collaboration chain.

The Patch the Planet main post has more numbers: Trail of Bits used Codex plus GPT-5.5-Cyber across 19 open-source projects and found hundreds of issues, including 8 kernel pointer leak PoCs and 24 local privilege escalation exploits in the Linux kernel, plus a System V semaphore use-after-free in OpenBSD that had lain dormant for 23 years. OpenAI also pledged $1 billion in usage credits through 'Daybreak for Frontline Defenders' for organizations that run critical services with limited security resources.

Questions that still have no answer

Looking back, the technology is the easy part of this incident: an old memory-safety disease, a cleanup path failure, no engine-level solution except upgrading. The real variable is process. A 22-year-old debt nobody took up, reported to PHP on August 11, fixed simultaneously in four branches worldwide on October 21, with leaks and real attacks squeezed in between.

If Wiz could independently find these two vulnerabilities, how many attackers have also independently found them? This time Cloudflare saw the traffic first and the official side got the patch out ahead of it; the window was six hours. How wide the next window is, no one can say.

Reference links

Search the WeChat public account: Pixel Tech Notes, follow so you don't get lost ~