Nguyen TuThe most-read story on daily.dev today is not a new framework or a benchmark — it is an emergency: on...
The most-read story on daily.dev today is not a new framework or a benchmark — it is an emergency: on October 21, 2026, the PHP project shipped fixes for two critical remote code execution flaws, CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell. Both score 9.2 on CVSS v4.0, and an unauthenticated attacker can run code on a vulnerable server. The news cycle covered the bug itself. This article is the practitioner's version — the problem we actually solve for clients: the fix exists, but does it run everywhere it needs to? The distance between "released" and "deployed" is where incidents happen, and closing that distance is pure automation work. It is the same philosophy as how we work: automate the routine aggressively so human judgment is spent exactly where it changes the outcome.
The patched releases are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (released October 21, 2026, at 14:00 UTC — pulled forward a day after the CVE IDs leaked). The PHP team also published minimal source backports for 7.4, 8.0 and 8.1, which receive no official release.
Now the uncomfortable part: Bitsight's internet-wide scans found that only about a third of the PHP instances where the version could be identified run a branch covered by the security update. The rest sit on older branches, and the most common branch by far is PHP 7.4, with more than two million observations. So for a large share of the real world, "the fix is out" and "we are fixed" are two different sentences. Your job on a day like this is to turn the second one into truth, systematically, before someone else turns it into a breach.
The two flaws themselves sit in the engine's memory management and error handling — code nearly every request touches. CVE-2026-109618 is OOM corruption: after a memory allocation fails, PHP leaves invalid data behind for its cleanup routine to process. CVE-2026-109619 is stale global state: request-owned state survives after its memory is freed, and a later request on the same worker can read or write through the dangling pointers. Every setup serving web requests is exposed — PHP-FPM, mod_php, php-cgi, FrankenPHP, LiteSpeed — with mod_php rated highest risk because the exploit fires before any application code runs.
You cannot patch what you do not know runs PHP. On emergency-patch day, the inventory step is the one most teams skip, and it is the one that decides whether the patch actually covers the fleet. A real inventory pulls from several sources of truth, because PHP hides in all of them:
Dockerfile and compose files that pin a PHP image tag. This is the fastest place to scan first, because it is text.php -v on every server, and ask your host when it will patch if you do not control it.A first-pass scan can be embarrassingly simple and still catch most of the fleet:
# 1. Every container image pinning a PHP base image
grep -rn "FROM php:" --include='Dockerfile*' --include='docker-compose*' .
# 2. Every host with PHP installed (Debian/Ubuntu fleet via your config management)
dpkg -l | grep -E '^ii\s+php[0-9]' | awk '{print $2, $3}'
# 3. What is actually running right now (catches source builds and stray SAPIs)
ps aux | grep -E 'php-fpm|apache2|httpd|litespeed' | grep -v grep
# 4. What version answers the web (spot-check the externally visible truth)
for host in web1 web2 web3; do
ssh "$host" 'php -v | head -1'
done
For codebases at scale, this is exactly what Sourcegraph published search queries and batch-change specs for during this event: finding Dockerfiles, CI pipelines and Helm charts that pin old PHP versions, then proposing the bump as a batch change. We have written about this machinery before — our guide to Git workflows for agent teams covers batch changes and reviewable, revertable rollouts, which is the same shape of work: many small, verified, reviewable changes instead of one heroic push.
This is the detail that eats patches in production. On Ubuntu, fixes exist for all supported releases — but running apt upgrade does not finish the job, because unattended upgrades do not restart running applications. The old, vulnerable PHP-FPM workers keep serving traffic until you restart them. The same is true for Apache with mod_php. A fleet that is "fully patched" in the package database and still vulnerable in memory is the classic failure mode of emergency patching.
The restart semantics differ by platform, and on patch day you need the real list, not assumptions:
| Platform / SAPI | What actually applies the fix |
|---|---|
| Ubuntu/Debian PHP-FPM |
apt upgrade + restart php*-fpm and the web server; unattended-upgrades will not restart running apps |
| mod_php (Apache) | Restart Apache; highest risk SAPI, since the exploit fires before application code runs |
| Laravel Cloud | Auto-patches |
| Laravel Vapor (PHP 8) | Apps redeployed automatically |
| Laravel Vapor (PHP 7) | No patch at all — the app must be upgraded off the branch |
| Laravel Forge | Manual update under Runtime → Versions |
| Google Cloud serverless | Supported runtimes auto-patched; custom images need a manual redeploy |
| Cloudways | WordPress instances auto-patched |
| DigitalOcean Droplets / DOKS / App Platform | Manual action required |
So the rollout script has two halves — the version bump and the restart — and the second half is where verification must happen:
#!/bin/bash
# bump-and-restart.sh — the boring half that actually matters
set -euo pipefail
sudo apt update && sudo apt upgrade -y php8.4-fpm
# Restart the runtime AND the web server; the old workers stay in memory otherwise
sudo systemctl restart php8.4-fpm
sudo systemctl restart apache2 || sudo systemctl restart nginx
# Verify the fix is the thing actually running, not just installed
php -v | head -1
The principle generalizes beyond PHP: a patch is only real when the vulnerable code is no longer in memory. Anything in your rollout that checks the package database but not the running process is checking the wrong thing.
Emergency does not mean reckless. A staged rollout with a canary still beats a fleet-wide push on a CVE day, because the patch itself is a change to every request path in your application, and engine-level patches have historically broken extensions and edge-case behavior. The mechanics that work:
A large share of the internet runs PHP branches that get no official release — 7.4 alone had more than two million observations in Bitsight's scans, and W3Techs data puts about 48.5% of PHP sites on end-of-life branches. The PHP project published minimal source patches for 7.4, 8.0 and 8.1 to help with backports, but that is not a supported release; your vendor's security advisory is the authority on whether a patched package exists for your platform.
Be honest about the decision tree:
"Awaiting the vendor" is not a plan. A written decision with a date — patched, isolated, or offline — is.
Patching a fleet takes time: inventory, maintenance windows, application testing against the new builds. During that window, boundary controls reduce exposure to the confirmed attack paths. Cloudflare turned on default WAF rules for its customers about nine hours before disclosure, after attackers began probing, and AWS published a guide with AWS WAF rules for the confirmed PHP2Shell attack paths. A reverse proxy or WAF in front of PHP can block hostile requests before they reach the vulnerable parser.
Two caveats, both worth stating plainly:
Detection matters as much as blocking. Datadog's analysis suggests looking for large form submissions, memory-exhaustion errors during request initialization, and unexpected shell processes spawned by the web server user — and notes that any PHP script exposes the vulnerable path, even one that only prints a response, because the request never reaches application code. Practical log checks:
# Requests hitting the session upload-progress path (the reported trigger surface)
grep -E 'PHP_SESSION_UPLOAD_PROGRESS' /var/log/apache2/access.log | tail -20
# The literal calling card some scanners used
grep -i 'php2shell' /var/log/nginx/access.log | awk '{print $1}' | sort -u
# Shell processes that should never be children of the web server user
ps -ef | awk '$1=="www-data" && /sh|bash|wget|curl/ {print}'
Arctic Wolf Labs reported scanning and attempts pulling ELF and Golang malware binaries via wget — so unexpected outbound fetch-and-execute behavior from web-server-owned processes is exactly the signal to alert on. (On the bot-detection side of boundary defense, we have a full breakdown of TLS fingerprints and token-based bot detection — the same layer where these WAF rules live.)
The rollout is not done when the script exits green. It is done when every runtime in the inventory answers with a fixed version. Close the loop:
#!/bin/bash
# verify-php-fleet.sh — the inventory from section 2, re-run as an assertion
FIXED=("8.5.12" "8.4.27" "8.3.36" "8.2.35")
for host in $(cat fleet-hosts.txt); do
ver=$(ssh "$host" 'php -r "echo PHP_VERSION;"')
ok="no"
for f in "${FIXED[@]}"; do [[ "$ver" == "$f" ]] && ok="yes"; done
# Also confirm the running workers are the new build, not stale memory
echo "$host -> php $ver | patched: $ok"
done | tee patch-verification-$(date +%F).log
Keep that log. When the next CVE drops — and Taylor Otwell's warning during this event was that this probably will not be the last time, nor PHP the only language to go through it — the difference between a two-hour response and a two-day scramble is whether your inventory already exists. The fleet list, the restart runbook and the verification script are assets. Build them once, on a calm day, and CVE day becomes execution instead of archaeology.
Does updating WordPress, my plugins or my theme fix PHP2Shell?
No. The vulnerability is in the PHP runtime itself, which lives on the host — below your application. Update the runtime (and restart it), regardless of what your CMS or framework reports.
Does putting a WAF in front fix it?
No. WAF rules from Cloudflare, AWS and others reduce exposure to confirmed attack paths while you patch, but they do not remove the vulnerability. Patching is the only complete fix.
I run PHP 7.4. Am I covered?
Not by an official release. The PHP project published minimal source patches for 7.4, 8.0 and 8.1 to aid backports; check your OS vendor's security advisory for patched packages. If neither exists, the official guidance from CERT-EU, NSA and CISA is to isolate the system or take it offline — or upgrade the branch.
How do I know if someone already exploited it against me?
Look for the signals in section 6: large form submissions, memory-exhaustion errors during request initialization, unexpected shell processes or wget/curl activity owned by the web-server user, and php2shell strings in access logs. Absence of these signals is reassuring, not proof.
Why is this suddenly everywhere?
Both flaws were found with frontier AI assistance — Calif's team with OpenAI's models, and Wiz independently with its Atlas tool — and one researcher demonstrated that a working proof of concept was roughly "one prompt away" once hints leaked. The finding side of security is accelerating; the patching side has to keep up, which is exactly what the playbook above is for.
Originally published on botslave.com. Botslave is an automation studio — see what we build and how we work.