Two critical PHP vulnerabilities, collectively dubbed php2shell, were disclosed on October 20, 2026. Left unpatched, these flaws allow unauthenticated remote attackers to gain full code execution on virtually any publicly accessible PHP instance. The issues have been officially resolved in PHP versions 8.2.35, 8.3.36, 8.4.27, 8.5.12
TL;DR
On October 21, 2026, the PHP core team issued an out-of-band security release addressing two critical vulnerabilities: CVE-2026-109618 and CVE-2026-109619.
PHP is a widely used server-side language – the engine under WordPress, Magento, Drupal, Laravel and endless custom apps. The scale is hard to overstate: on October 20, 2026, Shodan showed about 884,000 internet-facing hosts on PHP 8.1 or older, roughly 59% of the ~1.5 million that reported a version (another ~740,000 flagged PHP with no version). Those are advertised banners, not confirmed-vulnerable hosts – but a flaw in PHP core still lands on a huge slice of the internet at once.
The root cause is how PHP has historically reacted to running out of memory: it aborts the current work with a `longjmp` (`zend_bailout()`). When that abort happens inside engine code that is mid-operation – in particular the `session.upload_progress` handler, which decodes and rewrites a session during file-upload parsing, before the request’s script runs – it leaves PHP’s internal state inconsistent. The fix makes out-of-memory a recoverable condition instead of an abort, hardens the upload path to tolerate failed allocations, resets global state that startup code relies on, and prevents the upload-progress handler from invoking any class code while it decodes a session.
Fixed in 8.2.35, 8.3.36, 8.4.27, 8.5.12.
Why Are There Two CVEs?
Although both vulnerabilities stem from the same out-of-memory handling in PHP core, the vendor advisories split them into two independent defects:
- CVE-2026-109618 – RCE via OOM corruption: a use-after-free / memory-corruption flaw. PHP’s `longjmp` on out-of-memory leaves request memory in an invalid state before it is fully initialized, and request shutdown then reads or branches on it. The failing allocation need not be related to the corrupted memory.
- CVE-2026-109619 – RCE via stale global state: a use-after-free flaw. Worker-retained global state – session, error, object and extension state – points into a prior request’s freed memory and is accessed by early-running operations before the next request resets it.
Because each flaw is independently exploitable, two separate CVE identifiers were assigned.
| CVE ID | Flaw Type / CWE | CVSS v4.0 Base Score | Patched Versions |
| CVE-2026-109618 | Use-After-Free / OOM corruption (CWE-416, CWE-755, CWE-824) | 9.2 (Critical) | 8.2.35, 8.3.36, 8.4.27, 8.5.12 |
| CVE-2026-109619 | Use-After-Free / stale global state (CWE-416, CWE-755, CWE-824) | 9.2 (Critical) | 8.2.35, 8.3.36, 8.4.27, 8.5.12 |
Affected Products
- Product: PHP Core (ext/session, ext/standard)
- Affected Versions: All versions supporting session.upload_progress (PHP 5.4.0 through prior unpatched releases)
- Status: Fixed in 8.2.35, 8.3.36, 8.4.27, 8.5.12
Recommendations
- Immediate Upgrade: Apply the official vendor update to PHP version 8.2.35, 8.3.36, 8.4.27, 8.5.12.
- Access Control: Restrict or remove public access to unpatched PHP instances immediately.
- Caution with PoCs: Do not clone or execute unverified Proof-of-Concept (PoC) scripts or third-party patch scripts circulating on public repositories or social media.
Technical Deep Dive
The fix is a single commit, `01a40e89fb` (“Avoid state corruption via bailout”) by Arnaud Le Blanc and co-authors, closing GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619). The PHP 8.2.35 changelog lists both under Core.
Root cause: out-of-memory was handled by `longjmp`
Before the fix, any allocation that crossed `memory_limit` ended in `zend_mm_safe_error()` (`Zend/zend_alloc.c`):

Because `zend_bailout()` never returns, the allocation call that triggered it – an `emalloc`, a hashtable resize, a string grow – does not return to its caller either. Any cleanup, pointer refresh, or state reset that was supposed to run after that allocation is skipped. For ordinary script code that is fine, since the request is ending anyway. It is not fine when the skipped code is engine internals that hold live pointers and have not finished updating their own bookkeeping.
Why `session.upload_progress` is the exposed surface
`session.upload_progress` is enabled by default. When a multipart POST carries a `PHPSESSID` field and a `PHP_SESSION_UPLOAD_PROGRESS` field, the multipart parser (`main/rfc1867.c`) drives a session callback for each upload event – and this runs before the request’s script is compiled, whether or not the page uses sessions.
On each progress tick the handler re-opens the session, decodes it, inserts a progress array, re-encodes the whole session and writes it back, while tracking parts of that array through raw pointers in its per-request state. Every one of those steps allocates, and the amounts are set entirely by the request: the number of upload parts, the length of each part’s field name and file name, and the `PHP_SESSION_UPLOAD_PROGRESS` value all feed the size of what gets allocated and re-encoded.
The authors’ own regression test, `tests/basic/oom_rfc1867_upload_progress.phpt`, documents the condition plainly – it posts uploads shaped to exhaust a small `memory_limit` from inside the handler, and the commit title says what used to go wrong when that happened. The comments in that test read like a catalogue of the inputs that drive the allocation:

The point is not a clever payload; it is that reaching the memory limit here required no authentication, no application cooperation, and no unusual configuration – just a sufficiently large or numerous set of upload parts.
4. What the patch changes
The fix hardens out-of-memory handling in four places:
1. Memory manager – a new fallible-allocation model (`femalloc` / `safe_ferealloc`, guarded by `ZEND_HAS_OOM()`) returns `NULL` on exhaustion instead of calling `zend_bailout()` – the “Replace bailout on OOM with UnwindExit” change – with a new `memory_hard_limit_percent` giving the recovery path headroom.
2. Multipart parser (`main/rfc1867.c`) – converted to those fallible allocations, checking for exhaustion (`goto oom`) rather than trusting each allocation to succeed.
3. Upload-progress handler – stops forcing the session active regardless of result (it now checks `php_session_initialize()` and returns cleanly on failure), and brackets the operation with a new `in_rfc1867_update` flag that drops the unserializer into an “incomplete classes only” mode, so decoding a session in that pre-script window resolves no classes and runs no autoloaders, `__wakeup()`, `__unserialize()` or destructors.
4. Globals – startup-accessed globals are reset properly, so an early unwind cannot leave the engine reading half-initialized state.
Conclusions
Critical vulnerabilities in widespread core infrastructure represent an escalating operational risk—a challenge amplified by the speed at which AI models make vulnerability research and exploit generation faster and cheaper. When zero-days or unpatched flaws hit ubiquitous software like PHP, the gap between initial disclosure and effective remediation creates immediate, high-stakes turbulence for organizations worldwide.
The early leak of these PHP vulnerabilities triggered an all-too-familiar “information vacuum.” In the hours preceding official patches, security feeds on platforms like X and LinkedIn were flooded with half-tested mitigations, AI-hallucinated fixes, and dubious detection scripts. This environment creates two major risks:
- Social Engineering of Security Teams: Threat actors routinely exploit early panic by publishing fake PoC exploits, backdoor-laden detection scripts, or untested patches aimed directly at tricking security engineers into compromising their own networks.
- Operational Blind Spots: In the absence of confirmed technical vendor advisories, even well-intentioned community research remains speculative and can lead to ineffective mitigations or unnecessary service downtime.
When high-severity vulnerabilities leak without complete vendor details:
- Prioritize Isolation Over Quick Fixes: If an unpatched service is exposed and vendor guidance is pending, temporarily isolate or take sensitive instances offline rather than applying unverified community scripts.
- Rely Strictly on Official Sources: Trust only vendor-validated advisories, official core repository commits, and verified security advisories before applying fixes or changing system configurations.
- Audit Shadow Assets: Ensure your security operations team has full visibility into where vulnerable runtimes actually live within your infrastructure.
How OX Security Can Help
Use OX CNAPP platform to find every PHP runtime across container images and cloud workloads, including ones deployed outside standard processes.