| | {{infobox |
| - | | title = The Elephant's Glass Jaw: Architecture and Fallout of the PHP RCE Collapse |
| + | | title = PHP2Shell |
| | | event = Disclosure of two critical PHP RCE vulnerabilities |
| | | CVEs = CVE-2026-109618 · CVE-2026-109619 |
| | | CVSS = 9.2 (v4.0), each — Critical |
| | | disclosed = 21 October 2026 |
| - | | affected = PHP 5.4.0 (2012) through 8.5.11 — every supported branch |
| + | | affected = PHP 5.4.0 (2012) through 8.5.11 — all branches, including end-of-life |
| | | fixed_in = 8.2.35 · 8.3.36 · 8.4.27 · 8.5.12 (source backports for 7.4, 8.0, 8.1) |
| | | discovered_by = Wiz Research, with LLM-assisted exploit development |
| | | impact = Unauthenticated RCE; cross-request memory disclosure |
| | }} |
| | {{date|2026-10-21}} |
| | |
| | **PHP2Shell** is the collective name for two critical remote-code-execution vulnerabilities in the PHP engine — **CVE-2026-109618** (out-of-memory corruption) and **CVE-2026-109619** (stale global state / use-after-free) — disclosed and patched on 21 October 2026. Both carry a CVSS v4.0 score of 9.2, both permit unauthenticated remote code execution, and both live in code that every PHP web request touches: memory management and fatal-error handling.[[cite: PHP Foundation, ["Today's PHP Security Release, and How We're Strengthening PHP Security"](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/), 21 October 2026]][[cite: CISA, ["Critical Vulnerabilities in PHP Require Immediate Patching"](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching), 21 October 2026]] |
| | |
| - | The blast radius is the story. PHP runs roughly 70% of websites that disclose a server-side language — WordPress, Drupal, Joomla, MediaWiki, and the long tail of everything else — and the vulnerable code path executes *before* any application code runs. A PHP file that does nothing but print "Hello World" is exploitable.[[cite: Wiz Research, ["PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet"](https://www.wiz.io/blog/php2shell-php-rce), 21 October 2026]] Datadog Security Labs independently reproduced the OOM corruption on PHP 8.5.11 with a single unauthenticated HTTP POST and confirmed that the attacker's command runs during *request cleanup* — while PHP is trying to tidy up a request that already failed.[[cite: Datadog Security Labs, ["PHP2Shell — Critical vulnerabilities in PHP: detection, analysis, and remediation"](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/), 21 October 2026]] |
| + | The blast radius is the story. PHP runs roughly 70% of websites that disclose a server-side language — WordPress, Drupal, Joomla, MediaWiki, and the long tail of everything else — and the primary vulnerable paths execute *before* any application code runs. A PHP file that does nothing but print "Hello World" is exploitable.[[cite: Wiz Research, ["PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet"](https://www.wiz.io/blog/php2shell-php-rce), 21 October 2026]] Datadog Security Labs independently reproduced the OOM corruption on PHP 8.5.11 with a single unauthenticated HTTP POST and confirmed that the attacker's command runs during *request cleanup* — while PHP is trying to tidy up a request that already failed.[[cite: Datadog Security Labs, ["PHP2Shell — Critical vulnerabilities in PHP: detection, analysis, and remediation"](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/), 21 October 2026]] |
| | |
| - | The flaws are old. The vulnerable code reached back to PHP 5.4.0, released in 2012 — fourteen years of exposure, sitting in the engine's most-executed paths. What changed on 21 October 2026 is that Wiz Research proved the flaws exploitable, in large part with the help of frontier AI models that compressed months of exploit-development drudgery into hours. Complexity had been the security. It stopped being. |
| + | The flaws are old. The vulnerable code reached back to PHP 5.4.0, released in 2012 — fourteen years of exposure, sitting in the engine's most-executed paths. What changed on 21 October 2026 is that Wiz Research proved the flaws exploitable, in large part with the help of frontier AI models — Wiz credits them with cutting exploit development and adaptation to a matter of hours per target. Complexity had been the security. It stopped being. |
| | |
| | # What happened |
| | |
| - | The disclosure was coordinated and unusually broad. Wiz Research (Sagi Tzadik, Shir Tamari, Nir Ohfeld, Tomer Schwartz) reported the bugs to the PHP security team; the resulting patch touched roughly 300 files across the Zend engine. OpenAI and Calif — both involved in the AI-assisted research — published alongside Wiz, and an industry group including Automattic, Akamai, AWS, Cloudflare, Fastly, Google, Imperva, Microsoft, and Vercel prepared edge mitigations ahead of public release.[[cite: Wiz Research, [PHP2Shell disclosure](https://www.wiz.io/blog/php2shell-php-rce)]] |
| + | The disclosure was coordinated and unusually broad. Wiz Research (Sagi Tzadik, Shir Tamari, Nir Ohfeld, Tomer Schwartz) reported the bugs to the PHP security team; the resulting patch reworked large parts of the Zend engine — public exploit documentation describes roughly 300 changed files.[[cite: [php2shell PoC — patch scope](https://github.com/ikow/php2shell-poc)]] OpenAI and Calif — both involved in the AI-assisted research — published alongside Wiz, and an industry group including Automattic, Akamai, AWS, Cloudflare, Fastly, Google, Imperva, Microsoft, and Vercel prepared edge mitigations ahead of public release.[[cite: Wiz Research, [PHP2Shell disclosure](https://www.wiz.io/blog/php2shell-php-rce)]] |
| | |
| - | PHP shipped four fixed releases the same day — **8.5.12, 8.4.27, 8.3.36, 8.2.35** — and published minimal source backports for the out-of-support 7.4, 8.0, and 8.1 branches.[[cite: Datadog Security Labs, [remediation and backport details](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] CISA issued an alert within hours, urging inventory, immediate patching of internet-facing systems, and monitoring for PHP workers spawning shells or downloading tools; the UK NCSC issued a parallel warning.[[cite: [CISA alert](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching)]][[cite: NCSC, ["Critical security issue affecting PHP"](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php), 21 October 2026]] |
| + | PHP shipped four fixed releases the same day — **8.5.12, 8.4.27, 8.3.36, 8.2.35** — and published minimal source backports for the out-of-support 7.4, 8.0, and 8.1 branches.[[cite: Datadog Security Labs, [remediation and backport details](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] CISA issued an alert the same day, urging inventory, immediate patching of internet-facing systems, and monitoring for PHP workers spawning shells or downloading tools; the UK NCSC issued a parallel warning.[[cite: [CISA alert](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching)]][[cite: NCSC, ["Critical security issue affecting PHP"](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php), 21 October 2026]] |
| | |
| | Public exploit code appeared the same day — repositories demonstrating multiple attack chains against mod_php and PHP-FPM, including a pre-authentication RCE against Apache's mod_php using a single multipart POST.[[cite: [php2shell public proof-of-concept repository](https://github.com/ikow/php2shell-poc), released 21 October 2026]] At the time of the CISA alert, no in-the-wild exploitation had been confirmed, but public working code against 70% of the web is not a situation that stays quiet. |
| | |
| | # Thirty-one years of PHP |
| | |
| | To understand how the web's most-deployed runtime ended up here, you have to look at what PHP is built out of. |
| | |
| | Rasmus Lerdorf wrote the first version in 1994–95 as a set of C programs for his personal home page — *PHP/FI*, the "Personal Home Page / Forms Interpreter," released publicly in June 1995. It was CGI in the literal sense: a C binary the web server invoked fresh for each request. Andi Gutmans and Zeev Suraski rewrote it into PHP 3 (1998) and then built the **Zend Engine** that has powered every release since — Zend Engine 1 in PHP 4 (2000), Zend Engine 2 in PHP 5 (2004), Zend Engine 3 in PHP 7 (2015). PHP 8.0 (2020) added a JIT; 8.4 and 8.5 continued the line.[[cite: [PHP: History of PHP and related projects](https://www.php.net/manual/en/history.php.php)]] |
| | |
| | The engine has always been C. Hundreds of thousands of lines of reference-counted `zval` structures, hand-maintained `HashTable` buckets, per-request memory arenas managed by the Zend Memory Manager — and, at the center of today's CVEs, `zend_bailout`: a `setjmp`/`longjmp` pair that PHP uses to abandon a request on fatal error, including memory-limit exhaustion. |
| | |
| - | The line was not always smooth. PHP 6 — the ambitious Unicode-native rewrite — collapsed around 2010 and was abandoned outright, which is why version numbers jump from 5.6 to 7.0. Governance stayed thin for years: it took the near-departure of key maintainers and the resulting "bus factor" alarm to produce the PHP Foundation in November 2021 — the first paid, coordinated maintenance structure the language had ever had, twenty-six years in.[[cite: [PHP: History of PHP](https://www.php.net/manual/en/history.php.php)]] |
| + | The line was not always smooth. PHP 6 — the ambitious Unicode-native rewrite — collapsed around 2010 and was abandoned outright, which is why version numbers jump from 5.6 to 7.0. Governance stayed thin for years: it took public "bus factor" warnings about the volunteer maintenance load to produce the PHP Foundation in November 2021 — the first paid, coordinated maintenance structure the language had ever had, twenty-six years in.[[cite: [PHP: History of PHP](https://www.php.net/manual/en/history.php.php)]] |
| | |
| | Two performance decisions made three decades ago define the blast radius today: |
| | |
| | - **Persistent workers.** PHP abandoned the CGI model — one fresh process per request, total isolation — because it was too slow. mod_php embeds the interpreter inside Apache; PHP-FPM keeps workers alive across hundreds of requests. That is exactly what makes a cross-request use-after-free possible: there has to be a *next* request on the same worker for the stale state to poison.[[cite: Datadog Security Labs, [CVE-2026-109619 mechanics](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | - **Shared engine globals.** The engine tracks per-request and per-process state through macro-globals — `EG()` (executor globals), `BG()` (basic globals) — which the public exploit chains corrupt to reach function pointers like `zif_system`.[[cite: [php2shell PoC — attack chain documentation](https://github.com/ikow/php2shell-poc)]] |
| | |
| - | The security record has been thin for most of that history. PHP underwent its **first external security audit only in 2024**, funded by the Sovereign Tech Agency through OSTIF and performed by QuarksLab — twenty-nine years after the project's founding.[[cite: PHP Foundation, [security release post](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)]] And the specific feature at the center of the exploit chains — `session.upload_progress`, which fires during request body parsing before application code — was added in PHP 5.4.0 (2012), enabled by default, and barely used by anyone. A forgotten feature in the pre-authentication path is the classic place a bug goes to sleep for a decade. |
| + | The security record has been thin for most of that history. PHP underwent its first external security audit **in years** in 2024 — funded by the Sovereign Tech Agency through OSTIF, performed by QuarksLab — twenty-nine years after the project's founding.[[cite: PHP Foundation, [security release post](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)]] And the specific feature at the center of the exploit chains — `session.upload_progress`, which fires during request body parsing before application code — was added in PHP 5.4.0 (2012), enabled by default, and little-used in practice. A forgotten feature in the pre-authentication path is the classic place a bug goes to sleep for a decade. |
| | |
| | This is not PHP's first engine-level escape. In June 2024, CVE-2024-4577 — an argument-injection flaw in PHP-CGI on Windows — was exploited in the wild within days of disclosure, including by ransomware operators. The pattern recurs because the architecture that produces it hasn't changed: a C codebase with manual memory discipline, non-local error handling, and a process model that reuses workers indefinitely.[[cite: [CVE-2024-4577 — PHP-CGI argument injection](https://www.tenable.com/cve/CVE-2024-4577)]] |
| | |
| | # Anatomy of the two flaws |
| | |
| | Both CVEs are the *same shape of bug*: a fatal error interrupts PHP mid-operation, and the engine later treats the incomplete state as valid. They differ in where the interrupted operation happens. |
| | |
| | ## CVE-2026-109618 — the out-of-memory corruption |
| | |
| | PHP turns a form submission into `$_POST` by inserting each field into a `HashTable`. In the vulnerable versions, the insertion helper `_zend_hash_str_add_or_update_i()` ran its steps in a fatal order — incrementing the table's bucket counters *before* allocating the key: |
| | |
| | ```c |
| | idx = ht->nNumUsed++; |
| | ht->nNumOfElements++; |
| | p = ht->arData + idx; |
| | p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); |
| | ``` |
| | |
| | If that `zend_string_init` allocation pushes the request over `memory_limit`, PHP raises a fatal error and `zend_bailout`'s `longjmp()` abandons the request — leaving the counters already advanced over a bucket whose key and value were never initialized.[[cite: Datadog Security Labs, [exploitation walkthrough, stage 1](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| | The request is dead, but the engine still has to *clean it up*. When request shutdown destroys the array, it walks every counted bucket — including the phantom — and interprets whatever stale heap bytes it finds there as a PHP value. Datadog demonstrated the full chain: an attacker times the allocation failure, sprays the heap so the unfinished bucket lands on controlled data, and the cleanup routine calls a fabricated destructor pointer — `pDestructor` overwritten to reach `zif_system` — executing a command as the web worker.[[cite: Datadog Security Labs, [stages 2–3: heap spray to function pointer](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| | On Apache with mod_php the OOM trigger is reliable with default settings — an unauthenticated request, no application bug required. On PHP-FPM, RCE was confirmed through application-level code paths. The same bugs also yield unauthenticated **memory disclosure** — reading passwords, session tokens, API keys, and certificates out of the worker's heap, including credentials belonging to *other requests*. A PHPSESSID-based technique supplies the memory addresses needed to defeat ASLR.[[cite: PHP Foundation, [security release post](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)]][[cite: Datadog Security Labs, [memory disclosure and PHPSESSID technique](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| | ## CVE-2026-109619 — the stale global state |
| | |
| | The second flaw crosses the request boundary. A fatal error mid-request can leave module-level global pointers — engine state expected to be per-process — still aimed at per-request heap that request shutdown has already freed. The next request handled by the same PHP-FPM worker touches those stale references before normal initialization resets them: a textbook use-after-free, unauthenticated, carrying over from one anonymous visitor to the next.[[cite: NCSC, [vulnerability descriptions](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php)]][[cite: Datadog Security Labs, [stale-state mechanics](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| | Wiz grouped four exploitable bug instances under the two CVEs: an upload-progress unserializer use-after-free and a remote memory-disclosure under 109618; a fatal-error backtrace use-after-free and an OPcache type-confusion under 109619. Demonstrated results: pre-authentication RCE on current WordPress, Drupal, and Joomla container images, plus application-agnostic remote memory read. Wiz also noted there are *other tracked and likely untracked variants* of the pattern in the codebase — the patch addresses the root mechanism, not just the four instances.[[cite: Wiz Research, [vulnerability table and variant note](https://www.wiz.io/blog/php2shell-php-rce)]] |
| | |
| - | The confirmed trigger vector for the stale-state path is `session.upload_progress` — the 2012 feature nobody uses, firing inside the RFC 1867 body parser, before any PHP application code has run.[[cite: [php2shell PoC — rfc1867 pre-auth note](https://github.com/ikow/php2shell-poc)]] |
| + | The confirmed trigger vector for the stale-state path is `session.upload_progress` — a little-used feature dating to 2012, firing inside the RFC 1867 body parser, before any PHP application code has run.[[cite: [php2shell PoC — rfc1867 pre-auth note](https://github.com/ikow/php2shell-poc)]] |
| | |
| | A note on exploitability, because precision matters: these are not point-and-shoot bugs. The OOM chain requires the attacker to calibrate request size against the target's `memory_limit`, keep the worker alive through the failed request, and defeat ASLR with a memory-disclosure step — the PHPSESSID technique — before the destructor call lands. Datadog documented each requirement separately, and the public PoC's own documentation is frank that the remote chain is version- and configuration-specific. "Exploitable" and "automatically exploitable everywhere" are different claims; the first is proven, the second is not — which is the difference between "patch today" and "the web is already down."[[cite: Datadog Security Labs, [exploit requirements and limitations](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]][[cite: [php2shell PoC — universal? no](https://github.com/ikow/php2shell-poc)]] |
| | |
| | # Why this was allowed to exist |
| | |
| | The architectural critique writes itself once you list the preconditions. Every element of the bug is a design decision: |
| | |
| | **`longjmp()` across initialization boundaries.** C's non-local jump abandons a stack mid-construction. Everything between "operation started" and "operation complete" — half-incremented counters, half-copied strings, half-built structures — is left exactly where the jump found it, for the cleanup path to trip over. PHP uses this mechanism for every fatal error and every OOM event in every request. |
| | |
| | **Count-before-allocate ordering.** The `nNumUsed++` before `zend_string_init` is a two-line ordering bug that survived fourteen years of code review because the failure mode it creates is invisible until you make allocation fail *at that line* — which is precisely what an attacker controlling request size can arrange on demand. |
| | |
| | **Destructor chains over partially-initialized state.** Cleanup iterates counted buckets and trusts the count. The count is a claim about memory that was never allocated. The destructor call that follows is the RCE. |
| | |
| | **The persistent-worker boundary.** A per-request heap living inside a process that outlives the request is the shared-state boundary both CVEs cross. In 1995, PHP ran one process per request and this bug class *could not exist* — there was no "next request" to inherit stale state, and a corrupted worker died with the request that corrupted it. Every performance optimization since has widened exactly that boundary. |
| | |
| | **The pre-authentication parser.** `session.upload_progress` executes during RFC 1867 body parsing — before the application, before authentication, before any defensive code a developer might write. Application-level security cannot reach the vulnerable path; Datadog confirmed this independently.[[cite: Datadog Security Labs, ["application-level authentication can't block an attack"](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| - | **The audit gap.** A codebase serving 70% of the web received its first external security audit in 2024. The bugs survived not because they were hidden — they were in the most-executed code in the engine — but because turning them into an exploit required chaining heap-spray timing, ASLR defeat, and destructor fabrication, and nobody had done the work. |
| + | **The audit gap.** A codebase serving 70% of the web went years between external security audits; the first in years only arrived in 2024, on a grant. The bugs survived not because they were hidden — they were in the most-executed code in the engine — but because turning them into an exploit required chaining heap-spray timing, ASLR defeat, and destructor fabrication, and nobody had done the work. |
| | |
| | # The AI inflection point |
| | |
| - | That last point is where 2026 differs from every PHP CVE before it. Wiz is explicit about the method: the vulnerable code sat there for over a decade, but demonstrating exploitability required "an exceptionally complex chain of steps, at the edge of what human researchers could practically achieve." Frontier AI models — with the work coordinated across Wiz, OpenAI, and Calif — reduced exploit development from a months-long manual grind to hours per adaptation.[[cite: Wiz Research, ["The AI inflection point"](https://www.wiz.io/blog/php2shell-php-rce)]] |
| + | That last point is where 2026 differs from every PHP CVE before it. Wiz is explicit about the method: the vulnerable code sat there for over a decade, but demonstrating exploitability required "an exceptionally complex chain of steps, at the edge of what human researchers could practically achieve." Frontier AI models — with the work coordinated across Wiz, OpenAI, and Calif — are what Wiz credits with enabling the team to "develop and adapt working exploits across multiple applications in hours."[[cite: Wiz Research, ["The AI inflection point"](https://www.wiz.io/blog/php2shell-php-rce)]] |
| | |
| - | This is the first widely-documented case of LLM-assisted research producing a critical, internet-scale memory-corruption exploit — and both sides of the coin landed on the same day. The good news version: AI found a fourteen-year-old flaw in code humans had audited and missed, and a coordinated industry patched it before confirmed exploitation. The other version, which Wiz states plainly: *"complexity may no longer provide a meaningful barrier."* Every latent memory-corruption flaw in every large C codebase — and the web runs on several of them — just moved closer to the practical-exploit threshold. The same capability that found this bug is available to people who would not report it.[[cite: Wiz Research, [AI inflection point framing](https://www.wiz.io/blog/php2shell-php-rce)]] |
| + | This is among the first publicly documented cases of LLM-assisted research producing a critical, internet-scale memory-corruption exploit — and both sides of the coin landed on the same day. The good news version: AI found a fourteen-year-old flaw in code humans had audited and missed, and a coordinated industry patched it before confirmed exploitation. The other version, which Wiz states plainly: *"complexity may no longer provide a meaningful barrier."* Every latent memory-corruption flaw in every large C codebase — and the web runs on several of them — just moved closer to the practical-exploit threshold. The same capability that found this bug is available to people who would not report it.[[cite: Wiz Research, [AI inflection point framing](https://www.wiz.io/blog/php2shell-php-rce)]] |
| | |
| | The honest reading of October 21 is not "PHP got unlucky." It is that the economic model of vulnerability research inverted, on a codebase whose defense was partly *that nobody could afford to attack it this way*. |
| | |
| | # Could it have been avoided? |
| | |
| | Three honest answers, at three different levels. |
| | |
| | **At the code level — yes, trivially.** Swap two lines: allocate the key, *then* count the bucket. The 109618 variant dies. Initialize-on-failure or explicit "dirty bucket" flags kill the cleanup path. These are the kinds of bugs a rigorous static-analysis or bounded-model check flags mechanically — which is why the 2024 QuarksLab audit and today's strengthened pipeline matter more than any single patch.[[cite: PHP Foundation, [post-release strengthening measures](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)]] |
| | |
| - | **At the design level — partially.** Process-per-request (the CGI model PHP was born with) prevents the cross-request half of the bug class by construction; `php-fpm`'s `pm.max_requests` worker recycling bounds the damage window but doesn't close it. Failing a whole worker on fatal error rather than attempting surgical cleanup would trade availability for state cleanliness. Each of these was a known option; each cost performance or compatibility, and the ecosystem chose performance every time for twenty years. |
| + | **At the design level — partially.** Process-per-request (the CGI model PHP was born with) prevents the cross-request half of the bug class by construction; `php-fpm`'s `pm.max_requests` worker recycling bounds the damage window but doesn't close it. Failing a whole worker on fatal error rather than attempting surgical cleanup would trade availability for state cleanliness. Each of these was a known option; each cost performance or compatibility, and the ecosystem chose performance every time for more than two decades. |
| | |
| | **At the language level — structurally.** Both CVEs are use-after-free / uninitialized-read patterns (CWE-416 family). In Rust, the borrow checker rejects the code shapes at compile time; in Ada/SPARK, access-type discipline plus proof obligations eliminate the dangling-reference class; in any managed runtime with a GC, the "freed memory reinterpreted as live object" primitive simply isn't available to an attacker. You cannot write `_zend_hash_str_add_or_update_i`'s bug in a language where the count and the allocation are one indivisible operation — which, in a memory-safe language, they are.[[cite: [Tenable — CVE-2026-109618, CWE-416 classification](https://www.tenable.com/cve/CVE-2026-109618)]] |
| | |
| | The fair counterpoint is the one PHP cannot escape: seventy percent of the web is written in it, the C engine is what it is, and "rewrite it in Rust" is a 30-year project, not a patch. What PHP *can* do — what it did on October 21 — is fix the mechanism, harden the lifecycle boundary, and fund the audits it skipped for three decades. |
| | |
| | # The zero-heap counterpoint |
| | |
| | This site maintains [[AdaLogic_BASIC|AdaLogic BASIC]], and the comparison is worth one honest paragraph — not because anyone is migrating their WordPress fleet to ALB, but because these CVEs are a clean illustration of what "memory safety" actually costs and buys at the *language-design* level. |
| | |
| | ALB's runtime allocates no per-request heap. State lives in statically-sized structures allocated at initialization; a request — or a tick, or a BBS session — cannot trigger a mid-operation allocation failure because there is no mid-operation allocation. There is no `longjmp` analogue: error paths are structured, local, and cannot leave counters desynchronized from backing memory, because the two are the same object. A "use-after-free across worker requests" like 109619 isn't a bug ALB has patched; it's a bug the design cannot express — the same property SPARK Ada proves formally and Rust enforces through ownership. See [[Ada_vs_Rust_Memory_Safety]] for the deeper comparison. |
| | |
| | None of that makes ALB a web-scale PHP replacement — the ecosystems aren't comparable and pretending otherwise would be dishonest. The point is narrower and more useful: the classes of failure in today's CVEs are *choices*, not laws of nature. They were chosen once in 1995 for performance, chosen again every time the audit didn't happen, and re-chosen — correctly, this time, and for the last time in this instance — on October 21, 2026. |
| | |
| | # What to do right now |
| | |
| | **Patch. Everything else is a stopgap.** |
| | |
| | | PHP branch | Affected | First fixed release | |
| | |---|---|---| |
| | | 8.5 | ≤ 8.5.11 | **8.5.12** | |
| | | 8.4 | ≤ 8.4.26 | **8.4.27** | |
| | | 8.3 | ≤ 8.3.35 | **8.3.36** | |
| | | 8.2 | ≤ 8.2.34 | **8.2.35** | |
| | | 7.4 / 8.0 / 8.1 | all | source backports only — check your vendor | |
| | |
| | All web SAPIs are in scope: mod_php, PHP-FPM, `php-cgi`, FrankenPHP, LiteSpeed. When building the inventory, count containers and products that bundle their own PHP — the CLI binary's version tells you nothing about what serves the site. After patching, restart or replace every worker still running the old runtime.[[cite: Datadog Security Labs, [fixed versions and remediation steps](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | |
| | **If you cannot patch today**, the partial mitigations — partial, not protective: |
| | |
| | - For **109618**: enforce `memory_limit=-1` at the administrator level, paired with request-body limits at the frontend and memory caps on the container. Do *not* substitute `RLIMIT_AS`/`RLIMIT_DATA` — process-level limits trigger exactly the allocation failures this bug feeds on. An attacker who exhausts real system memory can still destabilize the host.[[cite: PHP / Datadog, [temporary mitigation guidance](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | - For **109619**: set `session.upload_progress.enabled=0` and, on PHP 8.5, `fatal_error_backtraces=0`, in configuration loaded *before* request parsing — then restart every worker. Hiding the errors does not prevent access to stale state; it only removes the known trigger.[[cite: Datadog Security Labs, [mitigation caveats](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]] |
| | - Put a reverse proxy or WAF in front of PHP — boundary controls that block oversized or malformed request bodies before they reach the vulnerable parser. The coordinated industry partners shipped out-of-box rules today.[[cite: Wiz Research, [industry mitigation coordination](https://www.wiz.io/blog/php2shell-php-rce)]] |
| | - On PHP 8.1 and older: CISA's guidance is blunt — take PHP-based systems offline until patched or upgraded. Source backports exist for 7.4/8.0/8.1, but they land in vendor packages on vendor time.[[cite: [CISA alert](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching)]] |
| | |
| | **Detection**: hunt for memory-exhaustion errors during *request initialization* (before app code), unusually large form submissions, and — the tell — PHP worker processes spawning shells or downloading tools. Successful exploitation doesn't necessarily crash the worker.[[cite: Datadog Security Labs, [detection guidance](https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/)]][[cite: [CISA monitoring guidance](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching)]] |
| | |
| | One honest caveat for after the patch: public PoC documentation describes additional exploitation paths — application-dependent chains through `unserialize()` sinks, serializer-handler mismatches, and session-injection LFI — reported as still functional on 8.5.12. These are *different* bug classes needing application cooperation, not the CVEs themselves — but the same codebase, the same architecture, and Wiz's warning about untracked variants should temper any sense that today's patch is the end of it.[[cite: [php2shell PoC — post-patch path status](https://github.com/ikow/php2shell-poc)]] |
| | |
| | # The read |
| | |
| - | Heartbleed (2014) showed what a memory bug in shared infrastructure looks like. Log4Shell (2021) showed what a dependency nobody inventories looks like. PHP2Shell is both at once — a memory-safety failure in infrastructure so shared that a "Hello World" page inherits it, disclosed by the first AI-assisted exploit campaign to hit internet scale. |
| + | Heartbleed (2014) showed what a memory bug in shared infrastructure looks like. Log4Shell (2021) showed what a dependency nobody inventories looks like. PHP2Shell is both at once — a memory-safety failure in infrastructure so shared that a "Hello World" page inherits it, disclosed by an AI-assisted exploit campaign at internet scale. |
| | |
| - | The elephant's jaw was always glass. The engine is C, the error model is `longjmp`, the worker is shared, the parser runs before authentication, and the first external audit arrived 29 years in. None of this was secret; all of it was accepted, because the alternative — the rewrite, the migration, the discipline — always cost more than the risk *seemed* to. Today an AI model repriced that risk for everyone, defenders included. |
| + | The elephant's jaw was always glass. The engine is C, the error model is `longjmp`, the worker is shared, the parser runs before authentication, and external audits arrived decades late. None of this was secret; all of it was accepted, because the alternative — the rewrite, the migration, the discipline — always cost more than the risk *seemed* to. Today an AI model repriced that risk for everyone, defenders included. |
| | |
| | Patch tonight. Then keep the uncomfortable fact somewhere you'll see it: the language holding up most of the web is written in the one language family where these bugs are always possible, and the difference between "theoretical" and "pre-auth RCE on WordPress" turned out to be a question of who could afford to look hard enough. Now anyone can. |
| | |
| | # See also |
| | |
| | - [[Ada_vs_Rust_Memory_Safety]] — the memory-safety comparison these CVEs argue for |
| | - [[AdaLogic_BASIC]] — the zero-heap runtime design referenced above |
| | - [[HolyC]] — a different one-man language, a different set of trade-offs |
| | |
| | [[Category:News]] |
| | [[Category:Languages]] |
| | [[Category:Reference]] |
| | |