Two PHP memory bugs, from the first web request to possible code execution — and how governments and critical services held the line in the 23 hours before a fix existed. A readable guide to the affected systems, the internals that matter, and the evidence defenders can look for.
Background
PHP2Shell is two critical flaws in PHP’s runtime — CVE-2026-109618 and CVE-2026-109619 — that can let an unauthenticated visitor run code on the server, before the website’s own code even starts.
Affected
Every PHP install serving web requests — mod_php, PHP-FPM and others
Fixed in
8.5.12 · 8.4.27 · 8.3.36 · 8.2.35
No official fix
PHP 8.1 and older (end of life)
How common PHP is — down to governments
PHP runs 69.2% of websites whose server-side language is known, and 59.3% of the 1,000 most popular. VulnCheck counted about 2.6 million internet-facing PHP hosts, roughly 42% on end-of-life versions.
W3Techs, 25 October 2026 (JavaScript: late August 2026).
The public sector is no exception. Government portals lean on PHP systems like Drupal and WordPress, and CISA warned that much of PHP’s footprint may not be directly observable. On 21 October, governments and critical services took sites offline rather than stay exposed:
The 23 hours without a patch
The CVE IDs leaked around 15:00 UTC on 20 October; PHP’s fix shipped at 14:00 UTC the next day. Attackers were already probing.
How organizations handled the gap
Complete · costs uptime
Take it offline
The only full protection before the fix. Cyber agencies went first — the Dutch NCSC pulled its own site.
Partial · stopgap
Shield at the edge
WAF rules blocked known exploits, but traffic reaching the origin directly bypassed them.
Partial
Harden the config
Disable session upload progress and cap request size at the proxy. Each closes only some attack paths.
Preparation
Find every PHP
Inventory PHP inside CMSs, panels and appliances so the patch could roll out the moment it landed.
Understanding PHP
When someone opens a PHP website, the web server hands the request to PHP. PHP reads the input, runs the site’s code and sends a page back.
Browser
Sends a request
Web server
Passes it to PHP
PHP
Reads input, runs code
Response
A page comes back
Workers handle one visitor after another
A worker is a running process that handles requests — think of a waiter who serves one table, clears it, then seats the next guest. The same worker serves many visitors in a row. With mod_php, PHP runs inside Apache’s own workers; with PHP-FPM, PHP keeps its own pool of workers.
The cleanup rule
While handling a request, PHP borrows memory and keeps notes of where things are — pointers. A few notes live in the worker itself and outlast the request; we call these worker globals. Before the next guest sits down, PHP must erase those notes and hand the memory back.
Use memory
Notes point to it
Erase the notes
No old addresses left
Release memory
Next request starts clean
A note that survives points at memory now owned by someone else — a dangling pointer.
When PHP runs out of memory mid-task
Each request gets a memory budget, memory_limit. If a request goes over it, vulnerable PHP drops what it was doing on the spot — a bailout — and skips the remaining steps. Both PHP2Shell bugs start here: one leaves half-built data behind, the other leaves an old note pointing at freed memory.
CVE-2026-109618: a half-built entry
An attacker makes PHP run out of memory while it’s in the middle of building a list. PHP abandons the job, but its cleanup later trusts the half-finished entry — and that opening can be turned into code execution.
1. A small request, a big memory bill
The attacker sends a POST made of many tiny upload parts with deeply nested names. Each part is almost empty, but PHP builds internal arrays and strings to describe them — so the bookkeeping costs far more memory than the bytes on the wire. A request well under the post_max_size cap can still blow past the memory_limit cap and trigger an out-of-memory bailout.
Tiny POST
Many nested upload parts
PHP describes each one
Arrays, entries, strings
Over the limit
Out-of-memory bailout
2. The crash lands mid-entry
PHP builds a list by first bumping its count, then filling in the new slot (its bucket). The bailout can hit in between: the list says it holds three entries, but only two are real. The third slot still holds leftover bytes from earlier memory use.
3. Cleanup trusts the count
PHP still runs cleanup on the failed request. Cleanup believes the count of three, walks to the unfinished slot and treats its leftover bytes as real internal data — including what it thinks are memory addresses. Following those false addresses lets PHP read or write the wrong memory. That’s the memory corruption.
How that becomes code execution
By choosing the request carefully, an attacker controls the leftover bytes in that slot — so the false “addresses” point where they want. With enough control they overwrite the value that decides what PHP runs next, redirecting the worker into their own code. Reaching out-of-memory alone isn’t enough; a plain attempt may just crash. This route has been demonstrated end-to-end on Apache mod_php, and the code runs with the worker’s permissions.
In one line: structured POST → excessive memory use → out-of-memory mid-build → cleanup trusts the unfinished entry → controlled corruption → remote code execution.
CVE-2026-109619: a note left behind
Here a worker keeps a note pointing at one request’s memory, a crash skips the step that would erase it, and a later request follows the stale note after that memory has been reused. The pattern: one request leaves state behind, a second request trips over it.
1. Request A leaves a note
PHP’s session upload-progress feature — on by default — tracks how much of an upload has arrived, and it sets up early, while PHP is still parsing the request. That leaves a worker global pointing into request A’s memory. Call it a note that says slot 7.
Stored note
→ slot 7
Slot 7
Request A’s data
2. A crash skips the erase
A fatal error hits before PHP finishes setting up. The general cleanup frees request A’s memory, but the step that should erase the upload-progress note is skipped. The worker lives on, holding a note that now points at freed — reusable — memory. A dangling pointer.
Stored note
→ slot 7 · dangling
Slot 7
Free / reusable
3. Request B reuses the slot
The next request to reach the same worker reads that stale note before normal setup resets it — meanwhile fresh data has moved into slot 7. PHP trusts whatever is there now as its own bookkeeping. This is a use-after-free, and it’s the memory-corruption stage.
Stored note
→ slot 7 · old reference
Slot 7
Request B’s data
How that becomes code execution
If the attacker gets their own bytes into the reused slot, PHP reads them as trusted counts and addresses. With enough control they steer what PHP runs next, into their own code — again with the worker’s permissions. It depends on the PHP build and on reaching the same worker, so a crash plus one more request isn’t enough on its own.
One flaw, several routes
Upload-progress is the clearest path, but the same stale state survives a crash pattern also shows up via PHP’s unserializer, fatal-error backtraces and OPcache. On PHP-FPM the startup route doesn’t apply — it stops the worker on a startup failure — but a crash after startup can still leave a reusable worker.
From a POST request to exploitation
How you stay protected
Patch, mitigate until the patch lands, then hunt for anyone who got in first.
1. Patch and restart
- Install a fixed build — 8.5.12, 8.4.27, 8.3.36, 8.2.35 or a vendor backport.
- Restart every worker and container — running processes keep the old code.
- Check the PHP that serves the site — command-line PHP may be a different install.
2. Mitigate until patched
Set in server config (not ini_set()) and restart workers. Partial — buys time, doesn’t replace the patch.
PHP configuration · partial coverage
; Apply in startup/server configuration, then restart workers.
session.upload_progress.enabled = 0
; PHP 8.5: mitigation for the confirmed backtrace variant.
fatal_error_backtraces = 0
Also keep WAF rules on and cap request size at the proxy. Leave upload progress off until the next bugfix release (related: CVE-2026-115560).
3. Hunt for exploitation
A hit is a lead, not proof — baseline before alerting.
A web server spawns a shell or recon tool
KQL · Defender XDR · DeviceProcessEvents
// Rule authors: Florian Roth, Nasreddine Bencherchali (Nextron Systems)
DeviceProcessEvents
| where ((InitiatingProcessFolderPath endswith "/apache" or InitiatingProcessFolderPath endswith "/apache2" or InitiatingProcessFolderPath endswith "/caddy"
or InitiatingProcessFolderPath endswith "/gunicorn" or InitiatingProcessFolderPath endswith "/httpd"
or InitiatingProcessFolderPath endswith "/lighttpd" or InitiatingProcessFolderPath endswith "/nginx"
or InitiatingProcessFolderPath endswith "/node" or InitiatingProcessFolderPath endswith "/nodejs"
or InitiatingProcessFolderPath endswith "/passenger" or InitiatingProcessFolderPath endswith "/php-cgi"
or InitiatingProcessFolderPath endswith "/php-fpm" or InitiatingProcessFolderPath endswith "/tomcat"
or InitiatingProcessFolderPath endswith "/unicorn" or InitiatingProcessFolderPath endswith "/uwsgi")
or (InitiatingProcessCommandLine contains "/bin/java" and InitiatingProcessCommandLine contains "tomcat")
or (InitiatingProcessCommandLine contains "/bin/java" and InitiatingProcessCommandLine contains "websphere"))
and (((FolderPath endswith "/ash" or FolderPath endswith "/bash" or FolderPath endswith "/busybox" or FolderPath endswith "/csh" or FolderPath endswith "/dash"
or FolderPath endswith "/ksh" or FolderPath endswith "/sh" or FolderPath endswith "/tcsh" or FolderPath endswith "/zsh")
and (ProcessCommandLine contains "-c \"id" or ProcessCommandLine contains ";id" or ProcessCommandLine contains "cat /etc/"
or ProcessCommandLine contains "crontab" or ProcessCommandLine contains "curl " or ProcessCommandLine contains "hostname"
or ProcessCommandLine contains "id;" or ProcessCommandLine contains "id\"" or ProcessCommandLine contains "ifconfig"
or ProcessCommandLine contains "iptables" or ProcessCommandLine contains "perl" or ProcessCommandLine contains "python"
or ProcessCommandLine contains "ruby" or ProcessCommandLine contains "uname" or ProcessCommandLine contains "wget "
or ProcessCommandLine contains "whoami"))
or ((FolderPath endswith "/bin/cat" or FolderPath endswith "/bin/crontab" or FolderPath endswith "/uname" or FolderPath endswith "/curl"
or FolderPath endswith "/hostname" or FolderPath endswith "/ifconfig" or FolderPath endswith "/ip" or FolderPath endswith "/iptables"
or FolderPath endswith "/netstat" or FolderPath endswith "/pwd" or FolderPath endswith "/route" or FolderPath endswith "/wget"
or FolderPath endswith "/whoami" or FolderPath endswith "/perl" or FolderPath endswith "/ruby") or FolderPath contains "/python"))
and (not((InitiatingProcessFolderPath endswith "/node" and ProcessCommandLine contains "ip neigh show")))
PHP memory-corruption error patterns
KQL · Sentinel · web / PHP error log
Syslog
| where TimeGenerated > ago(7d)
// Rule author: Swachchhanda Shrawan Poudel (Nextron Systems). Point at your web/PHP error log.
| where (SyslogMessage has "PHP Fatal error" and SyslogMessage has "Allowed memory size of"
and SyslogMessage has "bytes exhausted"
and (SyslogMessage has "rfc1867.c" or SyslogMessage has "in Unknown on line"))
or (SyslogMessage has "PHP Warning" and SyslogMessage has "open("
and SyslogMessage has "/ndary=" and SyslogMessage has "sess_" and SyslogMessage has "failed")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
Apache worker segmentation faults
KQL · Sentinel · Apache error log
Syslog
| where TimeGenerated > ago(7d)
// Rule author: Florian Roth (Nextron Systems). Point at the Apache error log.
| where SyslogMessage has "exit signal Segmentation fault"
| project TimeGenerated, Computer, ProcessName, SyslogMessage
4. With us, you are protected
Two Conifers platforms work together: threat hunting finds the attack in your data, and detection engineering turns an emerging attack into a detection rule — and when it fires, our agent investigates and protects. Neither waits for a CVE.
ResultProtected and alerted — now and next time.
Threat intelligence
Spots the threat early
- Recognizes emerging threats and campaignsContinuously monitors advisories, vendor research and threat feeds to identify new threats and malicious campaigns as they surface.
- Extracts what mattersTurns each report into campaigns, CVEs, indicators and ATT&CK-mapped behaviors.
- Scores it for youMatches the threat to your environment — internet-facing PHP servers make PHP2Shell a priority.
- Keeps indicators freshRe-checks indicators for as long as they stay useful, so late hits still surface.
Threat hunting
Finds the behavior in your data
- Knows where to lookMaps your internet-facing PHP servers and which ones send process and Syslog telemetry.
- Hunts behavior, not just indicatorsATT&CK-mapped behavior queries run on your Sentinel — a web server spawning a shell, PHP crash bursts. No CVE needed.
- Hunts new indicatorsAs threat intelligence publishes PHP2Shell scanner IPs, user-agents and CVEs, they’re hunted and enriched automatically.
- Connects the hitsThe hunt agent pivots on the server — probe → crash → shell → outbound — and returns a verdict.
Detection engineering
From emerging attack to protection
- Recognizes the upcoming attackSpots the new technique early — a web server crashing under odd uploads, then spawning a shell.
- Generates a detection ruleWrites a rule for that behavior, built for whichever SIEM or EDR you run.
- The rule alertsIt fires the moment an exploitation attempt hits one of your servers.
- Our agent investigates and protectsChecks whether the attempt succeeded, then contains it — isolate the server, block the source, preserve evidence.
Before the CVE
We catch the attack by its behavior and have a detection live — before it even has a name.
After the CVE
We hunt the new indicators back through your logs, and every alert activates our agent to protect you.
The practical takeaway
Patch, restart, verify. Then hunt behavior and turn it into a rule — a crash burst followed by a shell on one server is an attack, CVE or not.
Sources
Current to 25 October 2026. The list of organizations offline comes from press reports.
- W3Techs — PHP usage statistics
- W3Techs — server-side language trends
- VulnCheck — PHP2Shell vulnerabilities
- eBuilder Security — Government websites offline across Europe
- IBTimes — Websites go dark worldwide
- The Register — UK government services offline
- iTWire — cyber.gov.au and government sites offline
- heise — Websites go offline
- CNN — Websites shut down globally
- Newsweek — Organizations scramble over PHP2Shell
- Engineering.com — CISA on hidden PHP
- PPC Land — Cloudflare’s php2shell protections
- Coolify — WAF bypass via direct origin traffic