CVE-2026-109619 · GHSA-6wmf-4rv5-p3rp

CVE-2026-109619: RCE via stale global state across requests

The cross-request half of PHP2Shell: after a fatal error, PHP leaves global pointers aimed at memory it has already freed. The next request on the same worker uses them — leaking memory or giving an attacker code execution.

Last reviewed 2026-10-22

Severity

Critical · CVSS 4.0 9.2

Authentication

None required

Fixed in

8.5.12 · 8.4.27 · 8.3.36 · 8.2.35

Part of

PHP2Shell (with CVE-2026-109618)

Summary

A fatal error (including OOM) can skip the per-request reset, leaving global error, session, object or extension state pointing at memory freed at the end of the request. The next request on the same worker can use that freed memory before normal initialisation.

Impact: Use-after-free leading to worker memory disclosure or code execution. Demonstrated: Pre-auth RCE on WordPress and Drupal; application-agnostic remote memory disclosure.

Root cause

PHP keeps some state in request-scoped globals — error, session, object and extension state. Normally it is reset at the end of each request. A fatal error, including out-of-memory, can skip that reset, and warnings during shutdown can repopulate error state after it was cleared.

The memory those globals point to is freed when the request ends. If the same worker then runs code before normal request initialisation — INI diagnostics or session upload-progress handling — it uses freed memory. Workers that serve many requests in a row (PHP-FPM, mod_php children) are what makes this reachable.

Known attack paths

Fatal-error backtrace use-after-free

EG(last_fatal_error_backtrace) survives an OOM that destroys the request heap. An early diagnostic in the next request follows the dangling pointer. Strongest demonstrated case: PHP 8.5 with fatal_error_backtraces, RCE on mod_php.

Session upload-progress lifecycle

Session state left over from an aborted request is consumed by upload-progress handling in the next one. RCE validated on both mod_php and PHP-FPM.

OPcache shared-memory type confusion

An allocation failure between advancing CG(map_ptr_last) and publishing the shared frontier leaves slots workers disagree on, letting one worker reuse a slot for a different object type.

Other request-scoped state

Object and extension state families are described by the advisory as conditionally exploitable. The fix covers the pattern, not just these paths.

The PHP advisory and the Wiz write-up group some paths differently. The patch fixes the shared failure pattern, so the grouping does not change what you need to do.

Affected and fixed versions

BranchVulnerableFixed in
8.5< 8.5.128.5.12
8.4< 8.4.278.4.27
8.3< 8.3.368.3.36
8.2< 8.2.358.2.35
8.1 · 8.0 · 7.4allSource backport only
5.4 – 7.3allNo fix

Exposure by server setup

Setup…109619Notes
Apache + mod_phpRCE confirmedBoth CVEs validated to RCE with default limits. Highest-risk setup.
PHP-FPM (nginx, Apache, Caddy…)RCE confirmedSession-lifecycle RCE validated on FPM. OOM path confirmed through application-level code paths; no generic FPM exploit published.
FrankenPHP / LiteSpeed LSAPILikelyAdvisory rates them likely susceptible. Long-lived workers make cross-request state more relevant, not less.
CLI (cron, queue workers, artisan)Not coveredNot discussed by the advisories and not reachable by HTTP requests in normal use. Upgrade anyway — it is the same package.

CVSS 4.0 score: 9.2

Vector: 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

MetricMeaning hereEffect

AV:N

Attack Vector: Network

Reachable over the internet with ordinary HTTP requests.Raises score

AC:H

Attack Complexity: High

The exploit must be tuned to each target’s memory layout; it is not a one-request copy-paste.Lowers score

AT:P

Attack Requirements: Present

Success depends on deployment conditions (SAPI, limits, worker reuse) the attacker does not control.Lowers score

PR:N

Privileges Required: None

No account, no login.Raises score

UI:N

User Interaction: None

Nobody has to click anything.Raises score

VC:H / VI:H / VA:H

Vulnerable system impact: High

Full confidentiality, integrity and availability loss on the PHP server.Raises score

SC:N / SI:N / SA:N

Subsequent system impact: None

The score does not count pivoting to other systems — in practice stolen credentials often allow it.Lowers score

Fix and mitigations

The fix is a PHP update

Install a fixed release and restart every PHP worker. Patch guide →

If you cannot update immediately, php.net lists these partial measures:

  • session.upload_progress.enabled = 0 in php.ini, the FPM pool or server config, then restart every worker. Details →
  • PHP 8.5: fatal_error_backtraces = 0 and restart. This blocks the confirmed backtrace path only.
  • Remove invalid or deprecated settings applied per request that emit diagnostics before initialisation. Hiding them with error_reporting, display_errors or @ does not help.
  • Fix attacker-reachable fatal errors and execution timeouts (callbacks, upload and import processing, cleanup).
  • Consider pm.max_requests = 1 where performance allows.

References

Questions

Can CVE-2026-109619 leak data without code execution?+

Yes. A use-after-free across requests can disclose worker memory — passwords, API keys, session tokens of other users — without the attacker ever running code, which makes it harder to detect.

Does session.upload_progress.enabled = 0 fix CVE-2026-109619?+

Partly. It blocks the confirmed session paths but not other variants, and it only works if set in php.ini, the FPM pool or server config — not in .user.ini or via ini_set() — followed by a restart of every worker.

Is PHP-FPM affected by CVE-2026-109619?+

Yes. The session-lifecycle RCE was validated against both mod_php and PHP-FPM. FPM workers serve many requests in a row, which is exactly what cross-request stale state needs.

Does lowering pm.max_requests help?+

It shortens how long corrupted state survives, and pm.max_requests = 1 removes worker reuse entirely, at a large performance cost. It does not help against paths that need only one request.

Related