PHP ships php2shell fixes: two core RCEs, update today
PHP shipped an out-of-band security release today at 14:00 UTC: 8.5.12, 8.4.27, 8.3.36 and 8.2.35 close two unauthenticated remote code execution flaws in the engine itself, rated CVSS v4.0 9.2, with complete exploitation demonstrated against Apache mod_php on default settings before any application code runs (php.net announcement). Every PHP installation serving web requests is in scope, and the disclosure was messy enough that attackers had a day's head start.
What are the two vulnerabilities?#
| CVE | Mechanism | Confirmed exposure |
|---|---|---|
| CVE-2026-109618 | OOM corruption: longjmp() out of partially initialized request state | full RCE on mod_php with default limits; FPM susceptible through application paths |
| CVE-2026-109619 | stale global state: a later request consumes freed memory | use-after-free confirmed; RCE shown against a real application on mod_php |
The first is a memory-limits problem. PHP uses longjmp() to abort a request that hits memory_limit, and in some parsing states the request memory is reachable before it is fully initialized. An allocation failure at that moment leaves invalid data behind, and the shutdown procedure then reads or branches on it. Attackers steer into the limit through multipart parsing quirks the advisory lists: a request without a usable content length can pass the post_max_size check, one part can carry enough headers to multiply its wire size inside PHP's memory, and empty file controls build metadata arrays without counting toward max_file_uploads. The SAPI ranking matters for triage. mod_php and FrankenPHP run request shutdown unconditionally, so the corruption happens before application code and full RCE was validated over HTTP with default limits. FPM kills the child when startup fails, which closes that door; no complete FPM exploit has been demonstrated, though source analysis still calls it susceptible. The patch removes longjmp() for OOM entirely, replacing it with a graceful soft limit at the next VM safe point and a hard _exit() above a second threshold.
Why the second bug ignores your error settings#
The second flaw is a use-after-free across requests on the same worker, and its anatomy explains the odd mitigation list. A producer operation, a fatal error, a timeout, an upload in flight, leaves request-owned state published in a global field after the request heap is freed. A consumer operation in the next request touches that state before normal initialization replaces it. The advisory confirms two consumers, and both are worth understanding.
The first consumer is an INI diagnostic. A warning raised while per-request configuration is applied, an invalid or deprecated setting, reaches the stale error state before the executor initializes. The part that surprises people: global state is consulted before PHP decides whether to display, log, or suppress the warning, so error_reporting, display_errors and the @ operator offer no protection at all. On mod_php this is extra reach, because .htaccess and Apache php_value directives apply before request startup. The second consumer is session.upload_progress, enabled by default since PHP 5.4, which can reach session state before ordinary session initialization. The strongest demonstrated case sits on PHP 8.5: with fatal_error_backtraces enabled, fatal error reporting publishes backtrace state that shutdown always forgets to reset, and that is the path behind the confirmed mod_php exploitation.
The disclosure went sideways#
The release was pre-announced for October 22, and then the schedule collapsed. On October 20 around 15:00 UTC the two CVE IDs leaked onto forums with a wrong CVSS of 10.0 attached, so the project moved its pre-announcement to 19:30 UTC and pulled the release a day forward. Around 20:00 UTC the security firm Calif accidentally published a video of its proof of concept working against real targets, and copies circulated before the deletion meant anything. On release morning at 07:41 UTC, six hours ahead of the coordinated publication, Chainguard shipped container images containing the fix and broke the embargo, handing attackers a binary diff while everyone else waited. The PHP security team monitored the resulting proof-of-concept activity and found no functional exploit in the wild at disclosure time. SANS raised its infocon to yellow over PHP's reach, and VirusTotal's measurement puts numbers on that reach: about 5.1 million IP addresses run PHP, four out of five hide their version, and among those that disclose one, roughly 758,000 sit on end-of-life branches from 5.4 to 8.1 while only about 272,000 run a supported branch. Laravel shows up on around 141,000 of these hosts.
A fix that took two years#
The origin story is the uncomfortable half. The OOM problem first surfaced during PHP's 2024 external audit by Quarkslab, funded by the Sovereign Tech Agency. An exploit was theorized then, but the security team, working by consensus among competing fix approaches that each broke something, stalled for two years. The PHP Foundation's account calls the delay "a structural failure, caused by both the security team's decision process and its lack of resources", which is unusually plain language for a project post. What broke the stalemate was outside pressure arriving in a wave: through OpenAI's Patch the Planet program, researcher Ryan A at security firm Calif reported an unconditional RCE with the same root cause on August 11, OpenAI reported the same root cause the same day, and Wiz added vectors on August 24. The team has also published 15 CVEs since July 1 while working through more than a hundred invalid reports, a triage load that arrived with AI-assisted vulnerability research; AI helps validate reports, and every call and patch still goes through a person.
What to do, in order#
Update first: 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distribution's package, since distros and LTS vendors received the patches in advance, then restart every worker. PHP 8.1 and older no longer receive project releases, but the security team published minimal source backports for 7.4, 8.0 and 8.1, which says something about how many installations sit there.
If the update cannot land today, the advisories rank the stopgaps, and each maps to a specific path. Enforce memory_limit=-1 through php_admin_value so no pool, directory, or application setting can put a finite limit back, cap post_max_size at 8M without uploads or 32M with them, set session.upload_progress.enabled=0 in php.ini, and on 8.5 set fatal_error_backtraces=0, then restart workers in every case. Fix attacker-reachable fatal errors and timeouts in the application, because they are producers now. One prohibition deserves its own line: do not use RLIMIT_AS or RLIMIT_DATA as the memory boundary, because they cause the allocation failure the first RCE feeds on. A cgroup limit is the acceptable boundary when each worker gets its own group.
The Laravel angle is timing rather than framework. The flaw is in the core, so composer versions and Laravel releases change nothing about exposure, and Laravel Forge published its own guidance for servers it manages. The Laravel systems documented on this site, like the banking backend architecture, run on exactly this runtime. Both runtimes I ship needed an emergency patch this week, Next.js on Monday and PHP today, and the PHP one is the more urgent by a wide margin.