DEV Community

Cover image for PHP Patched the Two Critical RCE Flaws: Versions, Risk by SAPI, and Stopgaps
jamilxt
jamilxt

Posted on

PHP Patched the Two Critical RCE Flaws: Versions, Risk by SAPI, and Stopgaps

Earlier today, a preparation checklist on this blog covered PHP's upcoming emergency security release. At that point PHP had only announced a time slot and two CVSS scores. The release is now out, and the official security advisory answers the questions the first article could not.

The short version: update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Everything below explains who is most exposed, what the two bugs actually are, and what you can do if you cannot update today.

The fixed versions

PHP released four patched versions, one per supported branch:

  • 8.5.12 for PHP 8.5
  • 8.4.27 for PHP 8.4
  • 8.3.36 for PHP 8.3
  • 8.2.35 for PHP 8.2 (this branch reaches end of life on December 31, 2026)

The GitHub advisories list every version below these as vulnerable on each branch. Both flaws, CVE-2026-109618 and CVE-2026-109619, are rated CVSS v4.0 9.2. Each release announcement repeats the same line: "There are no complete mitigations, updating is the only safe choice." (PHP 8.5.12 announcement)

Linux distributions and LTS vendors received the patches in advance, according to PHP. If you install PHP from your distribution, check its security tracker for updated packages rather than building from source.

Who is at risk

The advisory says the risk covers "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed."

It is not equal across those setups. PHP rates the risk highest for Apache mod_php, where a demonstrated exploit lets an unauthenticated request trigger the attack before any application code runs. That last part matters: your application's own input validation never gets a chance to run.

The technical advisories add more detail per server API (SAPI, the layer that connects PHP to the web server):

  • mod_php: full remote code execution was validated over HTTP with default PHP input and memory limits, running as the web worker account. (GHSA-w8hw-w9fx-j79w)
  • PHP-FPM: for the memory bug, source analysis supports FPM as susceptible, but no complete FPM exploit has been demonstrated. For the stale-state bug, a validated FPM path exists, but it depends on a suitable application operation. (GHSA-6wmf-4rv5-p3rp)
  • FrankenPHP: source analysis indicates it is likely susceptible to the memory bug in the same application-independent way as mod_php.
  • LiteSpeed (LSAPI): behaves like FPM; application-specific exploits are considered likely but unvalidated.

"Not demonstrated on FPM" does not mean "safe on FPM". It means the exploit is harder to make reliable there. Patch either way.

The two vulnerabilities in plain terms

CVE-2026-109618: RCE via out-of-memory corruption

When PHP runs out of memory, it jumps out of the current operation immediately to abort the request. The problem is timing. In some places, request memory is already reachable before it is fully set up. If any allocation fails at that moment, PHP leaves that memory in an invalid state. Then the request shutdown code reads it as if it were valid.

An attacker needs a way to push a request into out-of-memory while staying inside PHP's apparent limits. The advisory lists several ways through multipart request parsing:

  • A multipart request without a usable declared content length can pass the initial post_max_size check and keep sending data.
  • A single multipart part can carry an unlimited number of headers, which take far more memory inside PHP than on the wire.
  • Empty file fields do not count towards max_file_uploads but still create file metadata arrays, and nested field names multiply that work.

On mod_php, all of this happens during automatic body parsing, before your code runs. FPM kills the worker process when request startup fails, which blocks the most direct version of the attack.

The fix replaces the immediate jump with a graceful "soft" out-of-memory that triggers at the next safe point, plus a hard exit if memory crosses a second, higher threshold. (GHSA-w8hw-w9fx-j79w)

CVE-2026-109619: RCE via stale global state

A PHP worker keeps some global state between requests: error, session, object and extension state. Some of it points into memory owned by a single request. That pointer must be reset before the request memory is freed.

If a fatal error interrupts an operation before its cleanup runs, the pointer survives. The memory is freed, but the worker still points at it. When the same worker handles the next request, some code runs before normal initialization and reads that stale pointer. This is a use-after-free, and with a crafted heap layout it becomes remote code execution.

The advisory names two confirmed cases:

  • PHP 8.5 fatal error backtraces: with fatal_error_backtraces enabled, the backtrace state is never reset during shutdown. This is the strongest demonstrated case and the basis of the confirmed mod_php exploit.
  • Session upload progress: this feature is on by default and runs before normal session initialization. It does not require your application to call session_start().

The advisory also notes that hiding diagnostics with error_reporting, @, display_errors or log_errors does not help, because PHP touches the stale state before it decides whether to report anything. (GHSA-6wmf-4rv5-p3rp)

If you run PHP 8.1 or older

The PHP project no longer ships releases for PHP 8.1 and older. The advisory gives three options:

  • Check whether your distribution or LTS vendor provides backported fixes.
  • Upgrade to a supported branch.
  • Use the minimal source backports the PHP security team published for PHP 7.4, PHP 8.0 and PHP 8.1. These are aimed at people who build from source or have no LTS vendor.

One more warning from the advisory: if you rely on the volunteer-maintained rpms.remirepo.net repository, it "will not have updates today". PHP urges switching to distribution packages or building from source so the update can go in immediately.

If you cannot update right now

PHP is explicit that the following measures "reduce exposure to specific attack paths but do not make an installation safe." Treat them as a bridge to the update, not a replacement.

For the out-of-memory bug (CVE-2026-109618):

  • Set memory_limit=-1 in a way applications cannot override, such as php_admin_value in the FPM pool or Apache configuration. Confirm no pool, directory or application setting puts a finite limit back.
  • Lower post_max_size: 8M if the application does not need file uploads, 32M if it does. Keep upload_max_filesize below post_max_size.
  • Enforce limits at the web server or proxy for decoded request size, input field count, multipart part count, header count and nesting depth. These must also cover requests without a usable Content-Length.
  • If the application does not use multipart form data at all, block those requests before they reach PHP.
  • Use a container or cgroup memory limit as an availability boundary, preferably one that kills only the affected worker. Do not use RLIMIT_AS or RLIMIT_DATA; the advisory says these trigger the vulnerable path.

memory_limit=-1 removes PHP's own memory cap, so a single request can now use as much memory as the system allows. The frontend and cgroup limits in the same list help cap that risk.

For the stale-state bug (CVE-2026-109619):

  • Set session.upload_progress.enabled=0 in php.ini, an FPM pool or server configuration that applies before request parsing. Do not rely on ini_set() or FPM .user.ini. Restart every worker.
  • On PHP 8.5, set fatal_error_backtraces=0 and restart every worker.
  • Remove invalid or deprecated INI settings that are applied per request and emit warnings before initialization.
  • Fix fatal errors and execution timeouts that an attacker can reach, including failures in callbacks, upload or import processing and cleanup.

The advisory asks for a restart of every worker after these changes. Do not skip it: the stale state this bug relies on lives inside workers that are already running.

After you update

Two checks help confirm the update actually took effect:

  • Check the binary that serves web traffic, not just the CLI. php -v on the command line can report a different PHP build than the one PHP-FPM or Apache loads.
  • Restart the web-facing PHP process. A package update on disk does not replace code already loaded into running FPM workers or Apache processes.

Then go back to the inventory from the first article: containers, staging systems, build images and third-party products that bundle their own PHP. A container image built yesterday still has the old PHP until it is rebuilt and redeployed.

Which part of your stack will take longest to get onto a patched PHP: hosts, container images, or vendor appliances?

Sources

Top comments (0)