PHP released emergency fixes on October 21, 2026 for two critical flaws that can allow unauthenticated remote code execution in web-serving runtimes. Administrators should treat CVE-2026-109618 and CVE-2026-109619 as a fleet-wide runtime problem, not as a vulnerability limited to one framework or content management system.
The PHP project says every installation serving web requests is in scope, including PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, and LiteSpeed. The project considers mod_php the highest-risk configuration because a demonstrated attack can execute before application code runs. CISA separately urged immediate patching and refers to the pair as “php2shell.”
What changed on October 21
The fixed upstream releases are PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35. The PHP project is explicit that configuration changes are not complete mitigations. Updating to a fixed build is the only safe resolution. PHP 8.1 and older no longer receive normal upstream releases, although some operating-system and long-term-support vendors may provide verified backports.
| Branch | Affected upstream releases | First fixed release | Required action |
|---|---|---|---|
| PHP 8.5 | 8.5.0 through 8.5.11 | 8.5.12 | Update and restart every worker |
| PHP 8.4 | 8.4.0 through 8.4.26 | 8.4.27 | Update and restart every worker |
| PHP 8.3 | 8.3.0 through 8.3.35 | 8.3.36 | Update and restart every worker |
| PHP 8.2 | 8.2.0 through 8.2.34 | 8.2.35 | Update and plan migration before branch end of life |
| PHP 5.4 through 8.1 | End-of-life upstream branches | No normal upstream release | Migrate or obtain a documented vendor backport |
Distribution package versions may include a security backport without matching the upstream version string. Do not declare a host vulnerable or fixed from the version number alone when Debian, Ubuntu, Red Hat, SUSE, or another vendor maintains the package. Confirm the package changelog and the vendor’s CVE record.
How the two PHP vulnerabilities differ
CVE-2026-109618: corruption after memory exhaustion
PHP describes CVE-2026-109618 as an out-of-memory handling flaw. A request can reach a point where internal request data is only partly initialized. If allocation fails, cleanup may treat invalid data as valid. The dangerous boundary sits inside the runtime, so application-level input validation is not a reliable protection.
CVE-2026-109619: stale state across worker requests
CVE-2026-109619 involves request-owned internal state that can survive after its backing memory is freed. A later request handled by the same persistent worker can use that stale state before normal initialization resets it. This makes a clean restart after patching important: replacing packages without recycling every web worker can leave old code running.
AWS summarizes the potential impact as arbitrary code execution, access to data available to the PHP process, or denial of service. The security boundary is the affected worker and its application permissions. That is still serious because PHP workers commonly hold database credentials, API tokens, session material, write access to upload directories, and network access to internal services.
Build a complete PHP exposure inventory
Start with runtime discovery rather than a list of public websites. One application may use several PHP builds across a reverse proxy, FPM pools, scheduled jobs, deployment containers, and recovery images. Check golden images and stopped containers because they can reintroduce a vulnerable runtime during rollback or scale-out.
- Internet-facing Apache, Nginx plus PHP-FPM, LiteSpeed, FrankenPHP, and CGI services
- Container images, Kubernetes workloads, sidecars, build images, and autoscaling templates
- Hosting panels and appliances that bundle their own PHP runtime
- Serverless functions or managed platforms where the customer selects the runtime version
- Disaster-recovery images, standby nodes, developer environments, and forgotten staging sites
- Distribution packages with vendor backports that require advisory-level verification
Record the runtime version, SAPI, package origin, application owner, exposure, restart method, and evidence source. Prioritize web-serving SAPIs, but do not delete CLI entries from the inventory. A CLI-only runtime may share packages or images with a web deployment.
Patch and verify without leaving old workers behind
- Capture state. Record package versions, enabled repositories, active SAPI, FPM pools, container digests, and current worker process IDs.
- Patch a representative canary. Use the distribution or platform vendor’s security package when that vendor owns the runtime. Avoid mixing repositories during an emergency.
- Run application checks. Exercise authentication, uploads, sessions, queues, database access, and health endpoints.
- Roll through the fleet. Replace immutable images or update packages, then restart Apache, PHP-FPM, LiteSpeed, FrankenPHP, containers, and long-running workers as applicable.
- Verify the serving process. Read the version through the same execution path used by the application. A shell’s default
phpbinary may not match the FPM pool or Apache module. - Retire vulnerable artifacts. Remove old image tags from deployment manifests and prevent autoscaling groups from launching them.
# Read-only local checks. Package commands vary by distribution.
php -v
php --ini
php -r 'echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;'
# Inspect running web workers and their executable paths.
ps -eo pid,lstart,comm,args | grep -E 'php-fpm|apache2|httpd|lsphp|frankenphp' | grep -v grep
# Debian or Ubuntu package evidence.
dpkg-query -W -f='${Package} ${Version}\n' 'php*' 2>/dev/null
# RHEL-family package evidence.
rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE}\n' 'php*' 2>/dev/nullDo not expose a phpinfo() page to verify production. It can disclose modules, paths, environment variables, and configuration. Prefer local process inspection, package evidence, controlled health checks, or authenticated management telemetry.
Temporary controls when patching is delayed
The upstream advisory provides temporary measures for specific attack paths, but repeatedly warns that they do not make an affected installation safe. Use them to reduce exposure while the patch is being deployed, not as a reason to postpone it.
- For CVE-2026-109618, PHP recommends setting
memory_limit=-1in a server-level location applications cannot override, reducing request-size limits, and enforcing decoded body and input-complexity limits at the proxy or web server. - For CVE-2026-109619, disable
session.upload_progress.enabledbefore request parsing and restart every worker. On PHP 8.5, disablingfatal_error_backtracesblocks one confirmed path only. - Remove invalid per-request settings that generate diagnostics during initialization and fix attacker-reachable fatal errors or timeouts.
- If you cannot update or confidently apply a vendor backport, remove the service from public reach until a safe runtime is available.
Web application firewall rules can help reduce known request patterns, but they are compensating controls. Encoding differences, request fragmentation, alternate server paths, and future variants can bypass pattern-based filtering.
Hunt for runtime compromise
The public advisories do not provide a universal exploit indicator. Detection should focus on behavior around the PHP worker and the application account. Establish the vulnerable exposure window, preserve logs before rotation, and correlate several signals before concluding that exploitation occurred.
- Unexpected PHP, Apache, or LiteSpeed worker crashes, out-of-memory events, segmentation faults, and rapid respawns
- Unusual POST bodies, session upload-progress parameters, oversized multipart requests, and repeated fatal errors
- Shells, interpreters, downloaders, or system utilities launched by a web worker
- New files in web roots, upload directories, temporary paths, cron locations, or service startup paths
- Outbound DNS, HTTP, or TCP connections from worker accounts that normally receive only inbound requests
- Access to cloud metadata, secret stores, database administration functions, or internal management endpoints
If credible evidence appears, isolate the workload, preserve memory and logs where feasible, rotate secrets accessible to the runtime, and rebuild from a known-good image. Patching stops the vulnerable code path but does not remove persistence established before the update.
Hands-on lab: audit a PHP fleet without exploitation
This offline lab classifies a synthetic inventory against the upstream fixed versions. It does not send network requests or attempt exploitation. The example was tested with Python 3.11 and intentionally returns exit code 2 when vulnerable or end-of-life runtimes are present, which makes it suitable for a CI inventory gate.
Prerequisites and sample inventory
mkdir php-runtime-audit && cd php-runtime-audit
cat > php-inventory.csv <<'CSV'
host,version,sapi,internet_exposed,owner
web-01,8.5.11,fpm-fcgi,yes,web-platform
web-02,8.5.12,apache2handler,yes,web-platform
api-01,8.4.26,fpm-fcgi,yes,api-team
jobs-01,8.3.36,cli,no,data-team
legacy-01,7.4.33,apache2handler,yes,legacy-apps
lab-01,8.6.0,cli,no,research
CSVCreate the audit script
#!/usr/bin/env python3
import argparse, csv, json, re
from pathlib import Path
FIXED = {
(8, 5): (8, 5, 12),
(8, 4): (8, 4, 27),
(8, 3): (8, 3, 36),
(8, 2): (8, 2, 35),
}
def parse_version(raw):
match = re.match(r"\s*(\d+)\.(\d+)\.(\d+)", raw)
return tuple(map(int, match.groups())) if match else None
def classify(raw):
version = parse_version(raw)
if version is None:
return "unknown", "Verify the runtime manually"
branch = version[:2]
if branch in FIXED:
fixed = FIXED[branch]
if version >= fixed:
return "fixed", f"At or above {'.'.join(map(str, fixed))}"
return "vulnerable", f"Update to {'.'.join(map(str, fixed))} or later"
if branch <= (8, 1):
return "eol-no-upstream-fix", "Migrate or obtain a verified vendor backport"
return "unsupported-branch", "Confirm vendor support and release status"
parser = argparse.ArgumentParser()
parser.add_argument("inventory", type=Path)
args = parser.parse_args()
counts = {}
with args.inventory.open(newline="", encoding="utf-8") as handle:
for row in csv.DictReader(handle):
status, action = classify(row.get("version", ""))
counts[status] = counts.get(status, 0) + 1
print(json.dumps({
"host": row.get("host", "unknown"),
"version": row.get("version", "unknown"),
"sapi": row.get("sapi", "unknown"),
"internet_exposed": row.get("internet_exposed", "unknown"),
"status": status,
"action": action,
}, separators=(",", ":")))
print("SUMMARY " + " ".join(f"{k}={v}" for k, v in sorted(counts.items())))
raise SystemExit(2 if counts.get("vulnerable", 0) or counts.get("eol-no-upstream-fix", 0) else 0)Run and interpret the audit
python3 audit_php2shell.py php-inventory.csv | tee audit-results.jsonl
echo "exit_code=$?"{"host":"web-01","version":"8.5.11","status":"vulnerable",...}
{"host":"web-02","version":"8.5.12","status":"fixed",...}
{"host":"legacy-01","version":"7.4.33","status":"eol-no-upstream-fix",...}
SUMMARY eol-no-upstream-fix=1 fixed=2 unsupported-branch=1 vulnerable=2
exit_code=2The ellipses shorten the displayed example only; the script prints complete JSON records. Treat unsupported-branch as a manual-review result, not proof of vulnerability. Replace synthetic entries with asset-management exports, then add package-provider and advisory-status fields before using the result for production decisions.
Troubleshooting and cleanup
- If a distribution version contains suffixes such as
8.3.30-1ubuntu2, the script reads the leading semantic version but cannot detect backports. Review the vendor advisory. - If the script reports
unknown, normalize the inventory to three numeric version components. - If a pipeline hides exit code 2 after
tee, enableset -o pipefailbefore running the command. - Clean up with
cd .. && rm -rf php-runtime-auditonly after confirming the directory contains no real inventory or evidence.
Operational checklist
- Find every web-serving PHP runtime, including containers, appliances, and dormant recovery images.
- Update to 8.5.12, 8.4.27, 8.3.36, 8.2.35, or a vendor-documented backport.
- Restart all workers and verify the version through the actual serving SAPI.
- Remove vulnerable images and packages from automated deployment paths.
- Review worker crashes, suspicious child processes, file changes, and outbound traffic across the vulnerable window.
- Isolate and investigate any host with correlated compromise evidence; rotate process-accessible secrets.
Teams can adapt the runtime inventory into the workflow in our CISA vulnerability-prioritization guide. For application-boundary validation, see the Express proxy-trust audit, and use the account-abuse telemetry guide when investigating suspicious authentication activity around affected web applications.
Conclusion
PHP2Shell demands fast action because the flaws sit below application code and affect several common web-serving architectures. Inventory broadly, patch using the runtime owner’s supported channel, restart every worker, verify the serving path, and investigate behavior rather than waiting for a perfect indicator. Temporary configuration and WAF controls can buy deployment time, but they are not substitutes for a fixed PHP build.
Primary references
- PHP security advisory and fixed releases, October 21, 2026
- CISA: Critical Vulnerabilities in PHP Require Immediate Patching, October 21, 2026
- AWS Security Bulletin 2026-143-AWS, October 21, 2026
- Google Cloud response guidance, October 21, 2026
- Red Hat CVE-2026-109618 record
- Red Hat CVE-2026-109619 record







