Would Rust Have Prevented PHP2Shell? The Bug Class It Rejects

Max WellsMax WellsFounder of Rustify
By Max Wells23 min read
PHP2Shell vs Rust

PHP2Shell is a pair of critical memory-safety bugs in the PHP engine itself, not in WordPress or a plugin, and both come from C patterns that safe Rust refuses to compile: request memory reachable before it is initialized, and worker-wide pointers that outlive the request that owns them. Rust would not have saved PHP from every bug, but this specific class would have stopped at the first build instead of shipping for over a decade.

If you run PHP, patch first: the checklist below covers versions, mitigations, and what to hunt for. Then comes the question security and platform teams are already hearing in postmortems: would a memory-safe language have prevented this, and should it change how we build the next service? This article answers both with compiler output, not slogans.

By Max Wells, updated October 22, 2026

TL;DR: On October 21, 2026, PHP shipped 8.5.12, 8.4.27, 8.3.36, and 8.2.35 to fix CVE-2026-109618 ("RCE via OOM corruption") and CVE-2026-109619 ("RCE via stale global state"), both rated CVSS 4.0 9.2. An unauthenticated attacker can reach code execution with crafted multipart requests, before any application code runs.

  • Scope: PHP itself, so every framework on top of it, with reporters saying releases back to PHP 5.4 are affected
  • Root cause: a longjmp() bailout on out-of-memory leaves half-built data behind, and persistent workers keep pointers into a freed request heap
  • Exposure: 73.5% of the 2.6 million internet-facing services that report a PHP version run 8.1 or older, branches with no official fixed release
  • Rust angle: both patterns are compile errors in safe Rust (we show the exact errors below), and Rust's allocator aborts instead of resuming
  • Verdict: patch PHP today; for new security-sensitive backend services, this is a concrete argument for a memory-safe language, not a reason to rewrite every PHP app
  • Running PHP right now? Skip to What Should PHP Teams Do Right Now?

What Is PHP2Shell?

PHP2Shell is the name for two remote code execution vulnerabilities in the PHP interpreter, disclosed and patched on October 21, 2026, that let an attacker run code on a web server without logging in. About 69% of websites whose server-side language is known run PHP, according to W3Techs, which is why the reaction was so drastic.

CVE-2026-109618CVE-2026-109619
PHP advisory titleRCE via OOM corruptionRCE via stale global state
Bug typeUninitialized data read after an out-of-memory abortUse-after-free of request memory through worker globals
CVSS 4.09.2 (AV:N/AC:H/AT:P/PR:N/UI:N)9.2 (same vector)
AuthenticationNoneNone
Confirmed RCEApache mod_php with prefork workersmod_php (fatal backtrace path), mod_php and PHP-FPM (session lifecycle)
Fixed in8.5.12, 8.4.27, 8.3.36, 8.2.35same releases
PHP security advisory GHSA-w8hw-w9fx-j79w, RCE via OOM corruption, rated Critical 9.2 with affected and patched PHP versions
The official php-src advisory for CVE-2026-109618: Critical, CVSS 4.0 9.2, patched in 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Its summary names the root cause directly: PHP uses longjmp() for out-of-memory errors.

Two details matter for everything that follows. First, this is a bug in the language runtime, so updating your WordPress, Laravel, or Symfony version does nothing. You have to update PHP. Second, PHP 7.4, 8.0, and 8.1 received source patches only, because they are end of life. The vulnerable population is not just unpatched servers but servers on versions nobody ships binaries for anymore.

The bugs were reported independently by Ryan Austin of Calif, by OpenAI, and by Wiz Research. Calif says PHP was one of the targets of OpenAI's Patch the Planet initiative. Wiz demonstrated unauthenticated RCE against the latest container images of WordPress, Drupal, and Joomla, and warns that "even a simple "Hello World" application" can be vulnerable. Wiz places the bug in the same family as Heartbleed (2014) and Log4Shell: a flaw in shared infrastructure that nearly every deployment inherits.

AI found it, and that is part of the story

All three reporters used AI. Wiz writes: "We used Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol throughout this research," and says "a single researcher completed in a week what previously would have taken a world-class team months of work." Calif stresses that it needed "no special harness, or access to models that weren't generally available to everyone." OpenAI called the bug "one of the most impactful security issues in recent memory," according to NBC News. The credit story is overlapping attributions, not a dispute: Calif reported first, on August 11, OpenAI the same day, and Wiz found further vectors on its own.

Fact vs Claim: What Spread About PHP2Shell, and What Is Confirmed

The panic was right about severity and wrong about many details. The disclosure leaked a day early, so the first wave of posts, including our own LinkedIn post, repeated numbers that the primary sources later corrected.

Claim that circulatedStatusWhat the primary sources say
"CVSS 10.0"WrongPHP rates both 9.2. watchTowr gave its own 10.0.
"75% of the web runs PHP"OverstatedW3Techs: 69.3% of sites with a known server-side language
"One HTTP request is enough"SimplifiedCVE-2026-109619 needs a producing request and a later consuming request on the same worker, and both advisories rate attack complexity high
"A working exploit leaked on Hacker News"WrongHacker News got the CVE IDs and the wrong score. A Calif proof-of-concept video was published by mistake. The PHP Foundation reported no functional public exploit at release.
"Nobody caught it for 14 years"Wrong, and worseMemory-limit RCE was first documented by Stefan Esser in 2004, a related report followed in 2020 (bug #78876), and a 2024 audit found the first signs of this exact problem
"Turning off upload progress fixes it"Partialsession.upload_progress.enabled=0 removes one trigger for CVE-2026-109619, not CVE-2026-109618
Exploitation before disclosureConfirmed attemptAmazon logged an attempt in its MadPot honeypots on October 17, four days early
"59% of PHP servers run 8.1 or older"SupersededMallory corrected its own Shodan figure to 73.5% of 2.6 million services on October 21
"Big sites went dark"ConfirmedNBC News: 4chan, Something Awful, parts of Wikipedia, and Pinboard went offline
"CISA told everyone to go offline"Accurate, with nuanceCISA said PHP systems "should" go offline until patched, and had not observed exploitation at publication

Alexandre Daubois, who works on PHP's security team, published a useful correction of the rumors the same day. His note on the version lists is a good example: one circulating list of affected versions, from watchTowr, included a PHP 6 that never shipped.

How Did PHP2Shell Leak Before the Patch?

The fix was ready for a coordinated release on October 22, but the CVE IDs, a proof-of-concept video, and a patched binary all escaped first, so PHP pulled the release forward a full day. The PHP Foundation's timeline is unusually candid about it.

Timeline of PHP2Shell from Calif's discovery on August 1, 2026 to the PHP release at 14:00 UTC on October 21, 2026, including the leak of CVE IDs, a PoC video, and Chainguard patched images
Ten weeks of private disclosure, then 23 hours of leaks. Rustify diagram from the PHP Foundation, Calif, Wiz, and CISA timelines (UTC).

The sequence, in UTC:

  • October 20, around 15:00: the two CVE IDs circulate publicly, with a wrong 10.0 score attached.
  • October 20, 19:30: the PHP team moves the release from October 22 to October 21 at 14:00.
  • October 20, around 20:00: Calif publishes a proof-of-concept video by mistake.
  • October 21, 07:41: Chainguard publishes patched container images more than six hours before the release. The PHP Foundation's verdict: "Any attacker could now study the fix in binary form while every other PHP user was still waiting for the release."
  • October 21, 14:00: PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 ship.

The binary leak matters most. Diffing a patched binary against the previous one is a standard way to rebuild an exploit, and with AI tools that step is faster than ever. The Foundation says decisions inside its security process can now be made by majority, and it has added staff: an Ecosystem Security Team created in May 2026, Volker Dusch as AI Security Engineer in Residence, and new security team members Alexandre Daubois (July) and Daniel Scherzer (September).

How Did the PHP2Shell Exploit Work?

The attacker forces PHP to run out of memory at a moment chosen by them, which interrupts the engine halfway through building an internal structure, and then gets PHP to trust that broken structure. Everything else is about choosing that moment and reusing what is left behind.

PHP2Shell exploit chain: oversized multipart request, allocation failure, longjmp bailout, half-built hash bucket or stale global, memory trusted at shutdown or by the next request, remote code execution, with the safe Rust checks that stop each step
The full chain on one page. Steps 3 to 5 are exactly where safe Rust intervenes: abort on allocation failure, E0381 for uninitialized reads, E0597 for references that outlive the request.

Step 1: control when the allocator fails

PHP enforces memory_limit per request. The CVE-2026-109618 advisory lists several ways a request that stays under post_max_size on the wire can still blow through memory_limit once PHP decodes it:

  • a multipart request without a usable declared content length, which can pass the initial post_max_size check;
  • a single multipart part with an unlimited number of headers, which costs far more in PHP than on the wire;
  • empty file controls that do not count toward max_file_uploads but still create metadata arrays, multiplied by deeply nested field names.

All of this happens while PHP parses the request body, before your framework's router even exists.

Step 2: the bailout leaves half-built data

When an allocation fails, PHP does not return an error to the caller. It calls longjmp(), a C mechanism that jumps straight to an error boundary and skips every line of code in between. Any function that was in the middle of initializing a structure never finishes.

Calif's write-up names one concrete spot. In _zend_hash_str_add_or_update_i, the hash table's bucket count is incremented before the key is allocated. If that key allocation is the one that fails, the table now claims a bucket that was never written. Request shutdown then walks the table and treats the uninitialized bucket as a live entry.

_zend_hash_str_add_or_update_i()
  1. ht->nNumUsed++                 <- table now counts one more bucket
  2. key = allocate(len)            <- memory_limit hit here
        |
        +--> longjmp() to bailout   <- steps 3 and 4 never run
  3. bucket->key = key
  4. bucket->val = value
 
request shutdown
  for each bucket in ht:            <- reads the half-built bucket as valid
      destroy(bucket)               <- attacker-shaped garbage interpreted as a pointer

Step 3: the next request finds stale pointers

The second CVE is about what survives the request. PHP keeps some global state per worker: error state, session state, extension state. In persistent setups like Apache prefork or PHP-FPM, one worker process serves many requests in a row. If a global still points into the previous request's heap after that heap is freed, the next request on the same worker can use it.

Wiz documented several of these globals:

  • PS(id), the session ID, published during upload-progress handling. After an OOM abort it points at freed memory, and a later request returns reclaimed heap bytes inside a PHPSESSID cookie. Imperva measured about 67 KB of process memory leaked this way.
  • BG(unserialize), the unserializer's bookkeeping, which a fatal error can leave published after the heap is destroyed.
  • EG(last_fatal_error_backtrace), the last fatal error's backtrace, which "still points to memory from the previous request," in Wiz's words.
  • An OPcache shared-memory slot allocated but never published, which later resolves "to a pointer of the wrong type."

The memory disclosure defeats address randomization, and the use-after-free primitives turn into code execution. Session upload progress is enabled by default, and it runs while PHP parses a multipart body, which is why no login and no application code are needed.

worker process (persistent, serves request after request)
+----------------------------------------------------------+
|  globals: PS(id) ---------+   BG(unserialize) ---+       |
|                           |                      |       |
|  request 1 heap  [ session id | unserializer ctx ]       |
|                     ^ OOM bailout, heap freed            |
|                                                          |
|  request 2 heap  [ attacker-controlled bytes .... ]      |
|                     ^ globals now point here             |
|                                                          |
|  request 2 cleanup reads PS(id) -> leaks or corrupts     |
+----------------------------------------------------------+

Why Wasn't It Fixed Earlier?

PHP's own foundation calls the delay "a structural failure, caused by both the security team's decision process and its lack of resources." The bug was not invisible. It was known, theorized, and deprioritized.

The PHP Foundation's post-mortem gives the history. In 2024, PHP's first external audit in years, funded by the Sovereign Tech Agency, organized by OSTIF, and run by Quarkslab, led a core developer to the first signs of the OOM problem. An exploit was theorized but not demonstrated. Several competing fixes existed, each breaking backward compatibility, adding denial-of-service risk, or forcing changes in third-party extensions. Consensus stalled.

Then, in August 2026, Calif reported an unconditional RCE with the same root cause on August 11, OpenAI reported it the same day, and Wiz followed on August 24. Calif's researcher started on August 1 from Esser's 2004 memory-limit research and the 2020 report, so the gap between "theorized" and "reported with a working exploit" was ten days.

The final fix explains why consensus was hard. It touches 390 files, according to Daubois, because no one could audit the assumption "allocation can jump out of me at any line" across every C function in the engine. That is the real lesson for language choice: the bug was not one bad line. It was an invariant the C compiler cannot check, spread across the codebase.

Would Rust Have Prevented PHP2Shell?

Safe Rust rejects both root-cause patterns at compile time, and its standard allocator aborts instead of resuming, so this bug class would not have shipped. That is a narrow, testable claim, so we tested it. Both snippets below were compiled with rustc 1.97.1.

Pattern 1: a worker global pointing into request memory

This is the CVE-2026-109619 shape: request-owned data published into state that outlives the request.

use std::sync::Mutex;
 
// Worker-wide state that outlives every request.
static LAST_SESSION_ID: Mutex<Option<&str>> = Mutex::new(None);
 
fn handle_request(body: String) {
    let session_id: &str = &body[..8];
    *LAST_SESSION_ID.lock().unwrap() = Some(session_id);
} // `body` is freed here
error[E0597]: `body` does not live long enough
  |
6 | fn handle_request(body: String) {
  |                   ---- binding `body` declared here
7 |     let session_id: &str = &body[..8];
  |                             ^^^^ borrowed value does not live long enough
8 |     *LAST_SESSION_ID.lock().unwrap() = Some(session_id);
  |      ---------------------- argument requires that `body` is borrowed for `'static`
9 | } // `body` is freed here
  | - `body` dropped here while still borrowed

A static lives for the whole process, so anything stored in it must also live for the whole process ('static). The compiler sees that body dies at the end of the request and refuses the program. To keep a session ID across requests in Rust, you have to store an owned copy, such as a String, which then cannot dangle. The C version compiles, works in every normal test, and becomes a use-after-free only when an attacker aborts the request between "publish" and "clear."

Our guide to Rust ownership and borrowing explains the lifetime rules behind this error in more depth.

Pattern 2: reading a structure before it is initialized

This is the CVE-2026-109618 shape: cleanup code reading a value whose initialization never completed.

struct Bucket {
    key: String,
}
 
fn cleanup(bucket: &Bucket) {
    println!("{}", bucket.key);
}
 
fn main() {
    let bucket: Bucket;
    // allocation for `key` fails before `bucket` is written...
    cleanup(&bucket);
}
error[E0381]: used binding `bucket` isn't initialized
   |
10 |     let bucket: Bucket;
   |         ------ binding declared here but left uninitialized
11 |     // allocation for `key` fails before `bucket` is written...
12 |     cleanup(&bucket);
   |             ^^^^^^^ `bucket` used here but it isn't initialized

Safe Rust tracks initialization per binding and per field. Uninitialized memory exists only behind MaybeUninit, and reading it requires an unsafe block where the programmer explicitly asserts it is ready.

Pattern 3: the non-local jump itself

The deepest difference is that Rust has no longjmp in safe code, and an out-of-memory allocation does not resume the program. In binaries that link the standard library, handle_alloc_error aborts the process by default. When code panics instead, unwinding runs every Drop implementation on the way out, and a partially constructed value drops only the fields that were actually built.

SituationPHP engine (C, before the fix)Safe Rust
Allocation fails mid-functionlongjmp() to bailout, function never finishesProcess aborts by default, nothing resumes on broken state
Error exits a function earlyCleanup code is skipped unless written by handDrop runs for every initialized value, compiler-generated
Reference outlives its dataCompiles, dangles at runtimeE0597, does not compile
Read before initializationCompiles, reads garbageE0381, does not compile
Global state shared by requestsAny pointer can be storedMust be 'static and Sync, owned data only

Daubois makes the same comparison from inside PHP's security team: Go's runtime calls throw("out of memory"), which recover() cannot catch, and Rust aborts. C++ keeps the PHP-style risk, because any new can throw std::bad_alloc through code that did not plan for it.

The Fix Moves PHP Toward Rust's Model

PHP's patch removes longjmp() for out-of-memory and replaces it with exactly the behavior Rust and Go already have: finish the current operation, then stop cleanly, or kill the process. That convergence is the strongest evidence for the Rust argument in this story.

Per the advisory and Daubois's walkthrough, the patched engine works in two layers:

allocation crosses memory_limit (soft)
   -> allocation still succeeds, a pending error is recorded
   -> the C function finishes initializing its structures
   -> at the next VM safe point (jump, call), PHP throws UnwindExit
   -> multipart parser frees its work, SAPI raises the fatal error
 
allocation crosses memory_hard_limit_percent (default 200% below 1G)
   -> _exit(137): the process dies, no cleanup runs on broken state

The soft limit is graceful unwinding at a known safe point. The hard limit is an abort. In Rust terms, that is Drop-based unwinding plus handle_alloc_error. PHP had to retrofit the model across 390 files after a critical CVE. Rust starts with it.

There is one honest caveat. A patched PHP worker that runs out of memory still dies, just without corruption. The same is true of a Rust service that hits an allocation failure. Memory safety turns this from remote code execution into, at worst, a denial of service on one worker. That is a huge difference in severity, but it is not "nothing happens."

None of this means rewriting a PHP monolith. Patch PHP today. The practical lesson is for the next service you build: when a new backend parses untrusted input or holds long-lived worker state, choosing a memory-safe language for it removes this whole bug class before the first deploy.

Choosing Rust for a real project or team?

Get a second opinion before you commit — architecture, hiring, and migration risk from someone who has shipped it in production.

What Rust Would Not Have Fixed

Rust removes a category of bugs, not the need for security engineering. Three limits are worth stating plainly, because the strongest Rust argument is the one that survives them.

unsafe and C dependencies. A Rust program can opt out with unsafe, static mut, raw pointers, or FFI into C libraries. The guarantee covers safe Rust only. Projects that matter, like the Rust code entering the Linux kernel, keep unsafe small, documented, and reviewed, but it is still where memory bugs can live.

Logic and resource bugs. Unbounded multipart parsing is a design problem in any language. A Rust server that lets one request allocate gigabytes will still fall over. Frameworks like Axum set default body limits for this reason, and you still have to choose sane ones.

Rewriting is not a patch. Nobody can rewrite PHP, WordPress, or a ten-year-old Laravel app in Rust this week. The right response to PHP2Shell is to patch, and our framework on whether to rewrite in Rust applies to the longer-term question: rewrite the parts where a memory bug is catastrophic and the code is stable enough to justify it.

The reverse objection also deserves an answer: "PHP application code is already memory-safe." True. PHP developers almost never write a use-after-free. But the interpreter underneath them is C, and PHP2Shell shows that the runtime's safety is part of your application's attack surface. Updating your app did nothing. Only updating the runtime helped.

How Many PHP Servers Are Still Exposed?

Most internet-facing PHP is not on a branch that received an official fix: 73.5% of 2.6 million services that report a PHP version run 8.1 or older. That figure comes from a Mallory scan of Shodan data taken between 16:04 and 16:22 UTC on October 21, two hours after the release.

Bar chart of 2.6 million internet-facing PHP services by version: 57,002 on PHP 4 to 5.1, 797,060 on 5.2 to 5.6, 857,707 on 7.x, 213,338 on 8.0 and 8.1, and 693,177 on supported 8.2 to 8.5
PHP 7.x and 5.x alone account for more than 1.6 million exposed services. Rustify chart from Mallory's Shodan data, October 21, 2026.

Mallory first published 59%, the figure Better Stack's video recap repeated, then corrected it to 73.5% (1.9 million services). Netlas measured a similar 75.9%. Read these numbers as a floor on the long tail, not an exact count: they count IP and port pairs rather than organizations, a version banner does not prove a host is exploitable, and Linux distributions often backport fixes without changing the version string. The direction is still clear. PHP 7.x, which left security support years ago, is the single largest group, and PHP 5.2 to 5.6 is close behind.

This is the long-tail problem every runtime has, but it is sharper for a bug in the interpreter. A team on PHP 7.4 cannot fix PHP2Shell by updating WordPress or Laravel. It needs a vendor backport, a source build, or a major version upgrade under deadline.

What Should PHP Teams Do Right Now?

Update PHP to a fixed release, restart every worker, and assume any internet-facing server that stayed unpatched after October 21 needs a compromise check. CISA's alert says organizations "should take PHP-based systems offline until they can be patched or updated," and the UK NCSC guidance says to look for backdoors before upgrading a system that stayed exposed.

A practical order:

  1. Inventory every PHP install, including control panels, appliances, and forgotten servers. Bundled PHP counts.
  2. Upgrade to 8.5.12, 8.4.27, 8.3.36, or 8.2.35. On 7.4 to 8.1, use your distribution's or LTS vendor's backport, or build from source.
  3. Restart all workers. Old processes keep the vulnerable code in memory.
  4. If you cannot update yet, apply the partial mitigations from the advisories: memory_limit=-1 set administratively (the primary configuration mitigation for CVE-2026-109618), session.upload_progress.enabled=0, on PHP 8.5 fatal_error_backtraces=0, and pm.max_requests=1 if performance allows. Restart every worker after changing them. Both advisories are explicit that only updating is a complete fix.
  5. Put a WAF rule in front while you patch. CISA describes a web application firewall as partial protection, and Cloudflare's CTO told NBC News: "We are seeing early attacks emerging and are updating mitigations as new vectors show up."
  6. Hunt for signs of attack: POST requests containing PHP_SESSION_UPLOAD_PROGRESS (an IOC from Amazon), unusually large PHPSESSID cookies, memory-limit errors after multipart requests, unexplained worker churn, and, per CISA, "PHP worker processes launching shells or downloading tools."

Do not use RLIMIT_AS or RLIMIT_DATA as a stopgap. The advisory warns that they trigger the same vulnerable path.

What PHP2Shell Means for Backend Engineers

PHP2Shell is the kind of incident that turns memory safety from a policy slogan into a line item. Governments did not just publish advice this week. They pulled their own sites.

In the United States, CISA told organizations to take PHP systems offline until patched, and NBC News reported that the NSA made the same recommendation. CERT-EU went in the same direction: "Organisations should consider isolating internet-facing applications from public networks until a fix is available." Well-known sites followed. NBC News reports that 4chan, Something Awful, some parts of Wikipedia, and Pinboard went offline, along with sites in Japan.

In the Netherlands, the national cyber security agency NCSC took its own website offline as a precaution, according to Techzine. Better Stack's video recap adds DigiD and the Dutch parliament, plus university sites in Edinburgh, Bristol, and Oxford. Finland's state IT center Valtori shut down public-facing central government web services as a precaution, and reports stress it saw no breach. The cost was not only the risk of compromise. It was the downtime organizations chose to avoid it.

Two trends make this more than a one-off. First, AI models now shorten the gap between "theorized bug" and "working exploit" from years to days: Calif went from public research to a reported exploit in ten days with generally available models, and Wiz compares one researcher's week to months of work for a world-class team. Bugs that stalled because nobody could prove exploitability will get proven. Second, the agencies have been saying this for years. Our breakdown of what the NSA, CISA, and the White House say about memory-safe languages covers the policy side. PHP2Shell is what that policy looks like when it lands on a Tuesday night.

For a backend engineer, the practical takeaway is about where you put memory-unsafe code. Rust lets you build the parts that parse untrusted input, such as HTTP bodies, sessions, uploads, and protocol handlers, in a language where this bug class is a compile error. Teams already doing this with Rust backends built on Axum get the same per-request ownership model shown above for free: each request's data is owned by its handler and dropped when the handler finishes, and nothing can keep a borrowed reference to it.

Where Does Rust Fit Next to a PHP Stack?

Rust fits best as the next new service at the edge of a PHP stack, the component that touches raw untrusted input, while the PHP application keeps the business logic it already does well. Nobody needs to retire Laravel or WordPress to benefit. The goal is to stop adding new memory-unsafe surface where attackers aim first.

                   internet
                      |
               reverse proxy / WAF
               /                 \
   /upload, /webhooks,          everything else
   /media, /auth/session              |
          |                     PHP application
   new Rust service             (CMS, admin, CRUD,
   (parses untrusted bytes,      business rules)
    owns each request,                |
    drops it at the end)  ---->  internal API / queue
New componentWhy memory safety matters thereKeep in PHP
File upload and media processingMultipart parsing, image and PDF decoders, attacker-sized inputsStoring the result, permissions, UI
Webhook and API ingestionHigh volume of unauthenticated requests, custom parsersBusiness handling of the event
Session or auth gatewayLong-lived worker state, exactly the CVE-2026-109619 patternUser management screens
Protocol bridges (WebSocket, gRPC, queues)Long-running processes with shared state across connectionsRequest-response pages

This is the strangler fig pattern applied to risk instead of performance: route one path to the new service, prove it in production, and widen the boundary only when it pays off. Our Node.js to Rust migration guide walks through that routing step by step, and the same mechanics apply in front of PHP.

Two cautions keep this honest. First, the PHP application behind the proxy still runs on a C interpreter, so patching discipline does not go away. Second, writing PHP extensions in Rust through FFI is possible, but the boundary with the Zend engine is unsafe by nature, so it does not give you the guarantees of a separate safe Rust service.

Frequently Asked Questions

PHP2Shell is the name for two critical vulnerabilities in the PHP interpreter, CVE-2026-109618 and CVE-2026-109619, patched on October 21, 2026. Both allow unauthenticated remote code execution on web servers through crafted multipart requests, before any application code runs. PHP rates them CVSS 4.0 9.2, and the reporters say releases back to PHP 5.4 are affected.

PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 fix both vulnerabilities. PHP 7.4, 8.0, and 8.1 are end of life and received source patches only, so users need a distribution or LTS vendor backport, or a source build. After upgrading, restart every PHP worker, since old processes keep running vulnerable code.

Almost certainly, and you have no official fixed release to install. Only PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12 ship the fix, while 7.4 to 8.1 received source patches only and older branches nothing. Use a distribution or LTS vendor backport, build from source, or upgrade to 8.2 or later. Shodan data suggests about 73.5% of internet-facing PHP services are in this situation.

Yes, because the bug is in PHP itself, not in any framework or plugin. Wiz demonstrated unauthenticated code execution against the latest WordPress, Drupal, and Joomla container images, and any framework running on a vulnerable PHP build is exposed. Updating the application does not help, only updating PHP does.

Safe Rust would have rejected both root causes at compile time. Storing a reference to request data in a process-wide global fails with error E0597, and reading a value before it is initialized fails with E0381. Rust's standard allocator also aborts on allocation failure instead of jumping out of half-finished code. unsafe code and C dependencies remain outside that guarantee.

Not exactly. CVE-2026-109619 needs one request that leaves stale state behind and a later request on the same worker that consumes it, and both advisories rate attack complexity as high. No login is required, and the whole chain runs during request parsing, which is why it was treated as critical.

Amazon Threat Intelligence logged an exploitation attempt in its MadPot honeypots on October 17, 2026, four days before public disclosure. On October 21, watchTowr reported attempts, Patchstack cited reports of active exploitation, and Cloudflare said it saw early attacks. CISA said it had not observed exploitation at the time of its alert, and the PHP Foundation reported no functional public exploit at release.

No, patching PHP is the fix, and a rewrite under incident pressure adds risk instead of removing it. The useful change is for new components: build the next service that parses untrusted input, such as uploads, webhooks, or session handling, in a memory-safe language like Rust. Route that one path to it behind your proxy and keep the PHP application for the business logic it already handles.

Bottom Line

PHP2Shell is not a story about PHP developers writing bad code. It is a story about a C runtime where "an allocation can jump out of any line" was an invariant no compiler could check, known in theory since 2004 and fixed only after AI-assisted researchers proved it exploitable. The fix took 390 files and moved PHP to the same model Rust ships by default: finish the operation, unwind cleanly, or abort.

Patch PHP now. Then, for the next service that parses untrusted input in your stack, ask whether you want this class of bug to be a code review problem or a compile error. For a security or platform lead, that is the defensible postmortem action item: not "rewrite in Rust," but "new internet-facing parsers ship in a memory-safe language."

Sources

Keep learning

Browse all articles

Ready to Land a $80k+ Rust Job in the US or Europe?