The situation
The CISA alert landed on 21 October and the questions started immediately: are we exposed, is this as bad as the headlines say, and haven’t we seen this before? The last one is the useful question. PHP has a short list of “patch tonight” RCEs, and comparing them tells you more about where to look and how long the tail will be than the CVSS score does.
This post covers what was disclosed, how it compares to the older PHP code execution bugs, a hypothesis on whether this one is worse or just the same type, and the commands to inventory, mitigate and hunt.
What was disclosed
The PHP security advisory describes two bugs:
| CVE | PHP’s name | What goes wrong |
|---|---|---|
| CVE-2026-109618 (GHSA-w8hw-w9fx-j79w) | RCE via OOM corruption | An out-of-memory bailout interrupts PHP while request data is only partly initialized; cleanup then treats that data as valid. |
| CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp) | RCE via stale global state | Request-owned state outlives the memory behind it; the next request on the same worker uses it before normal initialization resets it. |
Key facts from the primary sources:
- Scope: every PHP installation serving web requests: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. PHP rates mod_php highest risk, with a demonstrated exploit that fires before application code runs.
- What’s actually been validated: per the GHSAs, full RCE is validated on mod_php for both CVEs (default memory and input limits, normal prefork). On PHP-FPM, CVE-2026-109619 is validated through the session lifecycle path. CVE-2026-109618 on FPM is supported by source analysis, but nobody has demonstrated a complete exploit. FrankenPHP and LiteSpeed are listed as likely or conditional, not validated.
- Fixed in: 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP 8.1 and older get no official release; the PHP team published minimal source backport branches for 7.4, 8.0 and 8.1.
- Weakness and scoring: both GHSAs list CWE-416 (use after free) as the primary weakness, with vector
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H. Red Hat rates CVE-2026-109618 Important (CVSS 3.1 8.1, AC:H), because standard RHEL uses FPM and FPM workers exit early on initialization failures. Red Hat’s comparison table shows NVD at 9.8 with AC:L. That gap matters later. - Exploitation: CISA had not observed exploitation at publication and neither CVE was in the KEV catalog as of 22 October. watchTowr reported exploitation attempts in its honeypots, and Amazon reported an attempt four days before disclosure. Huntress had not seen exploitation on partner systems. Treat “exploited” as attempts until someone publishes confirmed compromise.
Timeline per Sansec and vendor reporting: researchers reported the bugs to PHP in August, details leaked on 20 October, and PHP shipped fixes at 14:00 UTC on 21 October, a day early.
The older PHP RCEs worth comparing
These are the PHP-core code execution bugs I’d put on the same shelf. All are unauthenticated and all hit PHP before the application gets a say. The three older CVEs are all in CISA’s KEV catalog. I’ve added one well-known exploitation technique (not a CVE) because PHP2Shell abuses the same feature.
| CVE | Year | Component | Class | Needed to be exploitable | KEV |
|---|---|---|---|---|---|
| CVE-2012-1823 | 2012 | php-cgi (sapi/cgi) | Argument injection: a query string without = became CLI options (-d, -s) | PHP running as CGI | Yes |
| CVE-2019-11043 | 2019 | PHP-FPM (fpm_main.c) | Buffer underflow in env_path_info pointer arithmetic | nginx + FPM with a specific fastcgi_split_path_info regex | Yes |
| CVE-2024-4577 | 2024 | php-cgi on Windows | Argument injection via Windows “Best-Fit” (soft hyphen becomes -); bypass of the 2012 fix | Windows, CGI mode, certain code pages | Yes |
| Session upload progress (technique, no CVE) | ~2018 onward | session.upload_progress | A default-on feature that writes attacker-controlled data to a session before the script runs, used to turn LFI into RCE | An existing LFI bug in the application | n/a |
| CVE-2026-109618 / 109619 | 2026 | Engine request lifecycle | Use after free on fatal-error / OOM paths (CWE-416) | Validated on mod_php (both) and FPM (109619); a persistent worker that is reused | Not as of 22 Oct 2026 |
A few details on each:
- CVE-2012-1823 affected PHP before 5.3.12 and 5.4.2 when configured as php-cgi. The first fix was incomplete, which produced CVE-2012-2311 and friends.
- CVE-2019-11043 was triggered by a crafted URL that broke the nginx path-splitting regex, leaving
PATH_INFOempty. Public exploits used the underflow to overwrite FastCGI’sPHP_VALUEand rewrite ini settings until code ran. - CVE-2024-4577 is the 2012 bug again, twelve years later, reached through character conversion the original fix never anticipated (Windows “Best-Fit” on certain Chinese and Japanese code pages). Exploit attempts showed up within a day of disclosure.
- Session upload progress is relevant because PHP’s GHSA for CVE-2026-109619 names it as a confirmed consumer of the stale state, and as a producer: the demonstrated producer is an OOM during multipart input processing. The feature is enabled by default and needs no
session_start()call. The old LFI technique still needed an application bug; PHP2Shell doesn’t.
The similarities
1. Pre-application reachability. Every CVE above lives in the SAPI or request-parsing code. Your login page and your framework’s input validation never get to run. A WAF in front of the server still sees the request, and CISA notes it may give some protection, but only if it has a rule for the pattern. Datadog says it reproduced the 2026 bugs and confirmed application-level authentication can’t block them. This is the most important trait the family shares.
2. Old code, long tails. The 2012 argument injection came back in 2024. The 2026 bugs sit in code going back to PHP 5.4.0, released in 2012, and the PHP Foundation says signs of the OOM flaw surfaced during a 2024 external audit without a working attack path being found. Request-lifecycle code is rarely rewritten, so its bugs live a long time.
3. Configuration decides exploitability. 2012 and 2024 needed CGI. 2019 needed a particular nginx regex. In 2026, mod_php is validated for both CVEs and FPM for one, and exploit reliability depends on build, extensions, front end and allocator layout. A researcher cited by Sansec found the public proofs of concept fail against a default install, even though PHP’s own mod_php validation used default limits: the public PoCs are not the private ones. In every case “is PHP installed?” is the wrong question; “which SAPI, which front end, which ini?” is the right one.
4. ini is the attacker’s lever and the defender’s stopgap. In 2012 the attacker passed -d to set ini values. In 2019 they wrote FastCGI’s PHP_VALUE. In 2026, PHP’s GHSA names ini diagnostics raised during early configuration as a confirmed consumer of the stale state. On FPM that path opens when the front end passes PHP_VALUE or PHP_ADMIN_VALUE, the same parameter as 2019. The interim mitigations are ini settings again. PHP’s runtime configuration has always been part of the attack surface.
5. Fast weaponization. CVE-2024-4577 saw attempts within 24 hours. In 2026 attempts reportedly started before disclosure. PHP is too widely deployed for a quiet patch window.
Worse, or the same type? A hypothesis
This section is my assessment, not a vendor finding. It is a hypothesis, and I’ll update it as exploitation data matures.
Mechanically, PHP2Shell is the same type as CVE-2019-11043: memory corruption in request-handling C code, reached before the application runs. The specific weakness differs: 2019 was a buffer underflow (CWE-124), and 2026 is use after free (CWE-416). The 2012 and 2024 bugs are a different type, logic flaws that were deterministic and easy to exploit (CVSS 3.1 9.8, AC:L). The 2026 bugs carry AC:H and AT:P in their CVSS 4.0 vector: exploitation depends on heap layout and on the producer and consumer requests landing on the same worker. That’s why Red Hat scored lower.
On blast radius, I think it is worse than anything in the table, for five reasons:
- Not tied to a niche deployment. Every predecessor needed one deployment shape: CGI, nginx with a certain regex, Windows code pages. These are validated on the two SAPIs that carry most PHP traffic (mod_php for both, FPM for CVE-2026-109619), and PHP lists every web SAPI as at risk.
- The bug class is systemic, not a single line. Both CVEs are about state left behind when PHP bails out of a request via
longjmp()(OOM, fatal errors, timeouts) on a worker that is then reused. Bailouts happen all over the engine. PHP says plainly that the ini mitigations block the confirmed paths, not other variants. I expect follow-on CVEs in the same class, the same way 2012 produced 2012-2311 and eventually 2024-4577. - Worker reuse is the exploitation primitive. FPM pools and Apache prefork processes are long-lived by design for performance. The architecture that makes PHP fast is the one that carries corrupted state into the next request, possibly someone else’s.
- The EOL tail is huge. Affected versions run back to 5.4. Every PHP 7.x and 8.0/8.1 box left behind by an app that “can’t be upgraded” stays vulnerable unless a distro or LTS vendor backported the fix. Expect this one to sit in scan reports for years, as CVE-2012-1823 did.
- Discovery got cheaper. OpenAI says a researcher found one of these with model assistance, and Wiz says it used models to find and fix both. Researchers called the exploit chain extremely complex. If tooling lowers the cost of finding and chaining bugs in error-path code, the next variant arrives sooner.
Where it may be less bad: AC:H is real. The public PoCs reportedly fail on default installs, nobody has demonstrated a complete FPM exploit for CVE-2026-109618, RHEL-style FPM deployments are harder to hit, and as of writing nobody has published a confirmed compromise. If that holds, the 2026 bugs may end up with a smaller exploited population than the trivially weaponized 2012 and 2024 CGI bugs.
Verdict: same type as 2019, wider blast radius than any predecessor, and the longest tail. Patch it like the worst case, and don’t let a lower vendor score talk you out of mod_php boxes.
Remediation
Updating is the only complete fix. Use distro packages where possible; PHP says vendors received patches in advance.
1. Inventory every PHP, by SAPI
The CLI version tells you nothing about the FPM or Apache module version on the same box. Check each one. On Linux the package manager is the source of truth:
# Installed packages (pick your distro)
dpkg -l | grep -E '^ii\s+(php|libapache2-mod-php)' # Debian/Ubuntu
rpm -qa | grep -E '^php' # RHEL family
# Did your distro ship the fix? Distros backport without bumping to 8.2.35 etc.,
# so check the package changelog for the CVE instead of the version string
apt changelog php8.2-common 2>/dev/null | grep -m1 -E 'CVE-2026-10961[89]' # Debian/Ubuntu (adjust package name)
rpm -q --changelog php-common | grep -m1 -E 'CVE-2026-10961[89]' # RHEL family
# PHP binaries no package owns (hand builds, vendor bundles, leftovers): these are the ones to chase.
# Lists them without executing anything
find / -xdev -type f -regextype posix-extended \
-regex '.*/(php|php-cgi|php-fpm)[0-9.]*' 2>/dev/null |
while read -r b; do
dpkg -S "$b" >/dev/null 2>&1 || rpm -qf "$b" >/dev/null 2>&1 || echo "UNOWNED: $b"
done
# Is mod_php loaded? PHP rates this SAPI highest risk
apachectl -M 2>/dev/null | grep -i php
find -xdev stays on the root filesystem. Repeat it for /opt, /srv or /var if they’re separate mounts.
On Windows (IIS FastCGI, XAMPP-style stacks, vendor appliances that bundle PHP):
# Local fixed disks only (DriveType 3): skips mapped network shares and removable media
$roots = (Get-CimInstance Win32_LogicalDisk -Filter 'DriveType = 3').DeviceID | ForEach-Object { "$_\" }
# php.exe (CLI), php-cgi.exe (IIS FastCGI / CGI) and php*apache2_4.dll (Apache mod_php on Windows)
foreach ($name in 'php.exe', 'php-cgi.exe', 'php*apache2_4.dll') {
Get-ChildItem -Path $roots -Filter $name -Recurse -File -Force -ErrorAction SilentlyContinue |
Select-Object FullName,
@{ n = 'Version'; e = { $_.VersionInfo.ProductVersion } }, # read from file metadata; never run unknown binaries
LastWriteTime
}
Don’t forget container base images, and software that embeds PHP without advertising it. CISA calls this out explicitly.
2. If you can’t patch tonight: interim settings
From the PHP advisory. Set these where the application can’t override them (FPM pool or Apache config, not ini_set() or .user.ini), then restart every worker.
| Setting | Value | Targets | Trade-off |
|---|---|---|---|
memory_limit | -1 | CVE-2026-109618 | Removes the controllable OOM; one request can now use all memory, so pair it with a container or cgroup limit |
post_max_size | 8M (no uploads) or 32M | CVE-2026-109618 | 8M is already PHP’s default, so this mostly means don’t raise it; keep upload_max_filesize below it |
session.upload_progress.enabled | 0 | CVE-2026-109619 | Server-side upload progress stops working |
fatal_error_backtraces | 0 (PHP 8.5 only) | CVE-2026-109619 | No backtraces in fatal errors |
FPM pool example:
; FPM pool file, e.g. /etc/php/<ver>/fpm/pool.d/www.conf (path varies by distro)
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 only: uncomment on 8.5. Unknown directives on older versions just add noise
;php_admin_flag[fatal_error_backtraces] = off
; Optional, per the GHSA: one request per worker, so no stale state carries over. Costs throughput
;pm.max_requests = 1
Apache mod_php equivalent, in the main server or vhost config (not .htaccess):
php_admin_value memory_limit -1
php_admin_value post_max_size 8M
php_admin_flag session.upload_progress.enabled off
Cautions from the advisories:
- Don’t use
RLIMIT_ASorRLIMIT_DATAas the memory boundary. They makemmap()fail, which is the vulnerable path. Use a cgroupmemory.max, ideally one group per worker. - Error suppression (
display_errors,error_reporting,@) does nothing for CVE-2026-109619. - Keep per-directory overrides clean. Invalid or deprecated
php_valuelines (including in.htaccess) emit diagnostics before request startup, and that is one of the confirmed consumer paths. - If the application doesn’t take file uploads, blocking
multipart/form-dataat the reverse proxy removes the demonstrated OOM trigger.
3. Hunt while you patch
There are no public IOCs yet. Hunt for the outcome: a PHP or web server process spawning a shell or download tool (T1190, T1505.003, T1059).
# Live check: children of PHP/web server processes that aren't more PHP/web server workers
for p in $(pgrep -f 'php-fpm|php-cgi|apache2|httpd'); do
ps --ppid "$p" -o pid=,ppid=,user=,cmd=
done | grep -vE 'php-fpm|php-cgi|apache2|httpd'
# Sysmon Event ID 1 (process creation), last 7 days: shells spawned by PHP/IIS processes
Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-Sysmon/Operational'; Id = 1; StartTime = (Get-Date).AddDays(-7) } |
ForEach-Object {
$d = @{}
([xml]$_.ToXml()).Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' } # parse by field name, not index
if ($d.ParentImage -match '\\(php-cgi|php|httpd|w3wp)\.exe$' -and
$d.Image -match '\\(cmd|powershell|pwsh|certutil|bitsadmin)\.exe$') {
[pscustomobject]@{ Time = $_.TimeCreated; Parent = $d.ParentImage; Child = $d.Image; CommandLine = $d.CommandLine }
}
}
Both checks assume the shell runs as a direct child of the PHP or web server process; your EDR’s process-tree query will catch grandchildren too. The Windows query needs Sysmon. Also look for new or changed PHP files in web roots since the first reported attempt (17 October):
find /var/www -type f -name '*.php' -newermt 2026-10-17 -ls # adjust web root
Get-ChildItem C:\inetpub\wwwroot -Recurse -Filter *.php -File | Where-Object LastWriteTime -ge '2026-10-17' # adjust web root
Timestamps can be faked. Treat a clean result as “nothing obvious”, not “clean”.
Verification
- Re-run the inventory. Every SAPI on the box should be on an upstream fixed version, or on a distro package whose changelog lists both CVEs. Distro builds often keep the old upstream version number.
- Confirm the running workers picked up the change by comparing master process start times with your patch time. A package update without a restart leaves vulnerable code in memory:
# Debian names the binary php-fpm8.2 etc., so match the master's title rather than -C php-fpm
ps -eo pid,lstart,args | grep -E '[p]hp-fpm: master|[a]pache2 -k start|[h]ttpd -k start|/usr/sbin/[h]ttpd'
- If you applied interim settings, verify the effective value from inside the SAPI (a temporary, access-restricted
ini_get()check through the web server, then delete it), becausephp -ireads the CLI’s ini, not FPM’s. - Re-scan externally, but don’t trust banner checks. A scanner keyed on
X-Powered-Bywill flag a patched distro build as vulnerable and miss a box with the header turned off.
Caveats
- Interim settings are partial. PHP says they don’t make an installation safe. Record them as compensating controls with an expiry, not as remediation.
memory_limit = -1trades RCE risk for DoS risk. Without a cgroup or container memory limit, one request can starve the host.- Compliance: for CMMC Level 2 this is SI.L2-3.14.1, Flaw Remediation (NIST SP 800-171 Rev. 2 3.14.1). The assessor will check that you defined a time frame for correcting flaws and met it, so a critical like this tests your SLA. For PCI DSS v4.0.1 it’s Requirement 6.3.3: critical patches within one month of release. For HIPAA it belongs in the Security Rule risk analysis and risk management process (45 CFR 164.308(a)(1)(ii)(A)–(B)); for SOC 2, in your vulnerability management evidence. If either CVE is added to KEV, federal civilian agencies get a BOD 22-01 due date.
- Risk acceptance: if a business owner insists on keeping an unpatched or EOL PHP app online, get it in writing, isolate it (no internet exposure, WAF in front, egress filtering) and set a review date.
Key takeaways
- PHP’s worst bugs keep landing in the same place: request handling, before the application runs. App-layer authentication and validation don’t protect you.
- Inventory by SAPI, not by “PHP installed”, and prove the fix from the package changelog. The CLI version is not the FPM or mod_php version, and distro backports keep old version numbers.
- PHP2Shell is the same type as the 2019 FPM underflow but with a wider blast radius: it’s validated on mainstream mod_php and FPM setups, not one niche deployment.
- Fixes in old request-lifecycle code have a history of being incomplete. Plan for variants and keep the interim hardening where it doesn’t hurt.
- The EOL tail will outlive the headlines. Use this event to retire PHP 7.x and 8.0/8.1 for good.
References
- PHP Group: Security advisory: Critical remote code execution vulnerabilities, update now (21 Oct 2026)
- PHP Group: GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp
- CISA: Critical Vulnerabilities in PHP Require Immediate Patching
- CISA: Known Exploited Vulnerabilities Catalog
- CVE Program: CVE-2026-109618, CVE-2026-109619
- Red Hat: CVE-2026-109618
- NVD: CVE-2012-1823, CVE-2019-11043, CVE-2024-4577
- PHP Manual: Session Upload Progress
- MITRE ATT&CK: T1190, T1505.003, T1059
- NIST: SP 800-171 Rev. 2
- Vendor analysis: Sansec, Datadog Security Labs, Huntress, Tenable, watchTowr