Two critical PHP CVEs landed on 21 October 2026, and this is not another vulnerable WordPress plugin story. The problem is underneath the application, inside PHP’s request lifecycle.
That is the part I care about when looking at this from a vulnerability assessment and penetration testing (VAPT) perspective. Your application can have sensible authentication, clean input validation and a green static application security testing (SAST) report, and still sit on a vulnerable interpreter.
The vulnerabilities are CVE-2026-109618, PHP’s out-of-memory corruption issue, and CVE-2026-109619, its stale global state issue. Researchers refer to the wider research as PHP2Shell. Both upstream advisories assign CVSS v4.0 9.2, Critical. PHP has confirmed unauthenticated remote code execution, not just a theoretical crash.123
Research cut-off: 22 October 2026. This is my analysis of the upstream disclosures and published research, not a claim that I discovered these bugs or independently reproduced the exploit. I distinguish demonstrated attacks, source-analysis findings and my own testing recommendations below.
Quick answer: what should PHP operators do?
Upgrade PHP to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or a later security release on the same supported branch. Restart or replace every affected worker. For distribution packages with backported fixes, use the vendor’s package advisory rather than comparing only the upstream version string.1
| Vulnerability | Root problem | Confirmed impact | Important qualification |
|---|---|---|---|
| CVE-2026-109618 | OOM interrupts initialization, then cleanup consumes invalid state | Unauthenticated RCE as the web worker | Complete HTTP exploitation confirmed on mod_php; the upstream advisory does not claim a complete FPM exploit for this CVE |
| CVE-2026-109619 | Request-owned state survives after its memory is freed | Heap use-after-free, memory disclosure and RCE | Fatal-backtrace RCE confirmed on mod_php; session lifecycle RCE validated on mod_php and FPM |
PHP says all installations serving web requests should be treated as at risk, including PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. That does not mean an identical exploit works on every configuration. The strongest application-independent evidence is for mod_php.123
For Malaysian teams running a customer portal, an agency’s shared hosting estate or an internal PHP application, the practical question is the same: who owns the runtime update? Updating a CMS or running composer update does not, by itself, patch PHP.
How the vulnerabilities were found
According to Wiz Research’s PHP2Shell disclosure, its team found the first issue on 19 August 2026, confirmed unauthenticated RCE against Drupal on 20 August and WordPress on 21 August, and reported the vulnerabilities to PHP on 24 August. Wiz’s timeline records coordinated fix development from 21 September and public releases on 21 October.4
Wiz describes using AI-assisted research to investigate memory lifetimes, develop exploit chains and adapt them to different environments. It credits collaboration with PHP, Calif and OpenAI. The upstream advisories separately credit reporters and remediation developers. This was coordinated research, not evidence that every PHP site was already compromised.423
One detail worth keeping straight: the researcher write-up groups concrete bug variants under the two CVEs, while PHP’s advisories explain broader failure mechanisms. I use PHP’s upstream titles and classifications here. Do not reduce either CVE to a single upload endpoint or one CMS.
CVE-2026-109618: when an out-of-memory error stops being a safe failure
Normally, exhausting PHP’s memory budget should terminate a request. Annoying, yes. A route to code execution, no.
The vulnerable implementation used longjmp() to leave the current execution context on an out-of-memory error. That non-local exit could interrupt an operation while request memory was already reachable from internal structures but not fully initialized. The allocation that failed did not even have to belong to the structure that became unsafe.2
The simplified sequence is:
- PHP begins constructing internal request state.
- Some of that state becomes reachable before initialization finishes.
- An allocation fails.
- The OOM bailout skips the remaining initialization or cleanup.
- Request shutdown reads or branches on the incomplete state.
That last step turns a resource-exhaustion condition into memory corruption. A fatal error message is not the vulnerability by itself. The vulnerability is what PHP does with invalid state after the interruption.
Why multipart parsing matters
The advisory identifies several ways request processing could reach OOM despite apparently reasonable input limits:2
- A multipart request without a usable declared content length could pass the initial
post_max_sizecheck while continuing to provide decoded body data. - A multipart part could contain an unrestricted number of headers. PHP’s in-memory representation could be substantially larger than the bytes sent over the network.
- Empty file controls did not count toward
max_file_uploads, but still created file metadata arrays. Nested field names increased the resulting array work.
So, “my upload limit is smaller than my memory limit” is not a proof of safety. Wire size and parser memory consumption are different quantities.
Application-specific allocations can also cause OOM. Upload parsing is a demonstrated route, not the entire vulnerability.
Why mod_php is particularly exposed
Apache mod_php runs request shutdown even when startup has failed. That gives an attacker a path from automatic body parsing, through OOM, into cleanup of invalid state, before application code runs.2
An authentication check in your controller is too late for that path. So is a validation rule in your framework.
FPM behaves differently: it terminates a child when request startup fails. That blocks this particular startup-to-shutdown sequence. However, after successful startup, application execution can still reach unsafe OOM contexts. PHP’s advisory therefore considers FPM susceptible based on source analysis, while explicitly saying a complete FPM exploit for this OOM issue was not demonstrated.2
This distinction belongs in a serious VAPT report. “Not demonstrated on this SAPI” and “not vulnerable” are not interchangeable.
What the fix changes
The fix replaces the unsafe OOM longjmp() behavior with a delayed, graceful soft OOM at the next virtual-machine safe point. If consumption reaches a second, higher threshold, hard OOM terminates immediately using _exit().2
The important design change is preserving valid state at cleanup boundaries, rather than assuming every interrupted code path can survive an arbitrary bailout.
CVE-2026-109619: the next request inherits a dangling pointer
PHP workers commonly handle more than one request. Their process lifetime is longer than the lifetime of an individual request’s memory.
That is fine until persistent state points into memory that belonged to a finished request.
The upstream advisory describes this as a producer and consumer problem:3
- A producer publishes request-owned state into a worker-global location.
- A fatal interruption prevents the matching reset, or a late shutdown diagnostic repopulates state that was already cleared.
- PHP frees the request’s memory.
- The worker handles another request.
- An early consumer uses the stale value before normal initialization resets it.
That is a cross-request use-after-free. With a suitable heap layout, memory that PHP treats as a legitimate internal object may no longer contain that object.
The demonstrated producer and consumer requests must reach the same worker. A larger pool can reduce success per attempt, but worker scheduling is not a security boundary.3
Confirmed case: fatal-error backtraces in PHP 8.5
With fatal_error_backtraces enabled, PHP 8.5 could retain request-owned backtrace state after shutdown. A diagnostic during the next request, before normal initialization, could consume that stale state. PHP identifies this as the strongest demonstrated mod_php RCE case.3
Crucially, the diagnostic does not need to be displayed or logged. PHP accesses the state before deciding whether to show the warning.
Turning off display_errors, suppressing errors with @, or reducing error_reporting does not fix it.
Confirmed case: session upload progress
Session upload progress can invoke session handling before normal session initialization. An interruption after session or decoder state is published can leave globals referring to released memory. A later upload-progress operation can consume those globals too early.3
And yes, the application does not need to call session_start().
For mod_php, the producer and fatal interruption can occur during multipart startup. FPM kills the child for that exact startup failure, but researchers validated another FPM path: publish state during application execution after successful startup, encounter a fatal error, then reach the early session consumer on a later request.3
So “we use nginx and FPM” is not a valid blanket exception.
The concrete internals researchers investigated
Wiz’s technical write-up makes the lifetime problem easier to picture:4
- Session decoding: request-owned unserializer bookkeeping in
BG(unserialize)could survive the request boundary. - Memory disclosure: a stale
PS(id)could be treated as a valid session-ID string, allowing reclaimed memory to appear in aPHPSESSIDresponse cookie. - Fatal backtraces:
EG(last_fatal_error_backtrace)could retain a dangling request-owned value. - OPcache: interrupted persistence could desynchronize process-local and shared pointer-map frontiers, enabling cross-worker type confusion.
The OPcache case is a separate shared-state variant, not a reason to discard the same-worker requirement of the demonstrated request-global producer/consumer paths. Different variants have different prerequisites.
The state-reset patch addresses the broader lifecycle problem. Disabling one consumer does not prove every remaining state family safe.3
What is the PoC, and what does it actually prove?
There are three different claims people tend to mash together.
First, confirmed exploitation exists. PHP’s advisories document complete remote exploitation on the configurations described above. Wiz reports a combined exploit script called php2shell, demonstrated against WordPress, Drupal and Joomla deployments, plus separate memory-disclosure demonstrations.234
Second, public PoC availability has been reported. Canada’s Cyber Centre said on 21 October that it had observed open-source reporting indicating a PoC was available. That statement is not my independent validation of a particular repository or script.5
Third, a random GitHub project with the CVE in its name is not automatically a working RCE exploit. It might only generate memory pressure, crash a worker, check a version, or do something entirely unrelated.
I have not independently run or authenticated a public exploit implementation for this article. I am not going to paste an unverified payload and call it research.
Conceptual PoC flow
This is an explanatory model derived from the advisories, not executable exploit code:
CVE-2026-109618
controlled request processing
-> allocation failure during partially initialized state
-> unsafe bailout
-> shutdown consumes invalid state
-> memory corruption; RCE demonstrated on mod_php
CVE-2026-109619
request A publishes request-owned state
-> interruption or late diagnostic leaves a stale reference
-> request A's heap is released
request B reaches the same worker
-> early consumer acts before normal reset
-> use-after-free; disclosure or RCE on validated paths
For an authorized lab, I would use isolated vulnerable and patched builds with the same SAPI, extensions and configuration. No production data, no public listener, no unrestricted outbound access. Capture worker identity, sanitizer or debugger evidence and request outcomes.
The evidence ladder matters:
| Observation | What it supports | What it does not establish |
|---|---|---|
| Old runtime without vendor fix | Exposure to an affected component | A working exploit on that exact deployment |
| HTTP 500 or worker exit | A request failure | Memory corruption or RCE |
| A matching use-after-free trace | A reproduced memory-safety defect | Reliable remote code execution |
| Controlled execution in an authorized lab | Exploitability of that tested build | Universal exploit portability |
Do not run memory-exhaustion probes on production as a “harmless check”. Even an unsuccessful exploit attempt can become an availability incident.
A non-exploit inventory check
Start with authenticated runtime inventory instead of firing payloads at a live service:
php -v
php --ini
php -r 'printf("PHP=%s SAPI=%s\n", PHP_VERSION, PHP_SAPI);'
These commands inspect CLI PHP only. They do not prove which version Apache, FPM, a container or a managed hosting pool is actually serving.
Record the web runtime separately through trusted administration tooling, its package revision or image digest, loaded extensions and effective configuration. Do not leave a public phpinfo() page behind. For backports, attach the vendor advisory and package evidence to the finding.
Why this is critical, without the hype
Both advisories publish this CVSS v4.0 vector:23
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Network access, no privileges and no user interaction are a bad combination. High attack complexity and required attack conditions explain why a portable exploit is not trivial. They do not negate demonstrated RCE.
For me, the priority comes from four things:
- Some paths execute before application defenses. A clean controller cannot repair an interpreter’s startup lifecycle.
- Worker privileges still matter. RCE is as the web worker, not automatically root. But that account may access application secrets, databases, uploads and service credentials.
- Memory disclosure is independently serious. An attacker may not need a shell to obtain sensitive data.
- The affected component is shared infrastructure. Several unrelated applications may depend on one runtime or base image.
I would not turn “PHP is widely used” into “every PHP website can be exploited identically”. Configuration, SAPI, extensions, build details and allocator behavior all matter.
Canada’s 21 October alert said no active exploitation had been identified at that time. That is a dated observation, not assurance about what happens after disclosure.5
My VAPT assessment checklist
For an assessment, I would split the work into inventory, reachability and verification.
1. Establish the actual runtime
Inventory internet-facing hosts first, then internal services, containers, appliances and managed hosting. Record:
- PHP branch, exact package revision, build source and vendor patch status.
- SAPI: mod_php, FPM, CGI, FrankenPHP or LSAPI.
- Worker reuse and process-management settings.
- Session upload progress and PHP 8.5 fatal-backtrace settings.
- Reverse-proxy request limits and per-request INI overrides.
- Runtime owners and the services that require restarting.
A banner-only scanner result is a lead, not conclusive patch evidence.
2. Review reachable operations and early configuration
Map multipart handling, upload/import processing, callbacks, deserialization-related flows and attacker-reachable fatal errors.
For mod_php, examine per-directory Apache settings and .htaccess. For FPM, inspect frontend-supplied PHP_VALUE and PHP_ADMIN_VALUE parameters. The upstream advisory distinguishes these early consumers from FPM pool values applied at worker startup and .user.ini applied after normal initialization.3
Do not flatten those differences into “any deprecated INI setting causes RCE”.
3. Write a finding that matches the evidence
My finding would include the affected runtime, CVE, exposure, vendor evidence, SAPI-specific prerequisites, confidence level and remediation owner.
If all I have is authenticated version and package evidence, I call it an affected-runtime finding. I do not claim that I executed code. Conversely, I do not demand a production RCE demonstration before recommending an urgent patch.
What SAST can and cannot tell you
An application SAST scan generally examines your PHP source, not the C implementation of the interpreter beneath it. A green application report cannot certify the runtime against these CVEs.
This needs several complementary controls:
- Runtime and image inventory: detect vulnerable PHP packages, embedded runtimes and outdated base images.
- Application SAST: identify attacker-controlled flows that can reach fatal errors, expensive allocations, callbacks, imports and cleanup behavior.
- Configuration review: inspect early INI overrides, session features and effective worker settings.
- Native-code analysis: for engine or extension maintainers, audit ownership and state across allocation failure, non-local exits, shutdown and request reinitialization.
- Dynamic testing: use fault injection and memory-safety instrumentation in isolation to verify lifecycle behavior.
The code-review question I would put on the board is simple:
If execution stops right here, who still holds this pointer, and who is allowed to use it next?
For native code, check that partially initialized structures are not exposed to cleanup, request-owned pointers do not outlive their request, and later diagnostics cannot republish state after its reset. Review extensions too, not just the engine.
Those are review heuristics, not a magical regex that detects both CVEs.
Recommendations: patch first, mitigations second
Apply the fixed runtime
| Branch | Affected upstream releases in branch | First fixed release |
|---|---|---|
| PHP 8.2 | Earlier than 8.2.35 | 8.2.35 |
| PHP 8.3 | Earlier than 8.3.36 | 8.3.36 |
| PHP 8.4 | Earlier than 8.4.27 | 8.4.27 |
| PHP 8.5 | Earlier than 8.5.12 | 8.5.12 |
PHP 8.1 and older are unsupported upstream. PHP provided exceptional minimal source backports for 7.4, 8.0 and 8.1, but that does not restore normal support. Prefer migration to a supported branch, or obtain documented vendor-maintained fixes. PHP 8.6 testers should use RC4 or later containing the fixes, not deploy a release candidate as a production workaround.1
Replace running containers, restart affected services and verify that old workers are gone. Check every replica and pool, not just one successful request through a load balancer.
If patching is temporarily blocked
PHP explicitly says there is no complete configuration-only mitigation.123
For CVE-2026-109618, upstream recommends administratively enforcing memory_limit=-1 as a temporary way to remove the predictable PHP memory-limit trigger. This is counterintuitive, and it transfers risk toward host memory exhaustion. Underlying allocation failures can still reach the vulnerable path.
Do not copy that setting blindly. It needs an operationally reviewed availability boundary, constrained request processing and a short patch deadline. PHP warns against substituting RLIMIT_AS or RLIMIT_DATA, which can trigger the dangerous allocation failure. Container or cgroup limits are availability controls, not a complete security fix.2
For CVE-2026-109619:
- Disable
session.upload_progress.enabledadministratively and restart all workers. - On PHP 8.5, disable
fatal_error_backtracesadministratively and restart all workers. - Remove invalid or deprecated per-request settings that generate early diagnostics.
- Fix reachable fatal errors and execution timeouts.
- Consider aggressive worker recycling only as a temporary, performance-costly reduction of cross-request exposure.
These controls block particular paths, not every variant. A runtime ini_set() is too late for parsing that already happened. Do not rely on FPM .user.ini to disable upload progress before early parsing.13
At the frontend, constrain decoded body size, field count, multipart parts, per-part headers and nesting, including requests without a usable Content-Length. Block multipart where it is unnecessary. PHP suggests conservative post_max_size values of 8M without required uploads or 32M where uploads are necessary, with upload_max_filesize below it.1
If those constraints cannot be implemented safely and promptly, restrict access or temporarily isolate the service. A WAF rule or application login page is not a substitute for the runtime patch.
Hunt for compromise and verify recovery
Review correlated signals rather than treating any single PHP error as proof of attack:
- Unusual multipart traffic followed by OOM errors, repeated 500s or worker churn.
- Unexpectedly large or malformed
PHPSESSIDresponse cookies. - PHP or web-server processes spawning unexpected commands.
- New files, accounts, scheduled tasks or outbound connections.
- Fatal errors and diagnostics around request startup or late cleanup.
The request and cookie signals come from Wiz’s published defensive guidance; Canada’s alert also recommends investigating unexpected processes, files and outbound activity.45
Application logs may miss activity that occurs before application execution. Absence of logs is not proof of safety.
If compromise is suspected, preserve evidence and follow incident response procedures. Patching closes a vulnerability; it does not remove persistence or revoke stolen credentials.
Frequently asked questions
Does updating WordPress, Drupal or Joomla fix PHP2Shell?
Not unless that update also replaces the vulnerable PHP runtime. The demonstrated CMS deployments illustrate impact, but the bugs are in PHP itself.4
Is PHP-FPM safe because it kills workers after startup failure?
No. That behavior blocks specific startup-failure paths. PHP documents validated session lifecycle RCE on FPM under suitable application conditions for CVE-2026-109619.3
Is this PHP-CGI argument injection?
No. These disclosures concern memory safety, OOM handling and request-state lifetimes. Do not reuse unrelated PHP-CGI payloads and label them a PoC for these CVEs.
Is there a verified one-line exploit in this write-up?
No. Upstream confirms exploitation, and public PoC availability has been reported, but I have not independently validated a downloadable exploit. The conceptual flow and inventory commands above are deliberately labelled for what they establish.
Can a clean SAST report close the finding?
No. Close it with evidence that the actual serving runtime has the complete vendor fix, old workers have been replaced, and deployment verification has passed. SAST remains useful for application-level risk reduction.
The takeaway
The interesting failure here is not simply “PHP ran out of memory”. It is that memory ownership and cleanup assumptions stopped holding at exactly the point the runtime was supposed to fail safely.
For VAPT, prove the runtime and its exposure. For SAST, follow ownership and failure paths, not just user input into obvious dangerous functions. For operations, patch the interpreter and replace the workers.
Boring advice, perhaps. Much cheaper than discovering that your beautifully validated application was never reached.
Sources and researcher credits
The citations below link directly to the original advisories and research, with publication dates and access dates. Technical conclusions are attributed to their sources; testing and reporting recommendations are my analysis. The cover uses the PHP logo from Simple Icons in an original composed security illustration.
The PHP Group. “Security advisory: Critical remote code execution vulnerabilities, update now.” 21 October 2026. Official scope, fixed releases, legacy source backports and interim mitigations. PHP announcement and release archive. Accessed 22 October 2026. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
PHP / php-src Security Advisories. “RCE via OOM corruption.” GHSA-w8hw-w9fx-j79w, CVE-2026-109618. Published 21 October 2026. Primary technical source for OOM bailout behavior, multipart conditions, SAPI differences, fix design and CVSS v4.0 score. Full upstream advisory. Accessed 22 October 2026. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
PHP / php-src Security Advisories. “RCE via stale global state.” GHSA-6wmf-4rv5-p3rp, CVE-2026-109619. Published 21 October 2026. Primary technical source for producer/consumer lifetimes, backtraces, session upload progress, FPM prerequisites, mitigations and confirmed impact. Full upstream advisory. Accessed 22 October 2026. Both PHP advisories credit reporters lcfr-hax, sagitzadik-wiz, tomash-areion and amyb-oai, and remediation developers arnaud-lb, iluuu1994, bukka and ndossche. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Sagi Tzadik, Shir Tamari, Nir Ohfeld and Tomer Schwartz, Wiz Research. “PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet.” 21 October 2026. Researcher account, demonstrations, concrete internal-state variants, detection guidance and disclosure timeline. Full PHP2Shell research. Accessed 22 October 2026. The headline is the publisher’s wording, not a measured claim here that 70% of all internet systems are exploitable. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Canadian Centre for Cyber Security. “AL26-026: Critical vulnerabilities impacting PHP: CVE-2026-109618 and CVE-2026-109619.” 21 October 2026. Operational response advice and dated reporting on PoC availability and exploitation status. Full government alert. Accessed 22 October 2026. ↩︎ ↩︎ ↩︎
