On 21 October 2026 the PHP project shipped emergency fixes for PHP2Shell, two critical flaws in the PHP runtime (CVE-2026-109618 and CVE-2026-109619). An attacker can trigger them without logging in. On some setups the request does its damage before any of your application code runs, so your input validation, your framework and your code review never see it. Since then, government portals and big public sites have gone offline while their teams patch.
A threat model would not have found this bug. Nobody draws a data flow diagram and spots a memory-safety flaw in PHP's error handling. What a threat model does decide is everything after the bug: how fast you find every place PHP runs, which of those to patch first, and how much an attacker gets from the servers you haven't patched yet. The same applies to the PHP you don't run yourself: the help desk, the payment plugin and the hosting company that hold your data on their PHP servers. This post shows how, using one small attack tree, a five-system estate and the third parties around it as the example.
The short version: patch first. PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 contain the fixes, and no configuration change fully blocks the attack. Then use your threat model for the rest of the work. Its data flow diagram tells you where PHP runs. Its trust boundaries show what each PHP worker can reach. Its attack trees show which other controls still stop an attacker who gets code execution. Rank your systems by exposure and reach, not by the CVSS score, which is the same 9.2 for all of them. Ask the vendors that run PHP for you the same questions, and treat any vendor that can't answer as a risk in your own register. Record each temporary measure as a treatment with an owner, and add "the runtime itself is compromised" to the model as a standing leaf.
What PHP2Shell is
Here is what was public on 22 October 2026. Details are still changing, so check the PHP project's own advisory before acting on any of it.
- Two flaws, one cause. Both happen when a request ends abnormally, for example when it hits
memory_limitor a fatal error, and PHP leaves internal memory in a bad state. CVE-2026-109618 is memory corruption on out-of-memory. CVE-2026-109619 is stale global state that a later request handled by the same worker can reuse after it was freed. The PHP project scores both 9.2 under CVSS v4.0. - Every way of serving PHP is affected. That includes PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. PHP rates mod_php highest, because a demonstrated exploit fires from one unauthenticated request before application code runs. Researchers report that several of the demonstrated techniques abuse session upload progress, a feature that writes to the session while a multipart upload is still being parsed.
- Fixed versions. 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP also published minimal source backports for 7.4, 8.0 and 8.1, which are end of life and get no official builds. PHP 5 gets nothing. Linux distributions and long-term support vendors had patches before the public release, so check yours.
- It was used before disclosure. Amazon's honeypots recorded an exploitation attempt on 17 October, four days before the patches. Cloudflare says it saw early attacks and has deployed temporary WAF rules. A public proof-of-concept is now circulating.
- Temporary measures are partial. PHP's published workarounds include
session.upload_progress.enabled=0, a lowpost_max_size, and on PHP 8.5,fatal_error_backtraces=0. A PHP core developer has said none of them fully blocks the attack, so they buy time and nothing more.
Sources: Help Net Security, BleepingComputer and Imperva's technical write-up.
What a threat model can't do about it
Threat modeling works at the level of design: components, data flows, trust boundaries and the things that can go wrong between them. PHP2Shell is a bug in code you didn't write, in a runtime nearly every PHP shop treats as a fixed part of the platform. Until 20 October, nobody outside the people who found it knew it existed. For defenders it was a zero-day, and no design review predicts a specific zero-day.
What a design review can do is assume that some component will fail one day, without knowing how, and decide in advance what that failure is allowed to cost. Teams that made that assumption about their web tier are having a much calmer week than teams that didn't. The rest of this post covers the six things that assumption gives you, the last of them about systems you don't run at all.
1. An inventory that includes the forgotten servers
The first question in any runtime emergency is "where do we run it?", and it takes longest to answer for the systems nobody thinks about. The marketing site built by an agency years ago. A support tool still on PHP 7.4. A vendor appliance with PHP inside its admin page. Hal Pomeranz expects a long tail for exactly this reason: embedded installations and applications nobody remembers.
A data flow diagram kept up to date is that inventory, organised by what matters. Every process on it is something that runs, and in ThreatTree each process has a Technology field. Write the runtime and how it's served there, such as PHP 8.3 / PHP-FPM or PHP 7.4 / mod_php, and "which of our systems run mod_php?" becomes a question you answer by reading diagrams, not by scanning the network at midnight. A scan is still worth running, because it finds what the diagrams missed. Every system the scan finds and no diagram shows is a gap in your model, not just a server to patch.
2. An attack tree where PHP2Shell is just one leaf
Here's the attack tree most PHP applications should already have. The goal isn't "exploit PHP". It's what the attacker actually wants: data. Getting there needs three things, so the root is an AND gate.
Read it from the top. PHP2Shell sits under Run code as the PHP worker, next to the ways that have always existed: a vulnerable WordPress plugin, an upload form that lets someone put a .php file in the web root. Patching PHP2Shell takes away one way in, and only on the servers you've patched. The other two branches don't care how the attacker got code execution. If the worker can't read a database password, and can't reach the internet except through an allowlist, code execution on a web server is a serious incident but not a data breach.
That's defence in depth, and an attack tree is where you can see whether you actually have it. A tree where every path runs through "the attacker gets code execution" and then reaches the data with nothing in between has a single point of failure. This week, that single point of failure is a runtime bug you can't fix any faster than your patch process allows.
3. Trust boundaries that show the blast radius
Code execution from PHP2Shell runs with the privileges of the PHP worker, usually www-data or a pool user, not root. So how bad it is depends entirely on what that user can reach. A threat model makes you write that down: the trust boundaries around the web tier and every flow that crosses them.
For each PHP process, trace the flows outward and ask what a stranger running code there could do with each one:
- Secrets on disk or in the environment. A Laravel
.env, a WordPresswp-config.php, API keys in config files. All readable by the worker, because the application needs them. - The database user's rights. An application that connects as a user who owns every schema hands every schema to whoever owns the worker.
- Cloud credentials. On a cloud VM, the instance metadata service hands out the role's credentials to any local process. IMDSv2 with a hop limit of 1 helps against some forwarding tricks, but code running on the host can still ask for the token. What protects you is how much the role is allowed to do.
- Other workers and other sites. PHP2Shell's second flaw works through state shared between requests on the same worker. On shared hosting, or a server running several applications in one PHP pool, another site's request can land on the same worker.
- Outbound traffic. A web server that can open a connection to anywhere on the internet makes it easy to get data out and to download the next stage.
Each answer is a leaf in the tree above, scored. Together they're the honest answer to the question your leadership is asking this week: "if we're hit before we patch, what do we lose?"
4. A patch order based on your systems, not the CVSS score
Every vulnerable PHP install gets the same CVSS score, 9.2. That's correct for the vulnerability and useless for deciding which of your servers to patch first. As we argue in the CVSS trap, CVSS measures the flaw. Your threat model measures what the flaw means in your system. Here is a typical mid-size company's PHP estate, scored with a 5×5 risk matrix. Likelihood is driven by exposure and how PHP is served. Impact is driven by what the worker can reach:
| System | How it runs | Exposure | What the worker can reach | L × I | Action |
|---|---|---|---|---|---|
| Legacy support desk | PHP 7.4, mod_php | Internet | Ticket database with customer messages and attachments | 5 × 4 = 20 | Take offline today; bring back on 8.x or a backport build |
| Customer portal | PHP 8.3, PHP-FPM behind nginx | Internet | Customer database, payment provider API key in .env, document bucket | 4 × 5 = 20 | Patch first; temporary measures and WAF rules until done |
| Marketing site | WordPress, PHP 8.2, mod_php | Internet | Its own WordPress database; nothing else | 4 × 2 = 8 | Patch today; low impact because it's isolated |
| Vendor appliance web UI | Embedded PHP, unknown version | Office network only | The appliance's own configuration and admin credentials | 3 × 3 = 9 | Restrict access to the admin subnet; chase the vendor |
| Internal admin tool | PHP 8.4, php-cgi | VPN only | Read-write access to the customer database | 2 × 5 = 10 | Patch this week |
Two rows show why the context matters. The marketing site is internet-facing and runs mod_php, the setup PHP rates highest, yet it scores low because a breach there reaches nothing else. That holds only because someone gave it its own database and its own host, and the threat model is where that decision was recorded. The internal admin tool is hard to reach but would hand over the customer database, so it moves ahead of anything where only the likelihood is high. Without the model you'd patch in the order of whatever the scanner listed first.
5. Temporary measures with an owner and an end date
Not everything can be patched today. The vendor appliance waits on its vendor. A regulated system may need a change window. The workarounds are partial by PHP's own account. That's exactly when risk decisions get made in a chat thread and forgotten a month later, when the WAF rule is still the only thing between a legacy server and the internet.
Record each temporary measure the way you'd record any compensating control: against the risk it covers, with an owner and the date it ends. For the customer portal:
| Treatment | Type | Covers | Ends |
|---|---|---|---|
| Upgrade to PHP 8.3.36 and restart every FPM pool | Mitigate | The PHP2Shell leaf itself | Permanent |
session.upload_progress.enabled=0, post_max_size=8M, workers restarted | Mitigate (partial) | The known exploit paths, not all of them | Removed after the upgrade is verified |
| Cloudflare's PHP2Shell WAF rules turned on | Mitigate (partial) | Known exploit patterns only | Reviewed after the upgrade |
Move the payment API key from .env to a secrets manager, read at boot by a separate role | Mitigate | The reach the data branch | Permanent |
| Outbound allowlist for the web tier: payment provider, mail relay, package mirror | Mitigate | The get the data out branch | Permanent |
The last two rows are the ones that matter after PHP2Shell is forgotten. They do nothing about this bug, but they reduce the damage from the next bug, whatever it turns out to be. Then re-score: once the upgrade lands and the two structural fixes ship, the portal's leaf under Run code as the PHP worker goes down, and more importantly, the branches after it stop the attack on their own.
6. Third parties: the PHP you don't run
Your own servers are only half of your exposure. PHP runs a large share of the web, so some of the companies you send data to almost certainly run it too: a hosted help desk or CRM, a managed WordPress host, an e-commerce or payment plugin, a forms or survey tool, the agency that maintains your marketing site. You can't patch their servers, and you can't see their patch order. But a breach on their side is still your customers' data leaking, and often your incident to report.
A threat model already has a place for them. On a data flow diagram, every vendor is an external entity, and every flow to it is labelled with what it carries. That gives you the third-party list for this incident in minutes, ranked by what matters: which vendors receive personal data, which hold credentials into your systems, and which can push code or content back into your site.
Third parties add three leaves to the attack tree above. Each one gets scored like any other:
- A vendor holding your data is breached. Your customer records sit in their database, behind their PHP worker. Impact is set by what you send them, likelihood by what they tell you, or don't tell you, about their patch status.
- A vendor's credentials into your systems are stolen. API keys, webhook secrets, OAuth tokens, or an SFTP account the vendor uses to sync data. An attacker on their server gets these, and with them a way into yours that no patch on your side closes.
- A vendor serves code to your users. A script, widget or plugin update loaded from a compromised vendor runs in your users' browsers or on your own PHP servers. This is the supply chain path, and it bypasses every control in front of your own application.
For each vendor on the diagram, the response is short:
| Question | Why it matters | If the answer is bad or missing |
|---|---|---|
| Do you run PHP anywhere that handles our data, and is it patched to a fixed version? | Sets the likelihood of the vendor-breach leaf | Raise the likelihood score and escalate through your contract's security contact |
| Did you see signs of exploitation before you patched, including before 21 October? | The flaw was used before disclosure, so "patched now" doesn't mean "not compromised" | Treat as possibly breached: start your own incident assessment |
| What credentials do you hold into our systems? | Each one is a path into your environment that their breach opens | Rotate them anyway once they confirm they've patched, and narrow their scope |
| Do you serve scripts, widgets or plugins that run on our site? | A compromised vendor can push malicious code to your users | Pin versions, add integrity checks, or remove the dependency until they confirm |
A vendor that doesn't answer within a few days is a risk decision, not an administrative delay. Record it in your register like any other: the vendor-breach leaf stays at a high likelihood, with an owner who decides whether to accept it, reduce what you send that vendor, or switch off the integration. After the incident, the useful change is structural. Send each vendor the minimum data it needs, give each one credentials scoped to exactly what it does, and keep the vendor list on your diagrams, not in a procurement spreadsheet nobody reads during an incident. Our guide to threat modeling third-party vendor integrations covers that in depth.
After the patch: update the model
An emergency patch is one of the events listed in when to re-threat model, and it's worth half an hour once things are calm:
- Add the leaf for good. "Runtime or framework flaw gives code execution as the worker" belongs under every web tier's foothold branch, permanently. Next time the name changes, but the leaf is already scored and its treatments are already written.
- Record what the scan found that the diagrams didn't. Each forgotten server goes onto a diagram or gets switched off.
- Check the detection you relied on. Researchers suggest correlating PHP memory-exhaustion errors and worker crashes with unusually large multipart requests or oversized session IDs. If you had to build that this week, add it to the model as a control so it isn't lost.
- Look for signs of compromise, not just missing patches. Code ran before disclosure, so a patched server may already be compromised. The UK NCSC has warned specifically about backdoors. Check for unexpected child processes, changed application files and unusual outbound connections, and rotate any secrets a compromised worker could have read.
A checklist for this week
- List every PHP runtime from your diagrams, then confirm with a scan. Add anything the scan finds to a diagram.
- For each one, write down how it's served (mod_php, FPM, CGI, FrankenPHP, LiteSpeed), whether it's internet-facing, and what its worker can reach.
- Rank by likelihood × impact. Take offline what you can't patch and can't isolate.
- Patch to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your vendor's backport, and restart every worker.
- Apply temporary measures where patching waits, each recorded with an owner and an end date.
- Close one branch after the foothold on your most exposed systems: secrets out of the web root, a least-privilege database user, an outbound allowlist.
- Hunt for compromise on anything that was exposed before you patched, and rotate what it could read.
- Send every vendor on your diagrams that receives sensitive data or holds credentials the four questions above. Rotate the credentials they hold, and record vendors who don't answer as risks with an owner.
Frequently asked questions
Could threat modeling have prevented PHP2Shell?
Not the bug itself. It's a memory-safety flaw in the PHP runtime, and design-level threat modeling doesn't find those. What threat modeling decides is how much the bug costs you: how quickly you find every affected system, which ones to patch first, and whether an attacker who gets code execution on a web server can get any further.
How do I assess third-party risk from PHP2Shell?
Start from the external entities on your data flow diagrams: every vendor you send data to or give credentials to. Ask each one whether it runs PHP on systems that handle your data, whether it has patched, whether it saw exploitation before patching, and what credentials it holds into your systems. Rotate those credentials once the vendor confirms it has patched, and record any vendor that can't answer as a risk with an owner.
Which PHP versions fix PHP2Shell?
PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released on 21 October 2026. PHP also published minimal source backports for 7.4, 8.0 and 8.1, which are end of life and get no official builds. Distribution and long-term support vendors have their own packaged fixes. PHP 5 gets nothing.
Is there a workaround if I can't patch PHP2Shell immediately?
Only partial ones. PHP's published measures include disabling session upload progress, lowering post_max_size and, on PHP 8.5, turning off fatal error backtraces, with workers restarted afterwards. WAF vendors including Cloudflare have rules for known exploit patterns. PHP says none of these fully blocks the attack. The UK NCSC advises taking systems offline if they can't be patched.
Does a WAF protect against PHP2Shell?
It reduces exposure to the exploit patterns the WAF vendor knows about, which makes it a reasonable temporary measure. It isn't a fix, because the flaw is in how PHP handles memory, and new ways to trigger it may not match existing rules. Treat it as a temporary measure with an owner and an end date.
How should I prioritise patching across many PHP servers?
By exposure and blast radius, since the CVSS score is the same everywhere. Internet-facing systems running mod_php come first for likelihood. Systems whose worker can reach sensitive data or broad cloud credentials come first for impact. A system with both goes to the top, and one you can neither patch nor isolate comes offline.