PHP2Shell: WTF Happened? (CVE-2026-109618 and CVE-2026-109619)

| Surendra Pathak

Isometric 3D illustration of web hosting infrastructure shown as a wide network of glowing glass server nodes connected by teal data lines. A cluster of larger frosted-glass blocks representing websites and applications sits on top of the network. A bright fracture splits the shared infrastructure, and a few sharp amber and red shards push up through the crack, representing attackers exploiting a flaw in the foundation that everything depends on.

Two PHP engine memory bugs became unauthenticated RCE, and the coordinated disclosure came apart in public. Here is what happened, what is affected, and how to respond.

Overview

On October 21, 2026, the PHP project shipped emergency patches for two critical vulnerabilities in the PHP engine, CVE-2026-109618 and CVE-2026-109619, each rated 9.2 on CVSS v4.0 by the PHP security team. Each can enable unauthenticated remote code execution under demonstrated conditions, using crafted HTTP requests against a vulnerable PHP runtime. The public exploit tooling that combines the vulnerabilities is called PHP2Shell.

The flaws are in the PHP runtime, not in any one application, so a server is exposed even when its own code is clean. The research demonstrated remote code execution against current WordPress, Drupal, and Joomla. The vulnerable code dates to PHP 5.4.0, which means the exposure sat latent for more than a decade.

The rollout was as notable as the bug. The coordinated disclosure broke down in public, reported exploitation attempts predated the patch, and the episode turned into a dispute between the PHP Foundation and Chainguard. The net effect was a compressed response window. This post covers both halves: the technical one, written tightly, and the messy one.

Key Facts

  • What it is: two memory-safety vulnerabilities in the PHP engine, CVE-2026-109618 and CVE-2026-109619, grouped under the name PHP2Shell. Each is rated CVSS v4.0 9.2.

  • Impact: each can enable unauthenticated remote code execution. CVE-2026-109618 additionally enables cross-request disclosure of PHP worker memory.

  • Why it is broad: the defects are in the runtime, so applications with secure code are still exposed. The research demonstrated RCE against WordPress, Drupal, and Joomla.

  • Affected: PHP releases back to 5.4.0 are affected by at least one of the disclosed vulnerabilities, across PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, and LiteSpeed. Check the PHP advisory and your distributor for the precise status of each release.

  • Fixed: PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, with source backports for 7.4, 8.0, and 8.1. Branches 5.4 through 7.3 are end of life and unfixed.

  • Exploitation: reported exploitation attempts around the disclosure window; confirm current status against primary sources before acting.

  • What to do: patch the runtime, verify by CVE ID rather than version string, and investigate any system exposed before patching.

What PHP2Shell Is

PHP2Shell denotes two distinct memory-safety defects in the PHP interpreter, together with the public tooling that exploits them. It is not a vulnerability in WordPress, Drupal, Joomla, or any other application. The security boundary at issue is the PHP runtime itself, which parses and processes a request before application logic runs.

Both vulnerabilities are reachable remotely and without authentication, and PHP's advisory identifies PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed as affected ways of serving PHP. The advisory identifies mod_php as the highest-risk configuration, citing a demonstrated exploit that can trigger the flaw before application code executes.

The Vulnerable Request Path

The demonstrated attack paths use crafted HTTP requests, including multipart/form-data requests. PHP parses multipart bodies in native C code during early request handling, before normal application logic runs, which is why the vulnerable code is reachable without a legitimate upload form, a valid session, or any vulnerable application-level feature. Input validation in the application cannot defend code that executes before the application does.

Two engine features sit on this path and supply the conditions the bugs require. The first is session upload progress, the mechanism PHP uses to report how far an upload has advanced, which populates internal state during multipart parsing. The second is PHP's fatal-error handling, including the backtrace the engine builds when a request dies, for example when it crosses memory_limit. Both run in native code and both can be driven from outside the application.

CVE-2026-109618: Upload-Progress Use-After-Free and Memory Disclosure

CVE-2026-109618 is a use-after-free in PHP's upload-progress handling, reported by Wiz Research and the researcher known as Calif. The published research establishes that it enables both application-agnostic disclosure of PHP worker-process memory and unauthenticated remote code execution.

The defect is reached during multipart parsing. A fatal error at the wrong moment, most often an out-of-memory condition when a request crosses memory_limit, interrupts the engine in native code before cleanup completes, leaving a reference to memory that has already been freed. A subsequent operation then uses that freed memory, and an attacker who can induce the allocation failure at a chosen point, by sizing a multipart/form-data body to cross the limit, gains influence over its contents.

In the demonstrated memory-disclosure path, PHP's upload-progress handling can return contents of freed worker-process memory to the client inside the PHPSESSID response cookie. Depending on what remains in memory, the disclosed bytes may include data from other requests the same worker handled, such as credentials, session tokens, API keys, or other sensitive application data. The research demonstrates cross-request disclosure, not guaranteed access to every secret on the server. The leak does not require the application to call session_start(). This is the Heartbleed analogy: unintended memory disclosure through a request that was never supposed to read it.

CVE-2026-109619: Fatal-Error Backtrace Use-After-Free

CVE-2026-109619 groups related memory-safety problems in PHP's fatal-error backtrace handling and in OPcache, per the Wiz and Calif research. The published work demonstrates unauthenticated remote code execution through this vulnerability. The problems support different demonstrated exploit chains and should not be read as a single sequence of steps that an attacker must execute in order.

In the backtrace path, a fatal error, such as a request crossing memory_limit, can leave a reference to memory that has already been freed, which a later operation then uses; on PHP 8.5, the fatal_error_backtraces feature is specifically implicated. Separately, a type confusion in OPcache can lead the engine to misinterpret what a region of memory represents. Each has been shown to support code execution.

The two CVEs are distinct, and the research demonstrates unauthenticated RCE through each. PHP2Shell combines the vulnerabilities, but defenders should not assume both must be exploited together, or that blocking one demonstrated path makes a vulnerable runtime safe. The public write-ups stop short of step-by-step weaponization, and so does this analysis.

How the Two CVE Descriptions Relate

The two CVEs are described at two levels of detail in circulation, and the gap causes confusion. PHP's advisory titles them by broad category: CVE-2026-109618 is "RCE via OOM corruption" and CVE-2026-109619 is "RCE via stale global state." The Wiz and Calif research describes the underlying primitives: the upload-progress unserializer use-after-free with memory disclosure, and the fatal-error backtrace use-after-free with OPcache type confusion.

These are not competing accounts of different bugs; they describe the same vulnerabilities at different resolutions. Each CVE also groups more than one related primitive: CVE-2026-109618 covers the upload-progress use-after-free and its memory disclosure, and CVE-2026-109619 covers related problems in fatal-error backtrace handling and OPcache. Those primitives support different demonstrated exploit chains and are not a single sequence every attacker must run, which matters because blocking one path does not eliminate the CVE's risk. For remediation, use the PHP advisory to establish affected and fixed versions; for mechanics, consult the original research. A short CVE summary should not be read as a complete account of exploitation.

Affected and Fixed Versions

The vulnerable code path has existed since PHP 5.4.0, released in 2012, and is reachable in default configurations across every major way of serving PHP, with no non-standard settings required. Any web-facing installation on an affected version should be treated as potentially exposed until its patch status and configuration are verified.

PHP shipped fixes in 8.5.12, 8.4.27, 8.3.36, and 8.2.35. Source backports exist for 7.4, 8.0, and 8.1, which no longer receive official updates, so those require pulling the backport or upgrading. Branches 5.4 through 7.3 are end of life and will not be fixed. PHP 8.2 itself reaches end of life on December 31, 2026, so 8.2.35 is a short-lived position rather than a durable one.

One operational trap: distribution maintainers may backport the fix without adopting the upstream version number. Verify a package's status against the distributor's advisory and changelog, using both CVE IDs, rather than trusting the upstream version string alone. The same applies to container images and bundled software: a patched host does not establish that a separately packaged runtime is patched.

How the Coordinated Disclosure Fell Apart

The response window was short because the embargo came apart in public, and this is the part that earned the "WTF happened" reaction.

The research had been quiet for weeks. Calif reported an initial flaw to PHP in August, and Wiz Research reported the vulnerabilities to PHP on August 24, with OpenAI's security team credited in the coordinated effort. The fix was hard to build, and the coordinated disclosure was scheduled for October 22.

Then the embargo broke in stages. On October 20, the two CVE identifiers began circulating on forums and social media with incorrect severity and timing attached; at that point the identifiers were public, not the technical details. Shortly after, Calif accidentally published a video that contained a working proof-of-concept, which did expose technical material and contributed to faster independent exploit development. Mitigation advice the PHP Foundation had shared privately with critical-infrastructure organizations may also have leaked. With the embargo visibly breaking, PHP pulled its release forward one day, to October 21.

The third exposure became a public dispute, and it is worth stating both positions plainly. The PHP Foundation characterized Chainguard's publication of fixed container images, at 07:41 UTC on October 21, roughly six hours before the coordinated release, as breaking the embargo: "Any attacker could now study the fix in binary form while every other PHP user was still waiting for the release," adding that it "disregarded weeks of work by many parties toward a coordinated disclosure." Chainguard's CEO, Dan Lorenc, responded that the company leaked nothing, did not dispute the timeline, had independently discovered the same vulnerabilities using its own AI tooling, and had helped test the patches. The two claims are not mutually exclusive, and the PHP Foundation has not publicly answered the independent-discovery account.

The mechanism behind the Foundation's complaint is a recurring supply-chain problem worth understanding. When a patched binary ships before the advisory, anyone can compare it against the previous build, locate the changed code, and work backward toward the flaw. Patch-diffing can turn a fix into a map of the bug. Publishing a fixed binary ahead of an advisory does not, on its own, establish that the publisher was the source of the leak or that every attacker used that path, but it can lower the barrier for those who do.

Exploitation does not map neatly onto the patch timestamp. Security vendors reported exploitation attempts before the patches landed, with some reporting activity dated to October 17, and Cloudflare observed attack traffic and said some attackers adapted payloads to evade its WAF rules. On October 21, Cloudflare had rules enabled by 03:02 UTC, PHP shipped the fixes around 14:00 UTC, and watchTowr moved from reporting no observed exploitation to active attempts against both CVEs within minutes of the release. These are different observations, not necessarily one campaign, and early samples, demonstrated chains, exploitation attempts, and confirmed compromise are not the same thing. As of the initial reporting on October 21, no confirmed breach had been published and neither CVE was in CISA's Known Exploited Vulnerabilities catalog, though several organizations, including BambooHR and some Dutch government and education sites, took services offline as a precaution. Treat that status as time-bounded and recheck it before acting.

The practical lesson is that the patch-release timestamp is not when the threat began. Public research and exploit tooling sharply reduced the work an attacker would otherwise have needed to reproduce the demonstrated attacks. That did not make every deployment equally exploitable, but it shortened the path from disclosure to practical attempts, and it means any system exposed beforehand needs investigation, not a patch alone.

What an Unpatched Server Faces

For an unpatched, internet-facing server, two kinds of outcome follow, and the first needs no code execution. The memory-disclosure path can return freed worker memory in the response, exposing secrets from other requests the same worker handled, such as session tokens and API keys. It may leave no obvious application-level error, so responders should examine web-server and proxy telemetry alongside PHP logs and process activity rather than expecting a clear log entry.

The second outcome is full remote code execution, and from there the familiar chain follows: a web shell, credential theft, lateral movement, data exfiltration, or staging for ransomware. The old-version problem makes this durable in specific places. End-of-life branches will never receive this fix, and PHP that is bundled into an appliance, an embedded device, or a vendor product may have no patch at all without the vendor, which makes inventory and supplier coordination the real work rather than an afterthought.

Detection and Threat Hunting

Because the trigger is request parsing and the effect is memory corruption, the observable signals cluster around malformed uploads and unstable workers. For a server that may have been exposed before patching, investigate:

  • Unusual multipart/form-data requests, especially ones followed closely by memory-limit errors in the PHP logs.

  • Repeated HTTP 500 responses, which accompany the fatal errors the first bug depends on.

  • Unexpectedly large or malformed PHPSESSID values in server responses. This is where the memory-disclosure path surfaces, so it is an outbound response artifact, not an oversized cookie arriving from the client.

  • Unexplained PHP worker restarts, which can follow a failed or partial exploitation attempt.

None of these is conclusive alone. Judged against the application's normal baseline, a cluster of oversized multipart requests, memory-limit fatals, and worker churn against an unpatched server is a high-priority investigation lead. A successful memory-corruption exploit may not produce the same symptoms as an ordinary error, so corroborate across web-server, proxy, and PHP logs plus process telemetry rather than relying on one pattern.

Responding to PHP2Shell: Priority Order

For an exposed environment, act in this order:

  1. Identify and patch. Find every internet-facing PHP runtime, including those bundled in containers, appliances, and third-party products, and apply the vendor-fixed release. Updating the application framework does not fix the runtime.

  2. Reduce exposure while patching. If an immediate update is impossible, restrict public access or isolate the affected service. Treat WAF rules and PHP configuration changes as temporary risk reduction, not complete mitigations.

  3. Investigate possible compromise. Review web, proxy, and PHP logs, endpoint telemetry, unexpected processes, modified files, and suspicious outbound connections. Patching does not establish whether an attacker accessed the system beforehand.

  4. Verify the result. Restart the relevant services and workers, confirm the running runtime is a fixed build, and check vendor advisories where fixes are backported.

Interim Mitigations and Their Limits

The PHP project is explicit that there are no complete mitigations and that updating is the only safe choice. The measures below reduce exposure to specific paths; they do not make an unpatched runtime safe. Restart the affected PHP workers and relevant serving processes after applying configuration changes, as your deployment requires. Verify the current recommendations in the PHP Foundation's security release post rather than relying on a summary.

For CVE-2026-109618, PHP recommends setting memory_limit to -1 through configuration that applications cannot override, such as the FPM pool or Apache config, which removes the controllable memory-limit trigger. This is not a casual change: it removes PHP's per-request memory cap, so a single request can consume substantial system memory, and external request limits alone do not guarantee the vulnerable allocation path cannot be reached. Blunt the denial-of-service risk by keeping post_max_size low and upload_max_filesize below it, enforcing request-size limits at the proxy, and placing a container or cgroup memory boundary around the worker that can terminate it. Do not substitute RLIMIT_AS or RLIMIT_DATA, which recreate the allocation failures that reach the bug.

For CVE-2026-109619, PHP recommends disabling session upload progress to block confirmed session-related exploit paths. Set session.upload_progress.enabled to 0 at the pool or server level so it applies before request parsing, then restart workers; setting it through ini_set() or a .user.ini is too late to help. On PHP 8.5, also set fatal_error_backtraces to 0 to block the confirmed fatal-backtrace path. These settings address specific paths, not every possible variant, and do not make an unpatched installation safe.

A post-patch note, per the reporting on the fix: it introduces a hard memory limit that can terminate a process without running PHP shutdown handlers. If your logic depends on shutdown functions or graceful memory-exhaustion handling, confirm the patched behavior against the PHP release notes before assuming it matches the old one.

What This Means for Inventory and Response

PHP2Shell is, operationally, an inventory problem before it is a patching problem. On October 21, the first question for most teams was not how the bug works. It was: where do we run PHP, which versions, and which products, services, and customers are exposed.

A current, sufficiently complete software bill of materials helps answer that by identifying the products, builds, and images that contain the affected PHP runtime. It is where PHP tends to hide: bundled inside appliances and embedded web interfaces, baked into container base images, pulled in as a dependency of a larger product, places a manifest-level scan tends to overlook. An SBOM is only as good as its coverage. It points you at the assets worth checking; it does not by itself confirm the exact version running in production or prove a given host is exploitable. When SBOMs are current and capture the relevant runtime, they turn a days-long census into a much shorter list of assets to verify.

Interlynk is built for this pattern. lynkctl generates build-aware SBOMs that capture the runtimes and components embedded in firmware, appliances, and images, well beyond the top-level dependencies. The Interlynk platform ingests those SBOMs, risk-assesses components against vulnerability data so an affected PHP version surfaces automatically, manages VEX assertions for the products you have assessed, and notifies stakeholders when a new disclosure changes the exposure of something already shipped. When the next runtime-level CVE lands, the inventory is already in place.

The Bottom Line

PHP2Shell shows how a memory-safety defect in a widely deployed runtime can undermine the security of applications that did nothing wrong. Two memory-safety flaws present in PHP for more than a decade were developed into unauthenticated remote code execution, and a coordinated disclosure that fell apart left defenders with almost no grace period.

Patch the runtime to 8.5.12, 8.4.27, 8.3.36, or 8.2.35, restart your workers, verify by CVE ID, and investigate anything exposed before the patch. Then make the inventory question answerable in advance, because when a vulnerability lives in a runtime shared across thousands of products, knowing where that runtime is deployed is part of incident response, not cleanup afterward.

Frequently Asked Questions

What is PHP2Shell? PHP2Shell is the name for two critical memory-safety vulnerabilities in the PHP engine, CVE-2026-109618 and CVE-2026-109619, each rated CVSS v4.0 9.2, along with the public tooling that exploits them. Both allow unauthenticated remote code execution.

Do both CVEs have to be chained to achieve RCE? No. The published research demonstrates unauthenticated remote code execution through each vulnerability independently, with different exploit chains. CVE-2026-109618 additionally discloses PHP worker memory. PHP2Shell combines them, but they are not required to be used together.

Which PHP versions are affected and fixed? The vulnerable code reaches back to PHP 5.4.0. Fixes are in 8.5.12, 8.4.27, 8.3.36, and 8.2.35, with source backports for 7.4, 8.0, and 8.1. Branches 5.4 through 7.3 are end of life and unfixed.

Is PHP2Shell being exploited in the wild? Security vendors reported exploitation attempts around the disclosure, including activity before the patch and attempts against both CVEs after release per watchTowr. Confirmed compromise is a separate question; check current advisories and CISA's KEV catalog before acting.

Does disabling file uploads or blocking multipart requests fix it? No. Those steps reduce exposure to the demonstrated trigger but do not make an unpatched runtime safe. Updating is the only complete fix.

How do I find every affected PHP runtime? Inventory every place PHP runs, including bundled and embedded instances, using a current SBOM where available, then verify the version in production and patch. PHP frequently ships inside appliances and container images that a top-level dependency list will not show.

Sources

About Interlynk. Interlynk helps teams generate, manage, and operationalize SBOMs across the software supply chain. lynkctl produces build-aware SBOMs for embedded and firmware software, capturing bundled runtimes and vendored, static, and SDK components with precise identifiers. The Interlynk platform ingests SBOMs and CBOMs, evaluates them for completeness and quality, risk-assesses components against vulnerability data, manages VEX assertions, and notifies teams when exposure changes.

Book a demo · Start free

This article is informational and does not constitute security or legal advice. PHP2Shell is a rapidly evolving incident; confirm current advisories, affected versions, fixed releases, and exploitation status against the primary sources before acting.