Skip to content

Patch your PHP before breakfast: two critical RCEs are already being exploited

CVE-2026-109618 and CVE-2026-109619 let anyone on the internet run code on your server — no login needed. What broke, why it matters, and the exact 20-minute fix.

Aboubakar Sidik FahaAboubakar Sidik FahaFounder

On Tuesday 21 October 2026, the PHP project did something it reserves for genuine emergencies: the day before, it pre-announced a security release for a fixed date and time — 21 October, 14:00 UTC — and shipped on schedule, for every supported branch at once. PHP does not do that for ordinary bugs. Two vulnerabilities — CVE-2026-109618 and CVE-2026-109619 — allow unauthenticated remote code execution. Both are rated 9.2 out of 10 (critical) on the CVSS v4.0 scale. Both are, in the words of the advisory, being actively exploited in the wild.

If that sentence doesn't ruin your coffee, read it again slowly: anyone on the internet can run arbitrary code on your server by sending it a crafted HTTP request. No account. No password. No click.

This post is the long version of the advisory we sent our customers — what actually broke, why it is worse than the average CVE, and the exact sequence of commands that takes you from vulnerable to verified-patched in about twenty minutes.

Short on time? Jump to the fix. But read the restart warning on the way — it is the step everyone skips.

What "unauthenticated RCE" actually means

Security bulletins throw the phrase around, so it is worth being concrete about what happens when one of these bugs fires:

  1. An attacker sends a normal-looking HTTP request to any PHP endpoint on your server — a WordPress login page, an API route, even a static-looking URL that eventually hits index.php.
  2. The request triggers the vulnerable code path and the interpreter's internal state corrupts in a way the attacker controls.
  3. The attacker executes code as the PHP process — typically www-data, apache or the FPM pool user.

From there, the game is over fast. That user can read your application's database credentials from its config file, dump the database, install a web shell, add a persistent cron job, and use the box to attack the rest of your network. Attackers automate this end-to-end: exploit, drop implant, move on. The whole sequence takes seconds, and it scales to thousands of scans per hour.

The two bugs

The project advisory has the authoritative technical detail; here is what matters for operators.

CVE-2026-109618 — RCE via OOM corruption (GHSA-w8hw-w9fx-j79w). Out-of-memory handling can interrupt PHP while internal request data is only partially initialized — and the request-cleanup path may then treat that invalid data as valid. The lesson generalises, and it is worth keeping: error-handling paths are code too, and they run precisely when the system is least able to defend itself.

CVE-2026-109619 — RCE via stale global state (GHSA-6wmf-4rv5-p3rp). Request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may then use that stale state before normal initialization resets it. PHP's request model is "shared-nothing" — each request starts with a clean interpreter — and this bug breaks exactly that isolation, which is the design feature that has saved PHP from whole bug classes for decades.

Both bugs are reachable over the network, need no authentication and no user interaction — exactly the combination that produces a 9.2.

Why mod_php servers are in the most danger

Every PHP deployment that answers web requests is affected, in all its modern flavours:

  • PHP-FPM — the standard behind nginx, Apache and Caddy
  • Apache with mod_php — the riskiest setup
  • php-cgi — still around on legacy stacks
  • FrankenPHP — the new app-server flavour
  • LiteSpeed / OpenLiteSpeed

The mod_php case deserves emphasis. With FPM, PHP runs in separate worker processes, so memory corruption is at least contained inside the pool. With mod_php, the interpreter is compiled into the Apache process itself — and the advisory confirms a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs. A corruption bug in that setup strikes the web server directly, in-process, often with broader filesystem access than an isolated pool. If you still run mod_php in 2026, consider this CVE your migration budget.

And a subtlety that catches people on shared hosting and panels (cPanel, Plesk, CyberPanel): servers routinely run several PHP versions side by side. Updating 8.3 does nothing for the 8.1 pool still serving three old sites. You have to hunt down every version on the box — more on that below.

The fix: twenty minutes, end to end

1. Snapshot first. Take a VM snapshot or at least back up document roots and databases. A security update is still a change, and changes deserve a way back. Two minutes now saves the blame game later.

2. Update the packages.

# Debian / Ubuntu
apt update && apt upgrade php-*

# AlmaLinux / Rocky / RHEL
dnf update php-*

On panels: cPanel/WHM users run /scripts/upcp (EasyApache 4 builds are already available), Plesk Obsidian updates from the panel, CloudLinux users should watch the CloudLinux status page for Alt-PHP builds. Distribution packages beat third-party repositories here — the volunteer-run remirepo does not publish fixes on release day, and the PHP project itself recommends distro packages or building from source.

3. Restart the web stack. The step everyone skips:

systemctl restart php-fpm
systemctl restart apache2   # and/or nginx

An update without a restart changes nothing. PHP-FPM workers and Apache's mod_php keep the old interpreter in memory until they are recycled. Your package manager will proudly report the new version while the process serving the internet still runs the vulnerable one. This is the single most common way "we patched it" turns out to be false in a post-incident review.

4. Verify what is actually running — the CLI and the web SAPI, and every installed version:

php -v

Then drop a two-line probe behind your vhost and load it over HTTP:

<?php phpinfo();

If the phpinfo() version does not match the CLI, you found a pool that was never restarted — go back to step 3. On cPanel, check every ea-php version in WHM's MultiPHP Manager; on Plesk, the panel's PHP versions page. Do this per version, not per server.

Two audiences often forgotten: anyone running PHP 8.6.0 release candidates should move to RC4 — published the same day with these fixes; earlier RC builds are vulnerable. And on PHP 8.1 and older there is nothing to update to beyond minimal source backports for 7.4–8.1 — your path is a supported branch or a vendor that backports for you.

Can't patch right now? Honest interim measures

Sometimes the change window is Tuesday and the fix is Friday. The advisory is unusually concrete about what reduces exposure — and unusually blunt that only an update is a complete solution:

Against CVE-2026-109618 (OOM corruption):

  • Set memory_limit = -1, preferably where applications cannot override it (php_admin_value in the FPM pool or Apache config) — and audit every pool, directory and app for a finite limit sneaking back in.
  • Lower post_max_size: 8M for apps without file uploads, 32M if uploads are needed, with upload_max_filesize below it. Enforce limits at the web server or proxy too: decoded request size, input-field count, multipart part count, header count, nesting depth — including requests without a usable Content-Length.
  • Use a container or cgroup limit as an availability boundary that kills only the affected worker. Do not substitute RLIMIT_AS or RLIMIT_DATA — they can trigger the vulnerable allocation failure themselves.

Against CVE-2026-109619 (stale global state):

  • Set session.upload_progress.enabled = 0 and restart every worker — via php.ini or pool/server configuration that applies before request parsing, not ini_set() or FPM .user.ini. This blocks the confirmed session paths, not every variant.
  • On PHP 8.5, set fatal_error_backtraces = 0 and restart every worker (blocks the confirmed fatal-backtrace path).
  • Remove per-request settings that emit diagnostics before initialization — hiding diagnostics with error_reporting, display_errors or @ does not help.
  • Fix attacker-reachable fatal errors and execution timeouts, including failures in callbacks and upload/import processing.

Treat all of it as a tourniquet: it reduces how easily an attacker can reach the bug; it does not remove the bug. Every hour of mitigation should be an hour spent testing the real upgrade.

How to know if someone got there first

Because exploitation is already active, patching is only half the job — the other half is asking "were we hit?" Do this even on servers you believe were patched quickly:

  • Web server logs: unusual POSTs to odd endpoints, bursts of 500s from a single IP or ASN, requests with malformed or truncated bodies.
  • Filesystem: files in web roots you never deployed — especially small PHP files with encoded payloads (look for eval, assert, gzinflate, long base64 strings), and .htaccess or config files changed recently.
  • Persistence: new cron jobs (crontab -l for every user, plus /etc/cron*), unknown systemd units, unexpected SSH keys in authorized_keys.
  • Behaviour: processes you don't recognise, sustained outbound traffic that isn't yours, CPU spikes with no traffic to match.
  • Compare with backup: a diff of the document root against last week's backup finds implants faster than any scanner.

If you find something: isolate the host from the network before cleaning. A compromised server is evidence, not just an outage.

The boring part that prevents the next emergency

Once the adrenaline fades, do the unglamorous work that makes the next advisory a non-event:

  • Automatic security updates. unattended-upgrades, dnf-automatic, or your panel's toggle — pick one, turn it on, and let security updates flow without a human in the loop.
  • A supported PHP. PHP 8.2 ends on 31 December 2026. Everything older than 8.2 is already unsupported, surviving only on vendor backports. The CVEs that land on dead branches do not get fixes — they get eulogies.
  • Less surface. Remove PHP versions and applications you no longer use. Every interpreter on the disk is a door you have to watch.
  • Defence in depth. A WAF, IP allow-listing on admin interfaces, MFA everywhere it fits. No single control should be load-bearing.

The bottom line

Two 9.2-rated bugs, exploits already in the wild, fixes already packaged. The distance between "vulnerable" and "verified patched" this time is one upgrade, one restart, and one honest phpinfo() per version on the server — about twenty minutes of work for a night's sleep. Do it now. Then automate it, so the next time PHP ships an emergency fix, the only thing you have to write is the calendar invite for the upgrade you already planned.

Sources: PHP.net — security advisory of 21 October 2026, GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. Fixed versions: 8.5.12, 8.4.27, 8.3.36, 8.2.35 (last 8.2 release before its 31 December 2026 end of life).

php security cve patching incident-response
Aboubakar Sidik Faha

Written by

Aboubakar Sidik Faha

Founder

Aboubakar is the founder of Notideus. He writes about email infrastructure, security and the day-to-day realities of running production systems.