Skip to content
PHP Security10 min read·

PHP's two critical RCEs of October 2026: which versions are fixed, how to patch, and what to do on 8.1 and older

On 21 October 2026 PHP shipped fixes for two unauthenticated remote code execution bugs, both CVSS 9.2, one of them demonstrated against mod_php before any application code runs. Which releases fix them, how to confirm the version your web server is actually running, what the mitigations do and do not buy you, and the options left on 8.1, 8.0, 7.4 and anything older.

PHPSecurityCVEPatchingPHP-FPMmod_phpLaravel

On Tuesday 20 October the PHP project did something it rarely does: it gave a day's notice of a security release and said both bugs in it scored 9.2. The release landed on Wednesday 21 October at 14:00 UTC as PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, and the announcement opened with update now in the title.

The wording was justified. Both vulnerabilities allow remote code execution without authentication, and for Apache with mod_php the PHP security team has a working exploit where a single crafted request triggers the bug before your application code runs. Your framework, your middleware, your WAF rules written for application routes and your input validation never see it. If the server parses the request body, it is in scope.

This is the practical version: what the two bugs are in plain terms, exactly which versions fix them, how to patch without fooling yourself, what to do if you cannot patch today, and the uncomfortable part, which is what happens to every server still on PHP 8.1 or older.

CVSS v4.0 score on both CVEsnetwork reachable, no privileges, no user interaction
9.2
lines of your code that run before the mod_php exploit firesit triggers during request body parsing, at startup
0
supported branches patched: 8.2, 8.3, 8.4 and 8.58.6.0 RC4 carries the fix too
4
end of life branches given unofficial source backports7.4, 8.0 and 8.1, as git branches rather than releases
3

The two vulnerabilities, without the CWE list

CVE-2026-109618: remote code execution via out-of-memory corruption

When PHP runs out of memory it has always bailed out with longjmp(), jumping straight out of whatever it was doing. In a few places request memory is reachable before it is fully initialised, so an allocation failure at exactly that moment leaves it half built. Request shutdown then reads that half built state as if it were valid, and with the right heap layout that becomes code execution as the web server's user (GHSA-w8hw-w9fx-j79w.

Hitting the memory limit on purpose turns out to be easy, and that is what makes this critical rather than theoretical. A multipart request without a usable Content-Length can get past the post_max_size check and keep feeding data. A single multipart part can carry an unlimited number of headers, and those cost far more memory inside PHP than on the wire. Empty file inputs do not count towards max_file_uploads but still build metadata arrays, and nested field names multiply that work. So a request that is well within post_max_size can still exhaust memory_limit, and mod_php runs shutdown even when startup fails.

CVE-2026-109619: remote code execution via stale global state

A PHP worker serves many requests, and it keeps some global state between them. That state is supposed to be reset at the start of each request. The bug is that some of it can still point at memory owned by a previous request that has already been freed, and a handful of operations run before the reset and read it anyway (GHSA-6wmf-4rv5-p3rp.

Exploiting it needs a pair of requests to the same worker. The first is a producer, which leaves dangling state behind: a fatal error, an execution timeout, or an out-of-memory error in the middle of processing. The second is a consumer, which touches that state before initialisation: an INI warning raised by a per-request setting such as a php_value in .htaccess, or session upload progress handling. Two paths are confirmed. On 8.5, fatal_error_backtraces leaves a backtrace behind that a later diagnostic reads, which is the basis of the mod_php exploit. On every branch, an out-of-memory error during input processing can leave session upload progress state pointing at freed memory, and that one was validated against mod_php and PHP-FPM.

Which versions fix it

Every PHP release older than these four is affected, on every supported branch. There is no affected range that starts at some later patch version; if you are below the fixed version on your branch, you are vulnerable.

BranchVulnerableFixed inBranch status today
8.58.5.11 and earlier8.5.12Active support until 31 Dec 2027, security fixes until 31 Dec 2029
8.48.4.26 and earlier8.4.27Active support until 31 Dec 2026, security fixes until 31 Dec 2028
8.38.3.35 and earlier8.3.36Security fixes only, until 31 Dec 2027
8.28.2.34 and earlier8.2.35Security fixes only, and end of life on 31 Dec 2026
8.6 (pre-release)RC3 and earlier8.6.0 RC4Not for production, RC5 due 5 Nov 2026
8.1, 8.0, 7.4AllNo official releaseEnd of life. Minimal source backports only, covered below
7.3 and olderAssume allNothing from the PHP projectEnd of life. Vendor backport or upgrade

One more thing about that table. September's releases (8.5.11, 8.4.26, 8.3.35, 8.2.34) fixed a batch of other CVEs, eleven of them on the 8.5 branch, including a heap overflow in OpenSSL wildcard certificate matching, an IPv6 bypass of FPM's listen.allowed_clients, and a credential leak across HTTP stream redirects. If you skipped September, moving to the October release closes those as well. It is one more reason the answer is the current patch release on your branch, not a cherry picked fix.

Who is actually exposed

The project's own line is that every installation serving web requests is at risk. The severity differs by how PHP is attached to your web server, and it is worth knowing which one you have before deciding how fast to move.

How PHP runsExposureWhy
Apache with mod_phpHighest. Exploit demonstrated, unauthenticated, default limits.Runs request shutdown even when startup fails, and .htaccess php_value lines are early consumers. Both bugs confirmed here.
PHP-FPM (Nginx, Apache with proxy_fcgi, Caddy)HighA failed startup kills the child, which narrows the first bug, but the session upload progress path of the second bug was validated against FPM. A frontend passing PHP_VALUE adds another consumer.
FrankenPHPLikelySource analysis says shutdown runs unconditionally, so the first bug is probably reachable. Not demonstrated end to end.
LiteSpeed (LSAPI), php-cgiLikelyModelled on FPM. Listed as at risk by the project, exploitation unvalidated.
CLI only (cron, queue workers, artisan)Low, not zeroNo HTTP request body to weaponise. Upgrade anyway, because the CLI binary is almost always the same package as the one the web server uses.

If you are on shared hosting, this is almost certainly mod_php or LSAPI and the patch is your host's job. Ask them, in writing, which version is serving your site and when it moved. Managed platforms (Forge provisioned servers, Ploi, Laravel Cloud, Vapor, the big PaaS providers) will roll the runtime for you, but will is not has, and the version check below takes a minute.

How to patch it properly

The most common way to get this wrong is to upgrade a package and check the wrong binary. php -v on a server reports the CLI. Apache's mod_php and each FPM pool are separate binaries, often from separate packages, and they keep running the old code in memory until they are restarted. So the order is: find what the web server runs, upgrade that, restart it, then check through the web server rather than the shell.

  1. 1Find what serves your requests. Is mod_php loaded, which FPM versions are installed, and which socket does the vhost point at. Servers that have been through two or three upgrades often have several PHP versions installed, and the one the site uses is not always the newest.
  2. 2Upgrade from the source you installed from. Distribution and LTS vendors received the patches before the public release, so check your vendor's security tracker for the fixed package. Official Docker images: bump the tag to the fixed patch version and rebuild, because a floating php:8.4-fpm tag in a cached build layer does not update itself. Windows builds are on php.net's download page.
  3. 3Watch out for rpms.remirepo.net. The announcement specifically said that volunteer maintained repository would not have updates on release day and urged users to move to distribution packages or build from source so they can patch immediately. If you are on RHEL, Alma or Rocky through Remi, check before assuming dnf update did anything.
  4. 4Restart every worker, not just the service you thought of. Restart Apache if mod_php is loaded, restart (not reload) each PHP-FPM version that serves a site, and restart long-running PHP processes such as Octane, RoadRunner, FrankenPHP workers, Horizon and queue workers. A process started before the upgrade is still running the vulnerable code.
  5. 5Verify through HTTP. Put a version check behind the web server, hit it, then delete it. If it does not say 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or later on your branch, you are not done.
bash
# 1. What does the web server load? (Debian/Ubuntu names shown)
apache2ctl -M 2>/dev/null | grep -i php     # php_module here means mod_php
ls /etc/php/                                # every installed version
grep -R "fastcgi_pass\|proxy:unix" /etc/nginx /etc/apache2 2>/dev/null

# 2. Upgrade, then restart everything that holds PHP in memory
sudo apt update && sudo apt install --only-upgrade 'php8.4*'
sudo systemctl restart php8.4-fpm           # restart, not reload
sudo systemctl restart apache2              # if mod_php is loaded
php artisan queue:restart                   # Laravel workers pick up the new binary

# 3. Check through the web server, not the shell, then delete the file
echo '<?php echo PHP_VERSION, " ", PHP_SAPI;' | sudo tee /var/www/html/v-check-91x.php
curl -s https://your-site.example/v-check-91x.php   # expect 8.4.27 fpm-fcgi or later
sudo rm /var/www/html/v-check-91x.php

If you cannot patch today

Both advisories say the same thing in bold: updating is the only complete fix, and no configuration makes an installation safe. What the configuration below does is remove the specific, confirmed attack paths, which turns a published, working exploit into something an attacker has to build for your setup. Apply it now, then patch anyway.

ini
; Apply where it takes effect BEFORE request parsing: php.ini, the FPM pool
; (php_admin_value / php_admin_flag) or the Apache vhost. ini_set() and
; FPM .user.ini files run too late and do nothing for these bugs.

; CVE-2026-109618, out-of-memory corruption
memory_limit = -1             ; removes the predictable limit the exploit relies on
post_max_size = 8M            ; 32M if the app genuinely needs uploads
upload_max_filesize = 6M      ; keep it below post_max_size

; CVE-2026-109619, stale global state
session.upload_progress.enabled = 0   ; keep this off after patching if unused
fatal_error_backtraces = 0            ; PHP 8.5 only

; In an FPM pool the same lines look like:
;   php_admin_value[memory_limit] = -1
;   php_admin_flag[session.upload_progress.enabled] = off
  • memory_limit = -1 needs a ceiling somewhere else. Without a PHP limit, one runaway request can take the whole box. Put each worker under a container or cgroup memory limit, ideally one that kills the offending worker rather than the FPM master. Do not reach for RLIMIT_AS or RLIMIT_DATA: they make mmap() fail, which is exactly the allocation failure that reaches the vulnerable path.
  • Make sure nothing puts a finite limit back. A pool file, a <Directory> block, an .htaccess line or an application calling ini_set('memory_limit', ...) restores the predictable failure. Grep for all of them.
  • Bound the request before PHP sees it. Limit body size, field count, multipart part count, headers per part and nesting depth at Nginx, Apache or your proxy, including requests with no usable Content-Length. If the app does not use multipart forms at all, block multipart/form-data at the edge.
  • Stop setting PHP options per request. Delete stale php_value lines in .htaccess and PHP_VALUE parameters passed by the frontend, especially ones for deprecated or misspelled settings. Silencing the warning with error_reporting, display_errors or @ does not help: the bug is the warning being generated, not displayed.
  • Remove the producers you can find. Fatal errors and timeouts on paths an anonymous visitor can reach (upload handlers, imports, error pages, destructors, shutdown functions) are the first half of the second bug. Turn on log_errors with error_reporting = E_ALL in a staging copy and look for warnings logged before your script starts. A clean log is not proof of safety, but a noisy one is a to do list.
  • pm.max_requests = 1 closes the cross-request path by giving every request a fresh FPM child, at a real cost in throughput. It is a short term tourniquet for a high value app, not a setting to leave behind.

PHP 8.1, 8.0, 7.4 and older: what is left for you

This is the part most coverage skips. PHP 8.1 reached end of life on 31 December 2025, 8.0 in November 2023 and 7.4 in November 2022. The PHP project does not ship releases for them, and there will be no 8.1.35 for this. A lot of production PHP is on exactly those versions, so here are the realistic options, from best to worst.

  1. 1Use your vendor's backport, if you have one. If PHP came from your Linux distribution's own packages on a release still in its support window, or from a paid LTS vendor, the vendor got the fix ahead of the public release and is responsible for patching the version they ship. That is the point of paying for or choosing an LTS distribution. Check their security tracker for both CVE numbers rather than assuming.
  2. 2Build the official minimal backport from source. Because of the severity, the PHP security team published minimal source backports as branches in php-src: PHP-7.4-CVE-2026-109618-109619, PHP-8.0-CVE-2026-109618-109619 and PHP-8.1-CVE-2026-109618-109619. These are git branches, not tagged releases or tarballs, intended for people who build from source and have no LTS vendor. Read the diff, build it with the same configure flags as your current binary, and test it in staging first.
  3. 3Upgrade to a supported branch. This is the actual fix, and it is also the one that takes longest. 8.1 to 8.3 or 8.4 is usually a few days for a reasonably maintained Laravel or Symfony app. 7.4 to 8.x is a project. Do it anyway, starting now, with the mitigations above holding the line in the meantime.
  4. 4PHP 7.3 and older has no fix from anyone but a vendor. No backport branch was published for 7.3, 7.2, 7.1, 7.0 or 5.x. If you run one of these on the public internet with no LTS vendor behind it, the mitigations are all you have, and the honest risk statement is that you are running an internet facing interpreter with a published critical RCE and no patch.
bash
# Building the official 8.1 backport. Reuse your current build's flags so the
# new binary has the same extensions as the one it replaces.
php -i | grep "Configure Command"     # copy the flags from here

git clone --depth 1 --branch PHP-8.1-CVE-2026-109618-109619 \
  https://github.com/php/php-src.git php-8.1-cve
cd php-8.1-cve
git log -3 --oneline                  # read what you are about to ship
./buildconf --force
./configure <your flags from above>
make -j"$(nproc)" && make test        # then deploy to staging, not straight to prod
Flowchart: what to do about CVE-2026-109618 and CVE-2026-109619
  1. Find the PHP version the web server actually runs

    Through HTTP, not php -v in a shell.

  2. Already on 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or later?

    ↳ yes? confirm every worker restarted since the upgrade, then you are done

  3. On 8.2 to 8.5: upgrade the package and restart every PHP process

    Apache, each FPM version, Octane, queue workers. Verify over HTTP again.

  4. Can the upgrade go out today?

    ↳ no? apply the ini and edge mitigations now, patch within days

  5. On 8.1, 8.0 or 7.4?

    ↳ vendor backport if you have one, else build the php-src backport branch

  6. On 7.3 or older with no LTS vendor

    No fix exists. Mitigate, restrict exposure, and treat the upgrade as an incident.

  7. Patched, verified, and an upgrade off EOL branches on the calendar

Mitigations reduce exposure on every branch. They never replace the patch.

What this one teaches about running PHP

The detail I would take away from this release is where the bug lives. It is not in a framework, a package, or a corner of an extension most people never load. It is in how the interpreter handles running out of memory and how a worker carries state between requests, which is code every PHP request on earth goes through. No amount of care in your own application would have protected you, and the only defences that worked on the day were the operational ones: knowing which PHP serves each site, being able to roll a runtime in hours, and not being two branches past end of life.

Your PHP version is a dependency like any other, and the only one your `composer.lock` does not pin, audit or warn you about.

Which is the same argument as the Laravel app that survives year two: the boring infrastructure decisions are the ones that decide how bad your worst week is. A team with a pinned runtime image, a staging copy and a one command deploy shipped this fix on Wednesday afternoon. A team on a hand built server from 2021 is reading this post.

If you have PHP servers you are not sure about, an app stuck on 7.4 or 8.1 that needs to get onto a supported branch, or a Laravel estate where nobody is quite sure which version each site is running, tell me what you have and I will come back with an audit, an upgrade plan and a fixed price. Start that conversation here.

Building something like this?

I design and ship these systems for clients: retrieval over private data, agents that complete real tasks, and the Laravel platforms underneath them.

Keep reading