The Language That Runs the Web Has a Memory Problem: PHP2Shell, Frontier Models, and the End of Slow Disclosure
跑在七成互联网上的语言得了“内存病”:PHP2Shell、前沿模型,与慢披露时代的终结
PHP is not an application. PHP is the substrate.
Break PHP, and you do not hack one site — you hack the floor under ten million sites.
PHP 不是应用,是地基。打破 PHP,不是黑一个站,是把一千万个站脚下的地板抽掉。
0. Prologue — a bug older than your Kubernetes cluster
The vulnerable code predates Docker, predates Rust-in-Linux, predates “AI security” as a category.
Some of it dates to PHP 5.4 (2012).
For fourteen years it sat in the request lifecycle:
- not in a plugin
- not in a theme
- not in WordPress core
- but in the C runtime that decides what a request means before your first line of PHP runs
Then in 2026, two independent teams — Calif under OpenAI’s Patch the Planet, and Wiz with Wiz Atlas + Gemini 4 Argon + GPT-5.6 Sol — walked into the same room in the engine and found the lights were already off.
They called it PHP2Shell.
CVE-2026-109618. CVE-2026-109619.
CVSS v4.0: 9.2 / Critical.
1. The real bug class: trust after interruption
Most “PHP RCE” stories are about eval(), unserialize(), assert(), bad CMS plugins.
PHP2Shell is not that genre. It is language-runtime memory philosophy:
PHP assumes that if a request was interrupted, the next request starts from a clean world.
That assumption is false under OOM and fatal-error paths.
PHP 假设:请求被打断后,下一个请求面对的是干净世界。
在 OOM 与 fatal error 路径下,这个假设是错的。
1.1 CVE-2026-109618 — RCE via OOM corruption
Request arrives.
PHP begins populating $_POST, $_FILES, session buffers, upload-progress bookkeeping.
Then a crafted multipart/form-data body pushes allocation past memory_limit.
Normal languages would return 500.
PHP instead does something sharper:
longjmp()out of deep allocation- request struct is half-built
- cleanup runs on structs that were never fully initialized
- invalid pointers get treated as valid
- later allocator decisions, unserializer state, or handler tables get poisoned
In mod_php, this can fire before any application code executes.
/* simplified mental model, not literal PHP source */
request = ecalloc(sizeof(php_request), 1);
request->upload_ctx = NULL; /* not yet set */
request->unserialize_state = NULL; /* not yet set */
if (expand_multipart(request) == FAILURE) {
/* OOM here: longjmp to shutdown */
/* but shutdown reads request->unserialize_state as if valid */
}
The bug is not “run out of memory.”
The bug is: the engine trusts state that only exists because allocation partially succeeded.
1.2 CVE-2026-109619 — RCE via stale global state
PHP workers are long-lived. One process serves thousands of requests.
A fatal error, timeout, or OOM can leave:
- error/backtrace globals
- session decode state
- upload-progress state
- object/extension bookkeeping
…pointing into memory that efree() already returned to the heap.
Next request on the same worker:
- allocator hands that freed region to attacker-controlled input
- stale global still points there
- PHP reads attacker bytes as internal metadata
- type confusion → UAF → primitive
- address leak removes ASLR uncertainty
- RCE as the web worker
Wiz demonstrated this against WordPress, Drupal, Joomla container images.
But the CMS is irrelevant. A “Hello World” PHP app is enough.
The application was never vulnerable. The runtime was.
应用从没漏洞。运行时才是漏洞。
2. Why humans missed it for a decade
This is the part people misunderstand.
It was not negligence. It was economics of attention.
PHP’s request lifecycle is:
- C macros
- globals accessed through
TSRM/CG()/EG() - error paths that rarely execute
- allocator behavior that differs by SAPI
- extension state that survives in ways docs never promise
A human auditor asks:
“What does this request do when it succeeds?”
The bug lives in the question nobody asks enough:
“What does the engine believe is true after a request fails halfway?”
OSTIF + Quarkslab audited PHP in 2024 and saw the OOM smell.
They could not prove the exploit path. The fix stalled.
PHP Foundation later called the delay a structural failure.
That is the key sentence of the whole episode:
Not “we had no auditors.”
But “our governance model cannot price a 9.2 that only manifests in failure paths.”
不是“没人审”,而是“治理模型算不出一个只出现在失败路径里的 9.2”。
3. What frontier models actually changed
The headline “AI found a PHP bug” is toy journalism.
The real change is exploit adaptation velocity.
Wiz’s own words:
A single researcher + frontier models did in a week what a world-class team took months to do.
Models adapt the exploit across PHP versions, configs, memory layouts, SAPIs.
Break it down:
|
Task |
Pre-AI |
AI-assisted |
|---|---|---|
|
Trace request lifecycle |
weeks |
hours |
|
Find stale global candidates |
expert-only |
model-assisted grep + call-graph reasoning |
|
Build UAF primitive |
months |
days |
|
Adapt to FPM vs mod_php |
rewrite |
prompt + debug loop |
|
Leak ASLR, stabilize exploit |
black art |
iterative fuzzing + model hypothesis |
|
Validate patch covers variants |
manual review |
model diff + variant enumeration |
Gemini 4 Argon helped find + exploit + detect + validate.
GPT-5.6 Sol helped audit + adapt.
Wiz Atlas helped scale across targets.
This is not “AGI hacked PHP.”
It is: the marginal cost of turning a memory bug into a portable weapon just fell off a cliff.

4. The defender’s window is now measured in hours
OpenAI’s phrase: Defender’s Window.
Old model:
find bug → report → 90-day embargo → vendor patches → distros ship → users update
New model:
model finds bug → leak happens → WAF vendors wake up at 03:02 UTC → attackers mutate payloads same day → patch ships 9 hours late → 70% of web still unpatched
Cloudflare turned on default WAF rules at 03:02 UTC Oct 21, roughly nine hours before formal disclosure, then watched attackers modify payloads to evade the rules.
That is the whole future in one sentence:
The patch is ready. The internet is not.
补丁准备好了。互联网没准备好。
5. Why “just patch” is a lie operators tell themselves
PHP’s advisory is honest: update is the only complete fix.
But reality for a Fortune 500 / government agency / SaaS:
- PHP version in CLI ≠ PHP version in FPM ≠ PHP version in Apache module
- vendor backport may carry old version string with CVE fix inside
- shared hosting cannot reboot workers
- Magento/Laravel/WordPress pinned to extensions that break on 8.3→8.4
- EOL PHP 8.1 still runs because nobody budgeted the migration
- container base images rebuilt nightly, but prod pods pinned to “stable-2024”
Temporary mitigations exist, but PHP itself warns they are incomplete:
; CVE-2026-109618
memory_limit = -1
post_max_size = 8M
; CVE-2026-109619
session.upload_progress.enabled = 0
fatal_error_backtraces = 0
These reduce known paths. They do not close variant space.
Anyone saying “set one ini and sleep” is selling comfort, not security.
6. The legal and governance aftershock
PHP2Shell is also a disclosure-system stress fracture.
- CVE IDs leaked on HN / X before embargo
- Calif PoC video mishandled
- Chainguard shipped patched binaries before the official clock
- PHP Foundation admitted structural failure
- OpenAI proposed a 5-stage AI-found-critical-flaw framework
- Greg Brockman floated “authorized defenders remotely patch vulnerable servers”
That last idea is the dangerous one.
If OpenAI/Google/Cloudflare can patch the internet “for your own good,” then:
- who authorizes the patcher?
- what if the patch breaks a sovereign system?
- is modifying a foreign server “defense” or “unauthorized access”?
- does a model company become a non-elected CERT?
We just moved from:
“researcher tells vendor”
to:
“frontier lab discovers 0day in global substrate, coordinates cloud giants, and proposes remote remediation”
That is not infosec. That is infrastructure geopolitics.
7. What PHP should learn from this
PHP does not need Rust. PHP needs lifecycle honesty.
Concrete engineering lessons:
- Fail-atomic request init
If request struct is not fully built, shutdown must treat it asNULL, not “maybe valid.” - No global survives a fatal error silently
EveryEG()/PG()field touched during request must have a reset contract tested by fuzz. - OOM is a control flow path, not an exception you swallow
Write tests that inject allocator failure at Nth malloc. - Variant regression suite
When one UAF is fixed, generate sibling variants automatically with models + static analysis. - SAPI-specific threat model
mod_php,FPM,php-cgi,FrankenPHP,LiteSpeedare different attack surfaces wearing one name. - Fund the maintainers like it’s a utility
PHP runs ~70% of the web and was maintained like a mailing list with a donation jar.
8. The deeper thesis
PHP2Shell is not a PHP story.
It is the first mainstream confirmation of a new law:
Frontline vulnerability discovery is no longer gated by human scarcity.
It is gated by coordination speed, disclosure law, and who may touch production.
漏洞发现不再受限于“有没有人看得懂”;
受限于协调速度、披露法律、以及“谁被允许碰生产环境”。
The attacker side already knows this.
Vibecoded exploit scripts are appearing in the wild.
The defender side is still writing blog posts.
9. Closing — the floor is load-bearing
If you run PHP:
php -v
# then check the SAPI that actually serves traffic
php-fpm -v
httpd -M | grep php
Then patch. Then restart workers. Then rebuild images. Then recheck backports. Then assume yesterday’s scanner lied.
If you build runtimes:
A request that fails must leave the world cleaner than it found it.
Not “mostly valid.” Not “probably fine.” Clean.
一个失败请求离开时,必须把世界收拾得比来时更干净。
不是“大概有效”,不是“应该没事”。是干净。
PHP2Shell will not be the last.
The next one may not leak nine hours early.
It may leak nine minutes early — and the model that found it may already be talking to the model that exploits it.
**PHP ran the web quietly.
Now the web knows its runtime can lie.
The only question left is whether governance evolves faster than allocation failure.
此文由 怡心湖 编辑,若您觉得有益,欢迎分享转发!:首页 > 赞·中华 > 经验谈 » 跑在七成互联网上的语言得了“内存病”:PHP2Shell、前沿模型,与慢披露时代的终结
Web3: The Next Generation of the
算力迁徙:AI时代下PC与移动互联网的
“卖人头”时代的终局:AI 如何正在