On 21 October 2026 the PHP project shipped 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to close two unauthenticated remote-code-execution bugs, CVE-2026-109618 and CVE-2026-109619, both scored 9.2 on CVSS v4.0 and now known as PHP2Shell. The details had leaked the day before. Cloudflare had rules blocking exploit attempts live at 03:02 UTC, about nine hours before the public release, and reported attackers mutating payloads against those rules before anyone outside the embargo was supposed to know the bug existed. CERT-SE published its advisory at 14:00 the same afternoon. Azure App Service on Linux finished rolling patched built-in PHP images to its Latest and Standard release channels on the same day. That last sentence is the good news, and it covers a smaller share of the PHP running in Azure than most teams assume.
What the two bugs are
Both flaws sit in the PHP engine, not in any framework or application. The PHP advisory describes CVE-2026-109618 as a case where "out-of-memory handling can interrupt PHP while internal request data is only partially initialized. Request cleanup may then treat the invalid data as valid." CVE-2026-109619 is the stale-state variant: "request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it." An attacker needs nothing more than the ability to send HTTP requests to a PHP endpoint.
Every SAPI that serves web requests is in scope: PHP-FPM, Apache mod_php, php-cgi, LiteSpeed and FrankenPHP. The PHP team and watchTowr both single out mod_php as the highest-risk configuration, because the attack can succeed before any of the application's own code has run. There is no complete configuration workaround. The advisory's wording is "there are no complete mitigations, updating is the only safe choice." The partial measures it lists, such as disabling session.upload_progress, lowering post_max_size and, on PHP 8.5, turning off fatal_error_backtraces, narrow specific paths and nothing more.
The fixed versions cover the four supported branches only. PHP 8.1, 8.0, 7.x and 5.x have no official release. For Debian and Ubuntu, Ondřej Surý's packaging repository carries backports down to PHP 5.6, and that repository is the realistic path for a VM you cannot upgrade this week. The exposure is large. CybelAngel's analysis of Shodan banner data from 20 October counted 2.24 million internet-facing hosts advertising PHP, and of the roughly 1.5 million that report a version, 59 percent run a branch with no official fix.
| When (UTC) | What happened |
|---|---|
| 20 Oct | Vulnerability details leak ahead of coordinated disclosure; watchTowr confirms they are genuine |
| 21 Oct, 03:02 | Cloudflare managed rules go live on all plans, including Free; attack traffic already observed |
| 21 Oct, ~13:30 | PHP releases 8.5.12, 8.4.27, 8.3.36, 8.2.35 with the full advisory |
| 21 Oct, 14:00 | CERT-SE advisory published; App Service Linux rollout to Latest and Standard channels complete; Microsoft publishes Azure WAF guidance |
| 21 Oct | watchTowr reports in-the-wild exploitation |
| by 28 Oct | Azure Front Door managed rule 99006001 expected to finish rolling out (disabled by default) |
Where PHP actually runs in an Azure estate
The reason "App Service is patched" is not the end of the story is that PHP arrives in Azure through at least five doors, and Microsoft only holds the key to one of them.
- App Service on Linux, built-in runtime. Microsoft patches this. The 21 October rollout updated the images for 8.2, 8.3, 8.4 and 8.5. The catch is the Platform Release Channel: apps on Latest got it first, Standard followed under an accelerated safe-deployment rollout, and Extended receives it last. Microsoft's advice for Extended-channel apps is to move to Standard or Latest now rather than wait.
- App Service on Windows, built-in runtime. Not patched, and not going to be. Microsoft's statement is unambiguous: "The built-in PHP runtimes on App Service for Windows are end-of-life (EOL). No security patch for these runtimes is currently planned." If you have a legacy intranet app or a WordPress site still on a Windows plan, you are running an exploitable PHP with a Microsoft logo on the portal page.
- Custom containers, on App Service, Container Apps or AKS. Microsoft is explicit: "If you run PHP in a custom container, or bring your own PHP runtime, App Service does not patch it." Nothing on the host side helps; the vulnerable binary is inside your image until you rebuild.
- Virtual machines and scale sets. Whatever the distribution's package manager says. Ubuntu LTS and Debian stable carry fixes through their security channels; anything on 8.1 or older needs the Surý backports or an upgrade. Marketplace images with a bundled stack, such as LAMP or WordPress appliances, count here too.
- Things you forgot were PHP. Monitoring front-ends, a Matomo instance, an internal wiki, the partner portal that a vendor delivered as a Docker image two years ago. Fortinet is still listing several products as under investigation in the NCSC-NL tracker. Your suppliers are in the same position.
The audit
Work subscription by subscription. The order matters: inventory before verification, verification before WAF work, because the WAF step is the one teams are most tempted to do first and then stop.
1. Find every PHP site and image
Azure Resource Graph answers the App Service question across every subscription you can read in one query. Linux apps expose the runtime in linuxFxVersion; Windows apps carry it in phpVersion, and any non-empty value there is a finding in itself.
# App Service: every site whose stack is PHP, Linux and Windows
az graph query -q "
resources
| where type =~ 'microsoft.web/sites'
| extend linuxFx = tostring(properties.siteConfig.linuxFxVersion),
winPhp = tostring(properties.siteConfig.phpVersion)
| where linuxFx startswith 'PHP' or isnotempty(winPhp) or linuxFx startswith 'DOCKER'
| project name, resourceGroup, location, kind, linuxFx, winPhp
" -o table
# Per app, when the graph view is stale
az webapp config show -n <app> -g <rg> --query "{linux:linuxFxVersion, winPhp:phpVersion}"
# Container Apps and AKS: which images are running right now
az containerapp list --query "[].{app:name,image:properties.template.containers[0].image}" -o table
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | grep -iE 'php|wordpress|laravel|drupal|nextcloud|matomo'
# ACR: repositories that look like PHP stacks
az acr repository list -n <registry> -o tsv | grep -iE 'php|wordpress|laravel|drupal'
For VMs, Azure Update Manager or Defender for Cloud's software inventory will list the php package where the agent is installed. Where it is not, a one-line check over SSH beats guessing.
2. Verify the version that is serving requests
A stack setting of "PHP 8.3" tells you the branch, not the patch level. Microsoft's own verification step is to open an SSH session to the app and run php --version. Do this for at least one app per plan and per release channel, and do it again for any app on the Extended channel.
# App Service Linux: open the web SSH console or use the CLI
az webapp ssh -n <app> -g <rg>
php --version # expect 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or newer
# Container image, without starting the app
docker run --rm --entrypoint php <registry>.azurecr.io/<repo>:<tag> --version
# VM
php --version && (apt list --installed 2>/dev/null | grep -E '^php[0-9.]*-(fpm|cli)' || rpm -q php-fpm php-cli)
Check the Platform Release Channel in the portal under the app's stack settings. If an app on Standard or Latest still reports an old version, Microsoft asks you to open a support case with the app name, region, channel and the version output. Do not restart and hope.
3. Rebuild every custom image, and pin it
Rebuild from the official PHP images at the fixed versions, then redeploy. The App Service team adds one specific note for WordPress: Alpine-based WordPress images should move to Debian-based alternatives. While you are in the Dockerfile, pin the base image by digest. A floating 8.3-fpm tag is exactly the surface that let last week's LiteLLM compromise spread, and the same discipline closes both problems.
# Dockerfile: fixed version, then pin the digest once you have pulled it
FROM php:8.3.36-fpm-bookworm
# FROM php:8.3.36-fpm-bookworm@sha256:<digest-you-verified>
# After the rebuild, prove the running revision uses the new image
az containerapp revision list -n <app> -g <rg> \
--query "[].{rev:name,active:properties.active,image:properties.template.containers[0].image}" -o table
4. Decide what to do with Windows App Service PHP
There are only two honest options. Move the app to a Linux plan on a supported built-in runtime, which for most WordPress and Laravel workloads is a matter of a new plan and a redeploy, or bring your own PHP runtime on Windows and take on patching yourself for the rest of the app's life. If neither can happen this week, CERT-SE's secondary recommendation applies: take the app off the public internet until it can. Private endpoint plus VPN is a few hours of work; a compromised web server is not.
5. Turn on the Azure WAF mitigations, and understand what they do not prove
Microsoft's network security team published Azure WAF guidance on 21 October, and the headline is that none of it is on by default.
- Azure Front Door. A new managed rule, ID 99006001 in the MS-ThreatIntel-Response-CVEs rule group, available on Default Rule Set 2.0, 2.1 and 2.2. It is rolling out with completion expected by 28 October, and it ships disabled. You enable it per policy. It is a response-side mitigation: it "removes the potentially malicious elements from the response" rather than blocking the request, and because of that it does not generate WAF log entries or hit counts.
- Application Gateway WAF v2. No managed rule yet; one is expected later. What is available today is a rewrite rule that evaluates Set-Cookie response headers against a PHPSESSID pattern and deletes or replaces the header when it matches. Available immediately on v2 SKUs.
- Application Gateway for Containers. Custom rules now, managed rule later, and it does not support regex-based response rewrite, so the Application Gateway approach does not carry over.
# Front Door: confirm which policies have the rule available and whether it is enabled
az network front-door waf-policy list -g <rg> --query "[].name" -o tsv
az network front-door waf-policy managed-rules list -g <rg> --policy-name <policy> -o json \
| grep -B2 -A6 99006001
# Application Gateway: the Set-Cookie pattern Microsoft publishes for the rewrite rule
^PHPSESSID=(?:[a-zA-Z0-9-]|%2[Cc])*(?:[^a-zA-Z0-9;%-]|%(?!2[Cc]))+[a-zA-Z0-9-]*[^;]+;
Three consequences follow from the design. First, a green WAF policy status is not evidence that the rule is enabled, and an enabled rule on Front Door is not evidence that it fired, because it writes no log. Second, Microsoft's own description of the mitigations is "temporary, supplementary"; they address a specific technique, and Cloudflare, with far more traffic to learn from, already reports payloads mutating around its rules. Third, a WAF rule is not retroactive. Anything exposed and unpatched between the 20 October leak and the moment you enabled the rule was reachable, and the WAF tells you nothing about what happened in that window. Test any custom rule in Detection mode against your real forms, uploads and session flows before you set it to Prevention; the PHPSESSID rewrite in particular will touch every logged-in user.
6. Look for intrusion on anything that was exposed and unpatched
CERT-SE's primary instruction has three parts: identify PHP-based applications, follow the vendor's recommendations promptly, and "undersöka sina system efter tecken på intrång", examine the systems for signs of intrusion. The third part is the one that gets skipped. For a Linux App Service app, pull the HTTP logs and the web-server logs for 17 to 22 October into Log Analytics and look for request patterns that end in 500s followed by successful requests to paths that did not exist before, new files under the wwwroot, and outbound connections the app has no business making. For VMs and containers, the usual post-exploitation hunt applies: new cron entries, new PHP files with recent timestamps, web-server processes spawning shells.
// App Service HTTP logs: error bursts followed by new paths, per app
AppServiceHTTPLogs
| where TimeGenerated between (datetime(2026-10-17) .. datetime(2026-10-23))
| summarize errors=countif(ScStatus >= 500), total=count(), ips=dcount(CIp) by _ResourceId, bin(TimeGenerated, 1h)
| where errors > 50
| order by errors desc
// Any PHP file written in the last week (run in the app's SSH session)
// find /home/site/wwwroot -name '*.php' -mtime -7 -newer /home/site/wwwroot/index.php
Decision guide: who patches what
| Where PHP runs | Who patches | Your action this week |
|---|---|---|
| App Service Linux, built-in 8.2 to 8.5, Latest or Standard channel | Microsoft, done 21 Oct | Verify with php --version per plan; file a support case if stale |
| App Service Linux, Extended channel | Microsoft, last in line | Switch the channel to Standard or Latest now |
| App Service Linux, built-in 8.1 or older | Nobody | Upgrade the app to 8.2 or newer; test, then redeploy |
| App Service Windows, built-in PHP | Nobody; runtime is end-of-life | Move to Linux, or bring your own runtime, or take it offline |
| Custom container anywhere | You | Rebuild on 8.x.y fixed images, pin by digest, redeploy, verify the revision |
| VM or scale set, supported distro | The distribution, you apply it | Patch via Update Manager; restart php-fpm or Apache |
| VM on PHP 5.6 to 8.1 | Community backports only | Surý repository for Debian and Ubuntu, then plan the real upgrade |
| Vendor appliance or SaaS you host | The vendor | Ask for a dated statement; isolate until you have one |
The Swedish and EU angle
CERT-SE's advisory went out at 14:00 on 21 October and was updated at 16:30. It states plainly that organisations unable to update should "isolera internetexponerade PHP-baserade applikationer från publika nätverk". That sentence is the one to quote when a product owner asks for another week. It also notes the CVEs were not yet in CERT-SE's automated ANTS notifications at publication, so if your vulnerability process waits for that feed, it waited too long.
Cybersäkerhetslagen. Under Sweden's NIS2 transposition, in force since 15 January 2026, a significant incident triggers an early warning within 24 hours of awareness and a notification within 72. An exploitable, internet-exposed PHP server is not an incident by itself. Evidence of intrusion found in step 6 is, and the timestamp of that finding is when the clock starts. Write it down when you find it, not when legal asks.
The Cyber Resilience Act. If you are a Swedish ISV shipping a product that embeds PHP, whether an on-premises appliance, a container image or a hosted platform that customers deploy, Article 14 reporting has applied since 11 September 2026. An actively exploited vulnerability in your product goes to ENISA's single reporting platform within 24 hours of awareness. PHP2Shell is actively exploited, and you became aware on 21 October at the latest. The NCSC-NL tracker shows what vendors are doing in public; your customers will expect the same dated statement from you.
GDPR Article 33. A PHP application that handles personal data, which is most of them, and shows signs of intrusion is a personal-data breach with a 72-hour notification window to IMY. The same step 6 evidence feeds all three clocks. Run it once, properly.
The procurement point from last week's LiteLLM post stands here with more force. Public-sector buyers running an upphandling for a web platform now have a concrete question for every bidder: on 21 October 2026, how long did it take you to verify every PHP runtime you operate, and can you show the evidence? "Microsoft patches App Service" is a partial answer. "We checked, and here is the list" is the one that wins.
What to do today
- Run the Resource Graph query across every subscription and keep the output. Any Windows app with a phpVersion and any Linux app below 8.2 goes to the top of the list.
- SSH into one app per plan and confirm 8.2.35, 8.3.36, 8.4.27 or 8.5.12. Move Extended-channel apps to Standard.
- Rebuild every custom PHP image on a fixed version, pin by digest, redeploy and verify the active revision.
- Decide the fate of each Windows App Service PHP app this week: Linux plan, bring-your-own runtime, or off the internet.
- Enable Front Door rule 99006001 as it lands in your region and add the Application Gateway rewrite rule, in Detection mode first. Record that neither one logs a hit and neither replaces the patch.
- Hunt for intrusion on anything that was exposed and unpatched after 20 October, and start the NIS2, CRA and GDPR clocks from the first confirmed finding.
- Email every vendor whose product embeds PHP and ask for a dated statement. Isolate the ones that cannot give you one.
subscribe # the AI news that matters, minus the noise
Sources
- PHP: critical security release for CVE-2026-109618 and CVE-2026-109619, 21 October 2026
- Azure App Service team: Critical PHP security release, what it means for App Service
- Microsoft Azure Network Security: Addressing PHP2Shell, upgrade guidance and Azure WAF mitigations
- Microsoft Q&A: App Service team confirmation of the 21 October rollout and verification steps
- CERT-SE: Kritiska sårbarheter i PHP
- Cloudflare: Cloudflare deploys protections against php2shell vulnerability
- watchTowr: PHP2Shell FAQ, affected versions, SAPIs and exploitation status
- NCSC-NL: php2shell operational information and vendor status tracker
- CybelAngel: 2.24 million internet-facing PHP hosts, 59 percent on branches without a fix
- LinuxCompatible: Surý repository ships Debian and Ubuntu backports from PHP 5.6 to 8.5
- Jones Day: EU Cyber Resilience Act 24-hour reporting duties start 11 September 2026
- Technspire: LiteLLM 1.107.0 was malware, your Azure AI gateway audit