Update the PHP runtime, not just your application. PHP's October 23 follow-up also recommends disabling session upload progress until the next regular bugfix release for an unusual additional RCE vector. Read PHP's current advisory.
What happened: the runtime became the attack surface
On October 21, 2026, the PHP project released emergency fixes for two critical vulnerabilities collectively called PHP2Shell: CVE-2026-109618 and CVE-2026-109619. Both concern the core PHP runtime's handling of memory when requests fail. They are not ordinary SQL-injection bugs, insecure Laravel controllers, or WordPress-plugin vulnerabilities. A well-written application can still sit on top of an affected runtime. PHP's announcement identifies all PHP installations serving web requests as at risk, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed.
The PHP team considers Apache mod_php the highest-risk configuration, with a demonstrated unauthenticated attack that can trigger before application code runs. That detail matters: middleware cannot protect a path that is reached before the middleware executes. It does not mean every deployment has an identical exploit path or that every malformed request succeeds.
| Issue | Broken invariant | Potential security outcome |
|---|---|---|
| CVE-2026-109618 | Cleanup must not treat incomplete state as fully initialized | Memory corruption and remote code execution |
| CVE-2026-109619 | A reference must not outlive request-owned memory | Use-after-free, memory disclosure, and remote code execution |
Google Cloud reports a critical CVSS 9.2 rating for both flaws. Severity describes the risk under the scoring model, not the percentage of servers already compromised. The exact trigger, memory layout, worker configuration, and available execution path affect exploitability. Google Cloud's response provides operational context.
Watch: both flaws explained in 72 seconds
First: understand a normal PHP request
Think of PHP as a runtime inside a web-serving process, not merely the code in your repository. Before your application can decide whether to authorize an action, the runtime must create enough request state for the application to run.
- The HTTP request arrives. A web server receives headers and, where applicable, a body containing forms or upload data.
- PHP starts the request. Its native runtime initializes request-specific structures and processes relevant input.
- Application code executes. Framework routing, login checks, validation, and business logic now have their turn.
- The response is returned. The normal result or error is sent to the client.
- Request resources are cleaned up. Memory and references belonging to this request must be released or reset. A persistent worker can then serve another request.
Three memory concepts to keep in mind
- Allocation: reserve a region of memory for an object or buffer.
- Initialization: write the fields that make that object valid. Allocated memory is not automatically a completely initialized object.
- Lifetime: the interval during which that memory belongs to the object. After it is freed, an old pointer does not remain valid.
Both bugs are violations of these contracts during abnormal execution. A request that runs to completion may never encounter the broken path. A request that fails midway can expose assumptions the successful path hides.
Bug 1 — CVE-2026-109618: initialization stops, but cleanup continues
1. PHP begins building internal request state
A request parser may need multiple allocations to construct its internal representation. Some fields become valid before others. For example, imagine a structure with a name, a value, and metadata. This is a teaching model, not the exact affected PHP structure.
RequestState name → initialized value → initialized metadata → not initialized yet
2. An allocation reaches an out-of-memory path
PHP has a configurable limit on PHP-managed memory for a request. A later allocation can reach that limit while initialization is incomplete. Native allocation failures can also matter; “out of memory” is not exclusively a machine having no RAM left.
PHP's failure handling can take a non-local bailout rather than returning normally through every intermediate function. A useful analogy is an emergency exit in the middle of assembling a machine: the assembly sequence stops, but the half-built machine is still on the table. An emergency jump does not automatically undo previous writes or establish all the invariants cleanup expects.
3. Cleanup interprets the partial structure incorrectly
The runtime must clean up after the failed request. If cleanup assumes initialization finished, it can interpret uninitialized or invalid data as trustworthy internal state. That may lead to an invalid access or incorrect memory operation. PHP summarizes the issue as cleanup treating invalid internal request data as valid. The PHP security advisory for CVE-2026-109618 is the authoritative technical reference.
4. Why this is more serious than a failed upload
A normal rejected upload should produce a controlled failure. Memory corruption is different: it can change how the runtime interprets memory rather than merely rejecting a request. Researchers demonstrated remote-code-execution capability through these flaws. Turning corruption into a reliable exploit requires suitable conditions and more than “send one large file.” Not every memory-limit error is an attack, and not every crash proves successful exploitation.
Bug 2 — CVE-2026-109619: memory is freed, but a reference survives
1. Request A creates data inside a persistent worker
In a persistent-worker model, one PHP process handles multiple requests over time. Request A's memory should be valid only for A's lifetime. Internal runtime state can temporarily refer to that memory. Here, “global state” means state inside the PHP engine's execution context, not necessarily an application variable declared with PHP's global keyword.
2. An abnormal exit frees the backing memory
A fatal error or another failing path causes cleanup. The request-owned memory is released, but an internal reference to it survives. Releasing the allocation and clearing every reference to it are separate responsibilities. Completing the first does not guarantee the second.
3. The allocator can reuse the region
The freed memory may later hold something completely different. An old pointer has no knowledge that ownership changed. If the runtime follows it, the bytes may still exist, but they no longer represent the object the runtime thinks they do. This is why a use-after-free can be dangerous even when the address is readable and the program does not immediately crash.
4. Request B reaches stale state before reset
PHP's advisory describes a later request handled by the same worker using stale state before normal initialization resets it. This is a lifetime boundary inside that worker. It is not a claim that any two unrelated requests anywhere in a server fleet automatically share the same vulnerable state. The PHP advisory for CVE-2026-109619 explains the affected internal paths.
5. The runtime reads or interprets the wrong data
A stale-state access can cause a crash, disclose memory, or contribute to corruption and code execution under appropriate exploit conditions. The critical mistake is not simply forgetting to erase sensitive bytes. The runtime is accessing data through a reference whose ownership and lifetime are no longer valid.
Related bugs, different broken transitions
| Question | Out-of-memory corruption | Stale global state |
|---|---|---|
| When does the invariant fail? | During interrupted initialization and cleanup | After cleanup, before later state reset |
| What is the mistaken assumption? | The object is fully initialized | The reference still points to live request data |
| What lifetime matters? | A partially constructed object | Request-owned data across a worker's requests |
| The safe mental model | Destruction must handle partial construction | No access after release; reset before reuse |
The defects share a root theme: the runtime must remain correct when control flow stops being ordinary. A patch must fix the engine's invariants, not merely add a validation rule to the application. The two CVEs are distinct; explaining them together does not establish that every attack needs to chain them.
How could attackers benefit?
At a high level, an attacker reaches an exposed PHP web-serving path, causes abnormal processing under suitable conditions, and exploits the resulting invalid memory behavior. The practical consequences depend on the deployment and what the compromised worker can access. We deliberately omit payloads and memory-manipulation recipes.
- Read secrets or sensitive data: memory disclosure may expose data present in the process; worker-level code execution may make readable configuration, credentials, or application data accessible.
- Modify what the worker can write: site content, upload directories, or database records could be affected if the worker's permissions and credentials allow those actions.
- Interrupt service: repeated crashes or resource pressure can disrupt requests. Availability impact can occur even without a successful code-execution chain.
- Attempt further compromise: accessible credentials and network permissions can create opportunities to reach other systems. Lateral movement is a possible consequence, not an automatic property of the CVE.
Remote code execution does not mean automatic root access. If the worker runs with constrained privileges, a read-only filesystem, narrow database permissions, and limited network access, the blast radius can be smaller. Conversely, a worker with broadly privileged credentials has a larger blast radius. A login screen and a managed cloud host are not, by themselves, proof that an affected runtime is protected.
Why application-only fixes miss the point
Updating a CMS or adding controller validation remains worthwhile, but it is not the same as updating PHP. Some vulnerable processing happens outside application code; the mod_php pre-application path is specifically demonstrated. Compare that boundary with the normal request journey in how an HTTP request works and with the application-level boundary in SQL injection explained.
What happened in the real world?
| Date (2026) | Reported event | What it establishes |
|---|---|---|
| October 17 | An exploitation attempt observed by Amazon Threat Intelligence, cited in Sophos's advisory | Attempted exploitation preceded public disclosure; not proof every target was compromised |
| October 20 | Advance notice of an urgent PHP security release | Operators were given a patch-preparation window |
| October 21 | PHP released fixes; ENISA says updates issued at 13:40 UTC | Supported fixed releases and coordinated response became available |
| October 22 | Vendor advisories and detection guidance updated | Operational recommendations continued to evolve |
| October 23 | PHP issued session-upload-progress follow-up | A configuration-specific additional vector needs the published precaution |
Sources: Amazon Threat Intelligence's primary MadPot report, Sophos's dated advisory, ENISA's joint statement, and PHP's news archive. AWS explicitly says it was unclear whether the observed attempts would have succeeded against real-world systems. Attempts justify urgent patching and investigation, but do not establish compromise of a particular server. This article does not claim a verified worldwide victim count.
What about the “70% of the Internet” headline?
Wiz's research disclosure emphasizes PHP's widespread use and the role of frontier AI in discovering and investigating the flaws. PHP's popularity helps explain the large potential attack surface. It should not be interpreted as “70% of all internet machines were compromised” or as a measurement of successful attacks. Exposure, exploitability, attempted exploitation, and confirmed compromise are different metrics.
What to update, and how to verify it
The October 21 fixed releases listed by the PHP project are:
| Supported branch | Affected upstream stable releases | October 21 fixed release |
|---|---|---|
| PHP 8.5 | 8.5.0–8.5.11 | 8.5.12 |
| PHP 8.4 | 8.4.0–8.4.26 | 8.4.27 |
| PHP 8.3 | 8.3.0–8.3.35 | 8.3.36 |
| PHP 8.2 | 8.2.0–8.2.34 | 8.2.35 |
These are the named upstream fixes for the two CVEs, not a recommendation to stop applying future updates. PHP 8.1 and older no longer receive normal upstream updates. Some distributions and LTS providers backport fixes; PHP also published minimal source backports for 7.4, 8.0, and 8.1. A backport requires explicit vendor evidence—the old version number alone neither proves safety nor proves the backported package is unpatched. Very old installations should move to a supported runtime or be handled through a verified vendor remediation plan.
- Inventory the actual web runtime. Include all FPM pools, Apache modules, containers, server images, hosted applications, and appliances bundling PHP. A CLI version can differ from the version handling web requests.
- Check the vendor advisory and install the fix. Use your distribution or image vendor's supported upgrade process. Rebuild container images and retire old image tags where appropriate.
- Restart or redeploy affected workers. Installed files do not guarantee a long-lived process has stopped running the old runtime. Include every replica and pool.
- Verify privately. Confirm the running web SAPI and package/build provenance through trusted operational tooling. Do not leave a public
phpinfo()endpoint exposing configuration. - Smoke-test application behavior. Check login, forms, upload flows, sessions, queues that use the same image, and error handling after the update.
- Review the period of exposure. Preserve logs and inspect crashes, unexpected files, suspicious processes, outbound connections, and credential use. If compromise is suspected, isolate and investigate rather than only restarting.
Important October 23 follow-up: session upload progress
The latest PHP informational advisory recommends setting session.upload_progress.enabled=0 until the next regular bugfix release to fully protect against an unlikely additional RCE vector. This precaution was already listed with the stale-state advisory on October 21; if you applied it, PHP says to keep it in place.
; php.ini / server-managed PHP configuration session.upload_progress.enabled = 0
PHP says the additional issue requires an unusual combination of configuration and application behavior involving a runtime change of the session serialization handler while upload progress remains enabled. Applications that never change the handler at runtime can keep that feature enabled, according to PHP's notice. PHP names CVE-2026-115560 for the forthcoming full advisory. This is a follow-up, not a renaming of the original two PHP2Shell CVEs. As of this article's check, the notice points to the next regular release rather than naming a newly released fix for this additional vector.
Operationally: keep the original runtime patches, check whether upload-progress functionality is actually needed, and follow PHP's latest guidance. Do not infer that the October 21 update alone resolves every newly discussed configuration-specific issue.
If you cannot patch immediately
PHP is explicit: only updating is a complete solution for the original vulnerabilities. Interim measures reduce particular attack paths and have trade-offs. ENISA recommends considering isolation from public networks when immediate remediation is not possible.
Out-of-memory path
The official interim guidance includes removing the controllable PHP memory-limit failure with a non-overridable memory_limit=-1, limiting decoded input at the web server or proxy, and using container/cgroup boundaries for availability. This is an emergency operational trade-off, not a safe generic tuning tip: underlying allocation failures can still reach the vulnerable path, and unbounded PHP memory without external resource controls can increase denial-of-service risk.
PHP suggests small post_max_size values—8M for applications not requiring uploads, 32M where uploads are necessary—with upload_max_filesize below that value. Proxy limits should cover decoded request size and structural counts, including requests without a usable Content-Length. The advisory warns against substituting RLIMIT_AS or RLIMIT_DATA, since they can cause the vulnerable allocation failure. Apply such changes with the hosting or operations team after checking application requirements.
Stale-state paths
- Disable session upload progress in configuration that takes effect before request parsing, then restart all workers. PHP says not to rely on application
ini_set()or FPM.user.inifor this mitigation. - For PHP 8.5, the advisory also lists disabling
fatal_error_backtracesto block the confirmed fatal-backtrace path. - Remove invalid/deprecated per-request configuration that emits diagnostics before initialization. Hiding messages with display settings or
@does not fix the underlying path. - Fix reachable fatal errors and execution timeouts, including failures in callbacks and cleanup. These measures do not cover every possible variant.
A WAF rule can be another layer, not a guarantee. Signatures may cover known requests while missing a different route to the runtime bug. Consult PHP's full mitigation text rather than copying one setting and assuming the server is safe.
What engineers should learn from PHP2Shell
Failure paths are part of the security model
Tests that only send valid requests miss the exact transitions these bugs exploit. Runtime and native-extension authors need to exercise allocation failures, partial initialization, callbacks that fail, cleanup errors, and repeated requests in the same process. The question is not just “did an error return?” but “what state survives after the error?”
Object construction and destruction need a shared contract
Initialize safe defaults before failure-prone work; track which resources have actually been acquired; ensure cleanup can handle partially built objects. In native code, a non-local jump can bypass ordinary cleanup assumptions. The correct pattern depends on the codebase, but the invariant is universal: destruction must not require work that might never have completed.
Request isolation is also a memory-lifetime property
A stateless HTTP API can still run inside a stateful process. Persistent workers, pools, and caches intentionally outlive individual requests. Request-owned pointers must not accidentally inherit that longer lifetime. Error-recovery testing should include A-fails-then-B-runs sequences, not only isolated requests in fresh processes.
Least privilege limits consequences
Run workers with constrained identities, keep runtime images reproducible, reduce writable directories, give database accounts narrow permissions, and restrict unnecessary egress. These controls do not repair memory corruption, but they can reduce what an attacker gains if a layer fails. For the deployment side, continue with how code flows from local to production.
Official sources and further reading
Prioritize the live PHP advisories over summaries. This article was checked on October 23, 2026; subsequent fixes and investigation findings can change operational guidance.
- PHP news archive: October 21 security releases and October 23 follow-up — authoritative release versions, affected serving configurations, and interim measures.
- PHP GHSA-w8hw-w9fx-j79w: CVE-2026-109618 — out-of-memory corruption advisory.
- PHP GHSA-6wmf-4rv5-p3rp: CVE-2026-109619 — stale global state advisory.
- PHP manual: session configuration — configuration reference, not a replacement for incident guidance.
- PHP supported versions — upstream lifecycle and support status.
- ENISA joint statement — inventory, patch prioritization, and exposure reduction.
- NCSC-NL PHP2Shell repository — third-party software and vendor tracking referenced by ENISA.
- Wiz research: PHP2Shell — discoverer analysis and AI-assisted research context.
- Google Cloud response and detection guidance — customer remediation context.
- Sophos security advisory — dated exploitation-attempt reporting and vendor response.
- Amazon Threat Intelligence: PHP2Shell attempts in MadPot — primary telemetry report and explicit limits on what attempted exploitation proves.