On 21 October 2026 the PHP project released versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to fix two critical vulnerabilities that allow unauthenticated remote code execution on the server. They are CVE-2026-109618 (GHSA-w8hw-w9fx-j79w) and CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp), both scored 9.2 on CVSS 4.0. This guide covers what each one does, which versions they affect, how to check yours on Docker, Debian and Ubuntu, how to update, and which settings reduce exposure if you cannot update today. Package and image data was checked at 14:30 UTC the same day. There is also a Spanish version of this guide.

Key takeaways

  • They affect 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. PHP 8.1 and older have no official fixed release.
  • Apache with mod_php is the riskiest setup: the advisory describes an attack where one unauthenticated request triggers it before your application runs. PHP-FPM, php-cgi, FrankenPHP and LiteSpeed are also in scope.
  • The only complete fix is to update and restart every PHP worker. Configuration mitigations block specific paths, not the flaw.
  • Debian 13 already ships php8.4 8.4.27 in trixie-security and Ubuntu published USN-8948-1. The 8.4.27 and 8.3.36 tags of the official php image on Docker Hub were not published yet.
  • On Ubuntu, php -v still reports 8.3.6 after the patched package is installed: compare the package version, not the PHP version.

What CVE-2026-109618 and CVE-2026-109619 fix

Both flaws leave PHP using memory that is no longer valid, and an attacker can turn that into code execution with the web worker’s privileges. The php.net security advisory[1] summarises them, and the GitHub advisories in the php/php-src repository give the technical detail.

CVE-2026-109618, "RCE via OOM corruption". When a request runs out of memory (OOM), PHP leaves the current context with longjmp(). If that happens while part of the request memory is half initialised, request shutdown reads it as if it were valid. According to advisory GHSA-w8hw-w9fx-j79w[2], a multipart upload can reach OOM without exceeding post_max_size: parts with unlimited headers, bodies without a usable declared length, or empty file fields that do not count towards max_file_uploads.

CVE-2026-109619, "RCE via stale global state". PHP keeps global state (errors, session, objects, extensions) that points into the memory of one request. If a fatal error interrupts an operation before that state is reset, the next request served by the same worker can use it before it initialises. Advisory GHSA-6wmf-4rv5-p3rp[3] confirms two paths: session upload progress (session.upload_progress), enabled by default, and on PHP 8.5 the fatal error backtraces (fatal_error_backtraces).

One patch fixes both. It removes the OOM longjmp(), replaces it with an orderly stop at the next safe point of the virtual machine, and resets that global state between requests. The ChangeLog of each release records it in one line: "Avoid state corruption via bailout".

Who is most at risk, by how you serve PHP

How you serve PHP changes how exposed you are, though none of the setups is out of scope. The php.net advisory puts it in one sentence: "We consider the risk highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs."

How PHP is served What the advisories say
Apache with mod_php Full unauthenticated RCE demonstrated, before the application runs
PHP-FPM (nginx, Caddy, Apache with mod_proxy_fcgi) Session RCE validated; for OOM, susceptible by source analysis, no complete exploit
FrankenPHP Likely susceptible to the OOM path by source analysis
LiteSpeed (LSAPI) Behaves like FPM; application-specific exploits likely but unvalidated
php-cgi Listed by php.net, no detail in the advisories

On FPM there is one detail worth checking today. If your nginx or Apache passes per-request settings with PHP_VALUE or PHP_ADMIN_VALUE and one of them raises a warning (an obsolete directive, for example), that request becomes a consumer of the stale state. Find them with grep -rn PHP_VALUE /etc/nginx /etc/apache2.

Alleged exploits are already out. Since the early hours of 21 October, before the official release, GitHub has been collecting repositories with proofs of concept named "php2shell". Searching for the CVE returned 98 of them at 14:25 UTC.

I have not run or reviewed any. Treat that code as untrusted, because fake exploits carrying malware are common after a CVE like this.

Which versions are affected and which fix them

You are affected if you run PHP 8.2, 8.3, 8.4 or 8.5 below the fixed release of your branch. The CVE record[4], published by the PHP project itself as CNA, gives these ranges for both flaws:

Branch Affected Fixed in
8.5 Below 8.5.12 8.5.12
8.4 Below 8.4.27 8.4.27
8.3 Below 8.3.36 8.3.36
8.2 Below 8.2.35 8.2.35 (the branch ends on 31 December 2026)
8.1, 8.0 and 7.4 No official release Source patches published by the security team

The record only lists 8.2 to 8.5, but php.net says that, given the severity, the team published minimal source backports for 7.4, 8.0 and 8.1. If you run one of those, you depend on your distribution or an extended-support vendor. NVD received both CVEs at 14:17 UTC and had not analysed them yet, so its page adds nothing to the CVE record.

How to check your PHP version

Check the version of the binary that serves requests, not only the command-line php. On Debian and Ubuntu they are separate packages (php8.4-cli, php8.4-fpm, libapache2-mod-php8.4), and the FPM or Apache one can lag behind. This script compares the version with the first fixed release of its branch:

<?php
// Branch => first fixed release (CVE-2026-109618/109619)
$fixed = ['8.2' => '8.2.35', '8.3' => '8.3.36',
          '8.4' => '8.4.27', '8.5' => '8.5.12'];
$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
if (!isset($fixed[$branch])) {
    echo PHP_VERSION, ": branch with no official fixed release\n";
} elseif (version_compare(PHP_VERSION, $fixed[$branch], '<')) {
    echo PHP_VERSION, ": VULNERABLE, update to ", $fixed[$branch], "\n";
} else {
    echo PHP_VERSION, ": fixed\n";
}

Save it as check.php and run it with the same binary your site uses. Against three official images I had pulled, the result was:

$ for i in php:8.3-cli php:8.4-fpm-alpine php:8.5-cli; do
>   docker run --rm -v "$PWD/check.php:/check.php:ro" "$i" php /check.php
> done
8.3.31: VULNERABLE, update to 8.3.36
8.4.26: VULNERABLE, update to 8.4.27
8.5.10: VULNERABLE, update to 8.5.12

In a running container, docker compose exec my_php_service php -v gives the version without mounting anything. For FPM installed from packages, php-fpm8.4 -v (or php-fpm8.3 -v) shows the FPM binary’s version. If you prefer a phpinfo() page, delete it as soon as you have read it: it exposes the whole configuration.

The script gets Ubuntu packages wrong. Ubuntu backports the patch without changing the PHP version, so a patched Ubuntu 24.04 still reports 8.3.6. There, the package version is what counts, as shown in the update section.

How to update PHP on Docker, Debian, Ubuntu and Alpine

Updating means replacing the package or image and restarting every PHP worker. A reload that leaves old workers alive is not enough, because the flaw lives in the loaded binary.

Official Docker image

The official image’s repository, docker-library/php[5], merged the update to the four releases at 14:19 UTC. At 14:30 UTC, Docker Hub was not serving the 8.4.27 or 8.3.36 tags yet, although the pull request to publish them in the official images repository was already open. Once they appear, bump the tag in your compose.yaml and recreate the service:

sed -i 's/php:8.4.26-fpm/php:8.4.27-fpm/' compose.yaml
docker compose pull
docker compose up -d
docker compose exec my_php_service php -v

If your image is built FROM php:8.4-fpm, rebuild it with docker compose build --pull so it fetches the new base. A moving tag such as 8.4-fpm does not update itself once the image is pulled. To automate the pull you can use Watchtower, and Trivy or Grype find which images in your registry still carry a vulnerable PHP.

Debian and Ubuntu

On Debian 13 (trixie), php8.4 8.4.27-1~deb13u1 is already in trixie-security, although the DSA had not appeared on the Debian website yet. On Debian 12 (bookworm), php8.2 was still at 8.2.34-1~deb12u1, unpatched. Upgrade only the PHP packages and restart the service:

sudo apt update
apt list --upgradable 2>/dev/null | grep '^php'
sudo apt install --only-upgrade 'php8.4*'
sudo systemctl restart php8.4-fpm   # or apache2 for mod_php
dpkg -l 'php8.4-fpm' | tail -1

On Ubuntu, USN-8948-1[6] fixes three branches. Ubuntu 24.04 gets php8.3 8.3.6-0ubuntu0.24.04.13, 22.04 gets php8.1 8.1.2-1ubuntu2.28 and 26.04 gets php8.5 8.5.4-0ubuntu1.5. The same commands work with the branch number changed. Watch the architecture: in an arm64 Ubuntu 24.04 container, the ports.ubuntu.com mirror was still offering the .12 build at 14:30 UTC.

Ondřej Surý’s repository and Alpine

If you install PHP from packages.sury.org[7], the bookworm amd64 index already had 8.4.27 and 8.3.36, dated 21 October. On arm64 it was still 8.4.26, so check with apt-cache policy php8.4-fpm before calling the server fixed. On Alpine, php84 was at 8.4.26-r0 and php83 at 8.3.35-r0, even on edge. If you depend on rpms.remirepo.net, php.net warns that the repository will not have updates today and recommends switching to distribution packages or building from source.

How to mitigate if you cannot update yet

Mitigations reduce exposure to specific paths, but none replaces the update, and both advisories say so. The 8.4.27 announcement is explicit: "There are no complete mitigations, updating is the only safe choice." These are the measures they propose:

Measure What it blocks Limit
memory_limit=-1 set administratively The controllable OOM of CVE-2026-109618 A real mmap() failure still reaches the vulnerable path
Low post_max_size (8M without uploads, 32M with them) Large bodies that push towards OOM Does not cover unlimited multipart headers
Proxy limits: size, parts, headers and nesting Requests crafted to exhaust memory Must cover requests without a usable Content-Length
session.upload_progress.enabled=0 The session path of CVE-2026-109619 Does not cover other global state
fatal_error_backtraces=0 (PHP 8.5 only) The fatal error backtrace path Only that path
Remove per-request settings that raise warnings The consumer before initialisation Hiding warnings with @ or error_reporting does not help

Put these settings where PHP reads them when the worker starts, never with ini_set() or in .user.ini. In the official image a file in conf.d is enough. I tested it on php:8.4-fpm-alpine by mounting this file as /usr/local/etc/php/conf.d/zz-cve-2026-1096.ini:

session.upload_progress.enabled = 0
memory_limit = -1
post_max_size = 8M

With the file mounted, php-fpm -i returned session.upload_progress.enabled => Off, memory_limit => -1 and post_max_size => 8M, and php-fpm -t validated the configuration. In an FPM pool installed from packages, the equivalent is php_admin_value[memory_limit] = -1 and php_admin_flag[session.upload_progress.enabled] = off, which the application cannot override. Then restart every worker.

Two defaults explain why these settings matter. In the three official images I tested (8.3.31, 8.4.26 and 8.5.10), session.upload_progress.enabled is on, and on 8.5.10 fatal_error_backtraces is on as well. memory_limit=-1 removes the memory ceiling, so pair it with a container or cgroup memory limit. The advisory warns against RLIMIT_AS and RLIMIT_DATA, because they make mmap() fail and lead straight to the vulnerable path.

How to verify the fix is active

Verifying means confirming that every process that serves requests runs the new binary. Run the check script with the FPM or Apache binary, not only with the command-line one. On Debian or Ubuntu, dpkg -l must show the fixed package version, and systemctl status php8.4-fpm a start time after the upgrade.

Also check that no old processes remain. With FPM, the master and its children must have started after the apt upgrade; with Docker, docker compose ps must show the recreated container. If the server has more than one pool or more than one PHP version installed, repeat the check for each. When the Debian DSA for bookworm or the new Alpine package lands, repeat the process on those hosts.

If you already applied the CVE-2026-80521 mitigations on your Docker hosts, the pattern is the same: inventory first, then patch and restart, and only after that the mitigations you want to keep.

Frequently asked questions

Am I affected if I only use PHP from the command line?

The advisories describe attacks against installations that serve web requests: an attacker has to send HTTP requests to a PHP process that is reused or that parses uploads. A cron script with no external input does not fit that scenario. Even so, upgrade the command-line package in the same run as the rest.

Is disabling session.upload_progress enough?

No. That setting blocks the session path of CVE-2026-109619, but not the OOM of CVE-2026-109618 nor other global state that the full patch also resets. The advisory itself recommends keeping it disabled after you update, if you do not use the upload progress feature.

What should I do with PHP 8.1 or 7.4?

php.net does not publish releases for those branches, but its security team left minimal source patches for 7.4, 8.0 and 8.1. Ubuntu 22.04 already ships php8.1 8.1.2-1ubuntu2.28 with the patch. Outside a supported distribution, the way out is moving to 8.3 or 8.4.

Conclusion

CVE-2026-109618 and CVE-2026-109619 allow unauthenticated code execution on a PHP server, and Apache with mod_php is the most exposed setup. The fix is PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distribution’s patched package, plus a restart of every worker. Debian 13 and Ubuntu already have it; the official Docker image, Alpine and Debian 12 should follow within hours.

Start today by running the check script on every server and disabling session.upload_progress wherever you do not use it.

Sources: [1] php.net security advisory of 21 October 2026[1], [2] GHSA-w8hw-w9fx-j79w, RCE via OOM corruption[2], [3] GHSA-6wmf-4rv5-p3rp, RCE via stale global state[3], [4] CVE-2026-109618 record on cve.org[4], [5] CVE-2026-109619 record on cve.org[4], [6] PHP 8 ChangeLog[8], [7] Ubuntu notice USN-8948-1[6], [8] Ubuntu tracker for CVE-2026-109618[9], [9] docker-library/php repository[5], [10] Ondřej Surý’s PHP repository[7].

Sources

  1. php.net security advisory
  2. advisory GHSA-w8hw-w9fx-j79w
  3. Advisory GHSA-6wmf-4rv5-p3rp
  4. CVE record
  5. docker-library/php
  6. USN-8948-1
  7. packages.sury.org
  8. PHP 8 ChangeLog
  9. Ubuntu tracker for CVE-2026-109618