updates

Critical PHP security release: what it means for your site and how to update

Logged 2026-10-21 · 10 min read

Status: complete

The fixed PHP versions were released at 13:52 UTC on 21 October. Our hosts were updated by 14:38 UTC, 46 minutes later. Our package registry didn’t have the updates yet, so we built from source. Details below.

9.2

CVSS v4.0, both bugs

46

Minutes, release to every host patched

4

PHP branches with a fix

On 20 October the PHP security team posted an advance notice: a security release was coming on Wednesday, 21 October at 14:00 UTC, fixing two critical vulnerabilities, each rated CVSS v4.0 9.2. No details came with it. On 21 October the release landed, with the advisory and the fixed versions.

What was fixed

PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two vulnerabilities that can allow unauthenticated remote code execution. Someone who can send a request to a PHP site may be able to run their own code on the server.

CVE-2026-109618

RCE via OOM corruption

Out-of-memory handling can interrupt PHP while internal request data is only partly set up, and cleanup then treats that data as valid.

CVE-2026-109619

RCE via stale global state

Request state can outlive the memory behind it, and a later request on the same worker may use it before it is reset.

PHP’s own advisory says every PHP installation serving web requests is at risk: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The risk is highest for mod_php, where an exploit has been demonstrated that triggers the attack before any application code runs. That means your WordPress plugins, your Drupal modules and your own code don’t change the exposure.

No complete mitigation

PHP calls updating the only complete mitigation. The partial workarounds further down only narrow the exposure.

How the bugs work

The PHP team published a detailed advisory for each bug on GitHub. This is a summary of the technical parts, but we’ve linked both at the end if you’d like to dig further.

CVE-2026-109618 · GHSA-w8hw-w9fx-j79w

RCE via out-of-memory corruption

PHP aborts a request that runs out of memory with longjmp(), a jump that skips the code in between. In some places, request memory can already be reached before it is fully set up. If any allocation fails there, even one unrelated to that memory, the jump leaves it invalid, and request shutdown then reads and branches on the bad state.

An attacker needs a request that reaches out-of-memory while looking ordinary. The advisory describes three multipart tricks:

  • A multipart request without a usable declared content length can pass the initial post_max_size check and keep supplying decoded body data.
  • A multipart part can carry an unrestricted number of headers. PHP holds them in far more memory than they take on the wire, so a request within post_max_size can still exhaust memory_limit.
  • Empty file fields don’t count toward max_file_uploads but still create file metadata arrays, and nested field names multiply the work.

On Apache mod_php all of this happens during request startup, before any application code runs: body parsing exhausts memory, mod_php runs shutdown anyway, and shutdown processes the invalid state. The reporters validated full remote code execution over HTTP with default PHP input and memory limits, on ordinary prefork setups with several workers.

The fix removes longjmp() for out-of-memory. A request now gets a delayed, graceful stop at the next VM safe point, and an immediate hard exit with _exit() if memory use reaches a second, higher threshold.

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

RCE via stale global state

PHP workers are long-lived. Some global state (error, session, object and extension state) can end up pointing at memory that belongs to a request, and is normally reset when the request ends. If the request is cut short by a fatal error, including out-of-memory or “Maximum execution time exceeded”, that reset can be skipped. The pointer then outlives the freed memory, which is a use-after-free.

The attack takes two requests that land on the same worker. The first leaves the stale state behind. The second triggers an operation that runs before normal request initialisation and uses it. Two cases are confirmed:

  • PHP 8.5 with fatal_error_backtraces: the backtrace state is never reset, and a later diagnostic uses it. This is the basis of the confirmed remote code execution against mod_php.
  • Session upload progress, which is on by default and needs no session_start(): an out-of-memory during input processing can leave freed session state behind, and a later upload-progress operation uses it. Validated against mod_php and PHP-FPM.

The operations that run early include warnings raised while PHP applies INI settings (deprecated or invalid directives, paths rejected by open_basedir) and, on mod_php, the php_value and php_flag directives applied from .htaccess. On PHP-FPM the front end has to send PHP_VALUE or PHP_ADMIN_VALUE parameters that produce a diagnostic. Turning off display_errors or log_errors doesn’t prevent it.

More workers and worker recycling make each attempt less likely to land. They don’t remove the bug.

Severity

Both: CVSS v4.0 9.2

Network attack, no privileges, no user interaction, and high impact on confidentiality, integrity and availability. PHP also rates attack complexity as high and notes attack requirements: reliability depends on the PHP build, extensions, the web front end and memory layout, and the second bug needs both requests to reach the same worker. Critical, but not a one-line exploit. Even so, working remote code execution has been validated against mod_php, so the complexity rating is no comfort.

Which setups are affected

The advisories’ own assessment of the common ways to run PHP:

Apache mod_php

Highest risk

Runs shutdown even when startup fails and parses request bodies automatically at startup, so the first bug is reachable before any application code. Exploit validated end to end.

PHP-FPM

Susceptible

The child exits if startup fails, which blocks the simplest path and removes that worker from the pool. Source analysis still supports it as susceptible, and the second bug’s session-upload case was validated against FPM. No complete FPM exploit has been shown for the first bug.

FrankenPHP

Likely susceptible

Source analysis shows it calls shutdown unconditionally, like mod_php, so attacks that don’t depend on the application are likely.

LiteSpeed (LSAPI)

Likely, not validated

LSAPI models FPM. Application-specific exploits are likely but haven’t been validated.

What an attacker could do

PHP’s advisory doesn’t list specific outcomes. These are what remote code execution generally allows on any server.

Secrets

Read your passwords

Configuration files hold database and API passwords. The site can read them, so an attacker running code as the site can too.

Data

Change or delete your data

Pages, customer records, form submissions and orders can be copied, altered or wiped.

Persistence

Plant a backdoor

A backdoor keeps working after the hole is fixed, so patching alone doesn’t remove it.

Abuse

Use your server

Sending spam and phishing, redirecting your visitors, or hosting malware under your domain.

Spread

Reach neighbouring sites

Sites that share a server account can be reachable too, depending on how the host separates them.

Why plugins can’t help

No login, no warning

“Unauthenticated” means no password or account is needed. For mod_php, PHP describes an exploit that works before any application code runs, so security plugins and login limits in WordPress or Drupal never get a chance to stop it.

How to update

Step 01

Find your PHP version

Run the command below on the server, or in WordPress open Tools, Site Health, Info, Server. The command line can report a different version than the one serving your site, so check the web-facing one too.

Step 02

Move to the fixed release

Use the release for your branch (table below).

php -v

PHP 8.5

8.5.12

PHP 8.4

8.4.27

PHP 8.3

8.3.36

PHP 8.2

8.2.35

End of life 31 December 2026. Plan a move to a newer branch.

PHP 8.1 and older

No fix from PHP. Move to a supported branch.

Step 03

Use your package source, if it has the update

On Linux, PHP says distributions and LTS vendors received the patches in advance, so install the update from your vendor’s repository (apt, dnf or your vendor’s equivalent). Check that the fixed version is actually listed first. On release day it may not be, as we found out (see below). If it isn’t, you can build from the source downloads on php.net, which is what we did.

Step 04

Docker

Rebuild and redeploy on the patched version of the official PHP image, or on your base image once it picks up the fix. Check the tag exists before you plan around it. Pulling the image alone does nothing until the containers are recreated.

Step 05

Restart the workers

Installing the package is not enough. PHP-FPM, Apache or whatever serves PHP has to restart or reload so the running processes use the new code. The second vulnerability involves state left over in long-running workers, which is another reason to restart them.

Step 06

Verify

Check the version again, and confirm the version your site actually serves (Site Health, or phpinfo() on a page you remove afterwards).

If you host with us, we’ve already updated PHP for you.

If you can’t update yet

PHP calls updating the only complete mitigation. If a host can’t be updated today, the advisories list settings that narrow the exposure. None of them replaces the update, and each needs every PHP worker restarted to take effect.

CVE-2026-109618

Take away the memory ceiling

Set memory_limit to -1 and enforce it, so no pool, directory or application setting puts a finite limit back. That removes the predictable limit the exploit uses. It is not complete: if mmap() or another allocation fails, PHP can still take the vulnerable path. It also leaves nothing in PHP to stop a runaway script using all the memory. PHP lists a container or cgroup memory limit as an alternative with limits of its own.

CVE-2026-109618

Bound the input

Keep post_max_size conservative and enforce the decoded request size at the front end. Lower max_input_nesting_level, and bound input variables, multipart parts, headers per part and uploads. If the application doesn’t use multipart forms, block them at the front end.

CVE-2026-109619

Switch off the two known paths

Set session.upload_progress.enabled to 0, and on PHP 8.5 set fatal_error_backtraces to 0. That closes the demonstrated cases only, not other ways to leave stale state. PHP suggests keeping session upload progress off after patching if you don’t use it.

Avoid

What not to do

Don’t cap memory with RLIMIT_AS or RLIMIT_DATA. They make mmap() fail once the limit is hit, which triggers the vulnerable path. For the second bug PHP also suggests pm.max_requests=1 where performance allows, and treating execution timeouts like fatal errors.

An example for a PHP-FPM pool. Restart every worker afterwards.

php_admin_value[memory_limit] = -1
php_admin_value[session.upload_progress.enabled] = 0
php_admin_value[fatal_error_backtraces] = 0   ; PHP 8.5 only

How our update went

We read the advance notice on 20 October and planned for the release window. We used our existing control plane to manage the rollout, including staging and canary deploys.

13:52 UTC

Fixed versions published

The fixed versions were published on php.net, and we started.

Registry

Updates not there yet

When we went to pull the updates from our package registry, they weren’t there. Our plan assumed they would be.

Source build

Built from source

Rather than wait for the registry, we started building the fixed versions from source. That is slower than installing a package.

14:38 UTC

Every host patched

Most registries had the package. The hosts that couldn’t update from the registry received our patch builds. Every host was on a fixed version and restarted.

Unpatched servers stay exposed until the update lands, so we want that time shorter.

Customers did not need to do anything. If you run your own PHP somewhere else, such as another host, a staging server or a container you manage, use the steps above.

What to watch for

  • Exploit details for critical bugs tend to spread fast once a release is out. The window between “fix published” and “someone is scanning for unpatched servers” is short, so patch quickly and don’t assume your package source has the update yet.
  • Patching doesn’t tell you whether anything happened before the patch. If you run unpatched servers, look through your web and PHP logs for odd requests and unexpected worker crashes or restarts, and check for files you didn’t create.

Need a hand?

Move your sites to Paranoid Hosting

We apply security updates like this one for you, so patch day isn’t on your list. Tell us what you’re running and we’ll plan the move.

Not ready to move? If you want to check which PHP version your sites run, or need help getting off an end-of-life release, reach out and we’ll help.

Sources: the PHP security advisories GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619), and the release announcements for 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on php.net, 21 October 2026.

Outsource your paranoia.

Tell us about the sites you look after and the work you need help with.