Free AI Safety Workshop — in collaboration with explainx.aiSave your seat
AgentbeamBeta
Open-source package vulnerability · PHP Group

CVE-2026-109619: PHP RCE via stale global state (PHP2Shell)

criticalCVSS 4.0 9.2GHSA-6wmf-4rv5-p3rp

CVE-2026-109619 is the stale-state half of PHP2Shell: global state in a long-lived PHP worker can keep pointing at memory freed with the previous request, and the next request on that worker can use it before initialization resets it. An unauthenticated attacker gets a heap use-after-free that leaks worker memory (passwords, tokens, API keys) or runs code. PHP below 8.2.35, 8.3.36, 8.4.27 and 8.5.12 is affected; update to one of those releases.

Published Oct 21, 2026 · Analysis Oct 21, 2026

What an attacker can do

A persistent worker (Apache mod_php, PHP-FPM, FrankenPHP, LiteSpeed) serves many requests. PHP keeps error, session, object and extension state in process globals, and some of it points into the per-request heap. The advisory says that when a fatal error, an out-of-memory abort or an execution timeout cuts a request short, this state may not be reset before the request heap is freed. Diagnostics raised during shutdown can also refill error state after it was cleared.

The next request on the same worker runs some input, configuration, session and error handling before normal initialization, and that code can follow the dangling pointer. Confirmed consumers are INI diagnostics raised before request initialization and session upload progress handling. The strongest confirmed case is PHP 8.5 with fatal_error_backtraces enabled, where a saved backtrace survives into the next request.

The research teams showed two outcomes. First, memory disclosure: the upload progress path can copy freed worker memory into the PHPSESSID cookie of a response, even on an application that never calls session_start(). That memory can hold database passwords, session tokens, API keys and certificates from other users' requests. Second, code execution with the web worker's privileges, demonstrated before login on WordPress and Drupal.

The attacker needs network access and enough requests to land on the same worker that holds the stale state. More workers raise the request count; they do not stop the attack.

Who is affected

Same releases as its sibling advisory: PHP 8.2 below 8.2.35, 8.3 below 8.3.36, 8.4 below 8.4.27 and 8.5 below 8.5.12, plus every end-of-life branch, which gets no upstream release. The 8.5 branch carries the strongest variant when fatal_error_backtraces is on.

Check the version and the two settings this flaw depends on, using the SAPI that serves the site (a CLI check can show a different php.ini):

php -v
php -i | grep -E 'session.upload_progress.enabled|fatal_error_backtraces'

For PHP-FPM, read the pool files too, because php_admin_value or php_admin_flag lines override php.ini.

How to fix it

Update to PHP 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or your distribution's patched package, and restart all workers so no process keeps the old binary. On Debian and Ubuntu, needrestart lists processes still mapped to deleted PHP libraries.

Interim steps from the advisory, which reduce but do not close the flaw:

  • set session.upload_progress.enabled=0 in php.ini, the FPM pool or the server config, then restart workers; ini_set() and .user.ini files are not enough
  • on PHP 8.5, set fatal_error_backtraces=0
  • remove invalid or deprecated per-request settings that emit diagnostics before initialization; hiding them with display_errors, error_reporting or the @ operator does not help
  • fix attacker-reachable fatal errors and timeouts, and lower pm.max_requests so workers recycle sooner

Apply the memory steps from GHSA-w8hw-w9fx-j79w as well, because out-of-memory is one trigger for this bug.

How to tell if you were hit

Look at response headers, not only request logs. Unusually long or binary-looking PHPSESSID values in Set-Cookie headers, on sites or paths that do not use sessions, match the disclosure path. Other signs the responders list: multipart POSTs followed by fatal errors, repeated HTTP 500s, and worker crashes or restarts.

Treat memory disclosure as a credential leak. If the signs appear on a host that ran a vulnerable build, rotate the database passwords, session secrets and API keys it held, and invalidate active sessions.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

Request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it. Error, session, object and extension state kept by a persistent worker can point to memory owned by a finished request. Fatal errors (including out-of-memory and execution timeouts) and diagnostics raised during shutdown can leave this state in place, and input, configuration, session and error handling operations that run before the next request initializes can use the stale pointer. Confirmed consumers include INI diagnostics raised before request initialization and session upload progress handling; the strongest case is PHP 8.5 with fatal_error_backtraces enabled. Impact is unauthenticated heap use-after-free, memory disclosure and remote code execution with the web worker's privileges. Affected: PHP below 8.2.35, 8.3.x below 8.3.36, 8.4.x below 8.4.27 and 8.5.x below 8.5.12. Fixed in 8.2.35, 8.3.36, 8.4.27 and 8.5.12.

Credited to lcfr-hax, sagitzadik-wiz, tomash-areion, amyb-oai, arnaud-lb, iluuu1994, bukka, ndossche.

Questions

Can CVE-2026-109619 leak data without running code?

Yes. The researchers showed that freed worker memory can come back to the attacker inside a PHPSESSID response cookie, even when the application never starts a session. That memory may contain passwords, tokens and API keys from other requests.

Is turning off session.upload_progress.enabled enough?

No. It closes one confirmed consumer of the stale state, but the advisory lists others, such as INI diagnostics and fatal error backtraces. It must be set in php.ini or the server config with a worker restart, and it is only a stopgap until you update.

Why is PHP 8.5 called out for CVE-2026-109619?

PHP 8.5 added fatal_error_backtraces, which saves a backtrace when a fatal error occurs. The advisory says the strongest confirmed exploit uses that saved state, so 8.5 users should set fatal_error_backtraces=0 until they run 8.5.12.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

Coverage on the Beam blog

More PHP Group vulnerabilities

All PHP Group entries