On 21 October, CISA published a recommendation nobody writes lightly: anyone unable to patch immediately should "take PHP-based systems offline until they can be patched or updated". Switch the website off. At least one European government did. The site you are reading also runs on PHP, so the warning landed at our door like everyone else's. What follows does not argue about whether to patch — you patch, full stop. It is about what happened in the two hours after one of those break-ins, because in those two hours PHP was no longer the thing failing.
The two hours
Palo Alto Networks' Unit 42 describes a specific case: a European government entity, compromised through these flaws. What the attackers did within the first two hours of initial exploitation was obtain password hashes and MFA secrets, stand up an NTLM-relay tunnel into Active Directory, and attempt to escalate to domain administrator. Two hours from "somebody got in through the website" to "somebody is trying to own the directory".
One detail in the same report deserves separate reading, and it is not decorative: the operators used agentic AI platforms during post-exploitation, with commands carrying delimiters such as ==SHELLUSER== so the output would be easy for a model to parse, in short bursts, serially spaced, with quick pivots after each failure. Put without drama: the slow part of an attack — trying things, reading output, deciding the next step — is precisely the part that automates best. If your response plan assumes there is a night of margin between the break-in and lateral movement, that assumption has just aged badly.
And now the question in this post's title, which is the one that actually interests us: why could a web server reach Active Directory at all? The PHP flaw did not open that. It was open beforehand.
What CVE-2026-109618 and CVE-2026-109619 are
Both advisories are published by the PHP project itself, both with a CVSS v4.0 score of 9.2 and the same vector CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. watchTowr christened them PHP2Shell and the name stuck; names patch nothing, but they help you search.
- CVE-2026-109618 — code execution through out-of-memory corruption. PHP uses
longjmp()when memory runs out. If allocation fails during request initialisation, the early exit leaves request state half-built, and the shutdown procedure then processes that corrupted memory. The attack vectors the advisory describes are multipart requests with no declared length that bypasspost_max_size, sections with unlimited headers that exhaustmemory_limit, and empty file controls that dodgemax_file_uploads. - CVE-2026-109619 — code execution through stale global state. State belonging to one request survives inside the worker process and is read by a later request, before normal initialisation resets it. The advisory says it plainly: "a strategically crafted heap layout can produce full remote code execution". It needs one operation that stores that state and another that consumes it.
An honest caveat, the kind rarely quoted because it costs headlines: that vector carries AC:H and AT:P — high complexity and attack conditions that have to be present. It is not one curl and you are in. That does not change the conclusion — patch anyway, and fast — but it does change the tone for the meeting: this is not instant magic, it is exploitation work with a very high payoff.
The timeline runs backwards
The usual order is: vendor patches, details are published, exploit appears. Here it ran differently. On 20 October the details leaked ahead of coordinated disclosure, with no patch yet in existence; watchTowr was among the first to publicly confirm the leak was genuine. That same day the PHP security team announced an emergency release for 21 October at 14:00 UTC. The fixed versions shipped on the 21st, and on that same 21st watchTowr's honeypot network observed exploitation of both flaws.
A tension between sources is worth saying out loud, because it will come up in any serious conversation about this. CISA, in its 21 October alert updated on the 23rd, states that it has not observed successful exploitation in the wild as of publication. Unit 42, meanwhile, documents the European government case with detailed post-exploitation, and watchTowr sees attempts on its honeypots. They are not necessarily contradictory — seeing attempts on a decoy is not the same as confirming a successful intrusion, and the observation windows differ — but we are not going to smooth them over either: nobody on the outside can resolve them, and whoever writes the patching policy should not wait for someone to. As of today, 24 October, incidentally, neither CVE appears in CISA's KEV catalogue. If your emergency process only fires on KEV, today is the day to notice that trigger arrives late.
The patch exists for four branches
The fixed versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Four branches. Not one more. And this is where the incident stops being a security story and becomes a technical-debt invoice, because according to W3Techs figures from 24 October 2026, of all sites using PHP, 26.8% run branch 7, 7.7% branch 5 and 0.1% still branch 4. That is at least 34.6 out of every 100 PHP sites on a branch with no patch and no prospect of one, and the bulk of that 26.8% is 7.4: 73.4% of the PHP 7 installs measured. We say "at least" because the real arithmetic is worse. Within the 65.4% running branch 8, 15.1% sit on 8.1 and 5.8% on 8.0, both unsupported: nearly 14 points more. Added up, close to half the PHP estate has no fixed version to install.
This is the shape we meet most often, and one we wrote about at the time: your patching policy has no entry for "no patch". The whole procedure assumes a package exists to install. When it does not, the document goes quiet and the decision escalates to management in the heat of a Wednesday, with the clock running. Writing that branch of the procedure on a quiet day takes an hour.
Updating the package is not the same as having patched
The second flaw lives in the state a worker keeps between requests; the first, in the initialisation of a single one. But both share a practical consequence everyone forgets: while the old workers remain in memory they are running the old code, so they stay vulnerable even though the new package has been on disk for half an hour. The CVE-2026-109619 advisory repeats it in both of the configuration mitigations it proposes — apply the setting and restart the workers. If you run a fleet, the follow-up check is not "what version does the package say", it is what version is the process answering port 443 actually running, and how long it has been alive.
Exposure is not uniform either, and the two advisories are worth reading separately because they do not say the same thing. In the first, with Apache and mod_php it is confirmed: shutdown is invoked even after a failed startup, and that is where code execution lives; with PHP-FPM the child exits on startup failure, but a later out-of-memory condition remains potentially exploitable, and FrankenPHP is likely susceptible by analogy with mod_php. In the second, the confirmed case is mod_php again — through fatal error backtraces and session upload progress — while FPM is conditionally exposed: when the front end passes it PHP_VALUE or PHP_ADMIN_VALUE parameters that produce a diagnostic before request initialisation. Of FrankenPHP it says no equivalent early consumer was established in its default configuration, and adds, with a candour we appreciate, that this does not make it immune; LSAPI is listed as a conditional candidate. Which server you run stops being an administrator's preference and becomes an input to the risk assessment, much as your php.ini decides the real exposure when we talk about WordPress.
The mitigations that do not mitigate
Here is a gem worth a minute. The temporary mitigation proposed in the CVE-2026-109618 advisory is to set memory_limit=-1: remove the memory limit. Precisely the setting we have spent fifteen years recommending people tighten. The reason is that the predictable per-request limit is what makes exploitation convenient, and without it the attacker loses their clock. And it is not enough: the advisory says so in those words — "this is not a complete mitigation", because if mmap() or another low-level allocation fails, PHP can still use longjmp() while incomplete request memory is reachable. Updating is, literally, "the only complete mitigation". The same advisory also asks for a conservative post_max_size at the front end, a lower max_input_nesting_level and bounds on variables, parts, headers and uploads. And here is what finishes the lesson: it says not to use RLIMIT_AS or RLIMIT_DATA, because they make mmap() fail once the limit is reached and trigger the vulnerable path. Two controls any audit would mark as good, playing for the attacker. Hardening is not good in the abstract; it is good against a specific attack, and sometimes it plays against you.
For the second flaw the advisory offers fatal_error_backtraces=0 on PHP 8.5 and session.upload_progress.enabled=0, both with a worker restart, and warns that each covers only the demonstrated case. Then comes the sentence that should end the debate in any committee: "there is no complete generic configuration mitigation", because different application or extension paths may produce or consume stale state unpredictably. Translated: if somebody proposes holding on with settings until the maintenance window three weeks from now, the vendor has already answered that one.
Where PHP runs without you knowing
Palo Alto's Cortex Xpanse put the number of exposed, potentially vulnerable instances at over 22.6 million when it published its report. That figure does not come from the web servers you and I have written down on a sheet: it comes from everything else. A hosting control panel. A legacy ticketing system nobody has touched since the person who built it left. The web interface of a storage array, a UPS, a departmental printer. A container built on a 2021 base image that only gets rebuilt when somebody changes the code, and the code has not been touched in two years. That is the inventory deciding your real exposure, and it almost never matches the official one.
Why a website reaches the directory
Back to the two hours, which is the part that actually needs fixing. For a compromised web server to pull domain hashes and stand up an NTLM relay, things have to be true that are not vulnerabilities: they are decisions. In what we come across, it is nearly always these three. One, the front end is joined to the domain — usually so people can sign in with their usual credentials, which is a good reason with a bad consequence. Two, the front end shares a segment with internal servers, so it can reach the domain controller on 445 and 389 without anyone having explicitly authorised it. Three, there are credentials stored in plain text in a configuration file that the web process can read, because reading them is literally its job.
None of the three shows up in a vulnerability scanner. All three are why hour two was far worse than hour zero. And all three are fixed with boring architecture work: an unprivileged service account in the directory, a dedicated segment for the front end with explicit egress rules to the ports it genuinely needs, and secrets kept off the web server's disk. It is not glamorous and it never triggers a red alert. It is, precisely, the difference between a fault and an outage.
When NOT to switch the website off
CISA's advice is correct and well aimed, and worth reading in full before repeating it in a meeting, because it has two addressees: those who cannot patch immediately and, verbatim, those who continue to use PHP 8.1 or earlier unsupported versions. That second group is the 20.9% of branch 8 we just counted, and for them this is not temporary advice: until they change branch, there is no patch to wait for. What the sentence is not is a blanket recommendation to shut things down just in case. Disconnecting carries its own bill: a shop that is down sells nothing, a downed customer portal generates calls that also cost money, and if the blackout drags on, shortcuts appear — "just email me the order" — that open worse holes than the one you were closing. The way we decide is to look at two things before the red button: if you can patch in under an hour, patch and switch nothing off; if you cannot, switch off what is internet-facing and not needed to invoice today, which is almost never everything. Between "all on" and "all off" sits an inventory with priorities, and that inventory gets written beforehand, not at nine in the evening on a Wednesday.
The twenty-minute check
Seven steps, in this order. The first four are for today; the last three are the ones that prevent hour two of the next flaw, which will arrive under a different name.
- Ask the version that serves requests, not the package. A page calling
phpversion()behind authentication, orphp-fpm -von the server, tell the truth;php -von the command line is sometimes a different binary. If it does not start with 8.5.12, 8.4.27, 8.3.36 or 8.2.35, you are not done. - Restart the workers and verify they actually died.
systemctl restart php*-fpm, then check process age: if they have been alive since before the patch, they are still vulnerable. With mod_php, the thing to restart is Apache. - Sweep your ranges before trusting the inventory. One pass looking for
X-Powered-Byheaders,PHPSESSIDcookies and.phppaths on everything that listens, management networks included. Whatever turns up that was not on the spreadsheet is precisely what nobody is going to patch. - If your branch has no patch, decide today and write it down. On 8.1 or older no minor update saves you: either you change branch — and that gets tested, because it breaks things — or you isolate that system from the internet in the meantime. What does not work is leaving it hanging and calling it "pending assessment".
- Check which account your web server runs as in the domain. If it is a domain account, review what it can do: group membership, read permissions in the directory, whether it can authenticate against other servers. Anything it does not need, remove.
- Check what your front end can reach inwards. From the web server itself, try opening 445 and 389 on a domain controller. If they connect, you have just reproduced the first step of lateral movement with your own hands on a quiet day. Egress rules on that segment are the fix.
- Decide who is watching the web server at three in the morning. A two-hour margin is not covered by a weekly report. Either somebody is on call with the ability to isolate the machine, or the plan is to find out on Thursday. Both options are legitimate; only one of them is usually written down.
Where this fits in what we do
We maintain other people's infrastructure, and this site runs on PHP: Wednesday the 21st was a working day for us just as it was for anyone reading this. What we put into cybersecurity is not a product that plugs PHP holes — no such thing exists — it is the boring part: knowing which version runs where, being able to patch a fleet the same day without breaking anything, and making sure the web front end is not a neighbour holding a key to the directory. The infrastructure and cloud layer is where the second part gets decided, and it gets decided on design day, not on incident day.
If you want us to run step 3's sweep and step 6's test with you — what PHP you actually have and what your front end can reach inwards — let us talk. It takes half a morning. If it turns out everything was patched and properly segmented, we will say so and that is that.
Sources. The identifiers, the CVSS v4.0 score of 9.2, the vector, the affected and fixed versions (8.2.35, 8.3.36, 8.4.27 and 8.5.12), the technical description of both flaws, SAPI susceptibility (mod_php confirmed; PHP-FPM conditional with confirmed execution via the session lifecycle; FrankenPHP likely; LSAPI a conditional candidate), the temporary mitigations and their limits (memory_limit=-1 and the warning that "this is not a complete mitigation"; updating as "the only complete mitigation"; a conservative post_max_size, max_input_nesting_level and the warning NOT to use RLIMIT_AS or RLIMIT_DATA; fatal_error_backtraces=0 and session.upload_progress.enabled=0 with a worker restart) and the sentence about there being no complete generic configuration mitigation come from the advisories published by the PHP project: GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619). The recommendation to update or take PHP-based systems off the network — aimed verbatim at those who cannot update immediately "or that continue to use PHP 8.1 or earlier unsupported versions" — the statement that successful exploitation had not been observed as of publication, and the check that neither CVE appears in the KEV catalogue (version 2026.10.23), are in CISA's alert of 21 October 2026, updated on the 23rd. The European government entity case (password hashes and MFA secrets, an NTLM-relay tunnel into Active Directory and an attempted escalation to domain administrator within the first two hours; the use of agentic AI platforms and delimiters such as ==SHELLUSER==), the figure of over 22.6 million exposed instances measured by Cortex Xpanse telemetry — a vendor's own telemetry, just as W3Techs is a sample — and the fact that at least one European government took its website offline as a precaution, are in Palo Alto Networks Unit 42's report. The timeline of the 20 October leak ahead of coordinated disclosure, the announcement of the emergency release for the 21st at 14:00 UTC and the observation of exploitation on their honeypot network are in watchTowr's analysis. Support and end-of-life dates per branch come from php.net/supported-versions and php.net/eol. The usage percentages per branch (65.4% on 8, 26.8% on 7, 7.7% on 5, 0.1% on 4; 73.4% on 7.4 within branch 7; and 15.1% on 8.1 and 5.8% on 8.0 within branch 8), measured on 24 October 2026 across sites using PHP, are from W3Techs; like any such measurement it depends on its sample and is best read as an order of magnitude. Translations of the quotations are ours. The following are our own judgement and not claims by any of those sources: reading the two hours as an architecture problem rather than a PHP one, the three usual reasons a front end can reach the directory, the caveat about the vector's high complexity, how to decide whether to switch things off, and the twenty-minute check. everyWAN provides managed infrastructure and security services, and this site runs on PHP: the conflict of interest is declared. Cover photo: wall-mounted comms cabinet with switch, router and modem, public domain (CC0), WordPress Photos media library.