Skip to main content
SecurityPHPVulnerabilitiesDevOps

PHP2Shell: CVE-2026-109618 & CVE-2026-109619 Explained

Oct 2026 · 9 min read

Dhiwin Samrich
Dhiwin SamrichAI/ML Engineer
Share

Two critical (CVSS 9.2) flaws in the core PHP runtime affect every version from 5.4 to 8.5.11. Here's how the bugs work, how to spot exploitation, and the exact steps to patch and harden.

TL;DR

On 21 October 2026, the PHP project and Wiz Research disclosed two critical flaws in the core PHP runtime itself — not a framework, not an extension. Together they are being called PHP2Shell, and they can expose applications to unauthenticated remote code execution and memory disclosure.

  • CVE-2026-109618 — out-of-memory handling bug, CVSS 9.2 (Critical)
  • CVE-2026-109619 — stale request state / native use-after-free, CVSS 9.2 (Critical)
  • Affected: every PHP version from 5.4 through 8.5.11
  • Fixed in: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35
  • No fix coming for PHP 8.1 and older — they are end of life

If you run PHP anywhere — a WordPress site, a Laravel API, a legacy admin panel, or an appliance with PHP buried in its firmware — this one is worth an hour of your day.

Why These Two Bugs Are So Serious

Most PHP security news is about application code: an unescaped query, an unsafe upload handler. These bugs live one layer lower, in how the runtime manages memory between requests. That means a perfectly written application is still exposed, because the flaw is triggered before your code even runs.

CVE-2026-109618 — Out-of-Memory Bailout

When PHP runs out of memory, it bails out of the current request. The advisory describes how that bailout can interrupt PHP while internal request data is only partially initialised. The cleanup that follows then treats that half-built data as valid. Attackers can deliberately push a worker into this state with oversized or deeply nested multipart request bodies.

CVE-2026-109619 — Stale State Across Requests

The second bug is a classic native use-after-free. Request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may then use that stale state before normal initialisation resets it. Because PHP-FPM, Apache mod_php and FrankenPHP all reuse long-lived workers, one malicious request can poison the next.

Why They Chain Well

One bug creates a corrupted state; the other lets that state leak into a fresh request. Public proof-of-concept chains reportedly combine them to leak memory (for example through oversized session cookies) and then pivot to code execution. Google Cloud notes that some chains produce zero segfaults and zero error.log entries, so "nothing crashed" is not evidence that nothing happened.

Step 1 — Patch, Then Actually Restart

Upgrading is the only complete fix. Everything else in this post is a compensating control.

  1. Upgrade to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or later).
  2. If you are on 8.1 or older, plan a jump to 8.2+ — those branches will not be patched.
  3. Restart the process that serves requests. Upgrading a package does not replace libphp.so or php-fpm already loaded into running workers. Restart php8.x-fpm, apache2/httpd, frankenphp or lsws, including anything managed by a supervisor.
  4. Rebuild container images rather than trusting a running container to update itself.

Confirm the fix landed by checking the changelog for the CVE IDs:

apt-get changelog php-fpm | grep -E "CVE-2026-109618|CVE-2026-109619"
rpm -q --changelog php-fpm | grep -E "CVE-2026-109618|CVE-2026-109619"

Get packages from your distribution or official sources rather than waiting on volunteer third-party mirrors.

If you suspect a host is already compromised, capture memory forensics before restarting — restarting workers permanently destroys the volatile heap evidence.

Step 2 — Hardening If You Can't Patch Today

If an upgrade has to wait, these php.ini settings neutralise the known triggers. Put them in the global php.ini or a static FPM pool (php_admin_value), not .htaccess — warnings during early loading can activate the vulnerable paths.

; CVE-2026-109619 — remove stale-pointer producers
session.upload_progress.enabled = 0
fatal_error_backtraces = 0          ; on PHP < 8.5.12

; CVE-2026-109618 — stop OOM bailouts and multipart amplification
; TEMPORARY: pair with a cgroup MemoryMax=, restore a finite limit after patching
memory_limit = -1
post_max_size = 8M
upload_max_filesize = 4M
max_input_nesting_level = 16
max_input_vars = 250
max_file_uploads = 10
max_multipart_body_parts = 100

; Runtime hygiene
expose_php = Off
display_errors = Off
log_errors = On

Three Details That Matter

  • Recycle workers every request with pm.max_requests = 1 in www.conf. It costs throughput, but it breaks multi-request memory attacks.
  • Use cgroup limits, not ulimit -v. Address-space limits make mmap() fail, which is exactly the out-of-memory path you are trying to avoid. A systemd MemoryMax=2G is the safer way to cap memory.
  • memory_limit = -1 is a stopgap. It removes a safety net, so only use it together with a cgroup limit and revert once patched.

Step 3 — Push the Defence to the Edge

Multipart parsing happens before any of your PHP code executes, so your reverse proxy or WAF is the first real line of defence.

  • Block multipart/form-data on routes that never accept uploads.
  • Reject chunked POSTs and cap body size (for example client_max_body_size 8m; in NGINX).
  • Block deeply nested field names such as a[b][c][d]…, and cap headers per multipart part.
  • Validate PHPSESSID against ^[A-Za-z0-9,-]{22,256}$ and drop cookies containing null bytes.
  • Allow unpatched public servers only through the WAF, or take them offline.

On Google Cloud, Cloud Armor's managed rules already cover these patterns; turn on the stable ruleset categories in enforce mode and add a request-body size rule. Other WAFs need equivalent custom rules.

Step 4 — Detect Exploitation Attempts

These are the high-signal indicators published alongside the disclosure. Wire them into whatever you already alert on.

Network and WAF

  • Outbound Set-Cookie: PHPSESSID= values longer than 256 bytes — a sign of leaked memory. Regex: PHPSESSID=[A-Za-z0-9%,_-]{257,} (include %, because leaked pointers are URL-encoded).
  • Bursts of 50–150 concurrent OPTIONS * HTTP/1.1 keep-alive connections, or slow chunked multipart POSTs.
  • Repeated .php/ path segments (/index.php/index.php/…) returning 404, and rapid interleaved 500/200 POST bursts against one endpoint.

Host and Process

  • sh, bash, curl, wget, nc, python or chmod spawned by php-fpm, httpd, apache2 or frankenphp — especially as www-data.
  • Spikes in PHP-FPM worker respawns (SIGSEGV, exit code 139) or unexpected core dumps.

Because some chains never crash, process telemetry beats crash logs here. If your EDR or auditd can watch child processes of the web server, that is your best single signal.

Step 5 — Contain the Blast Radius

Assume a worker can be compromised and limit what it can do:

  • Run PHP workers as a dedicated unprivileged user with a read-only document root (ProtectSystem=strict in systemd, readOnlyRootFilesystem: true in Kubernetes) and NoNewPrivileges=yes.
  • Block outbound internet from PHP servers so a shell can't download tools or call home.
  • Prevent the PHP process from launching /bin/sh, curl or wget using seccomp, AppArmor or runtime security.

Don't Forget the PHP You Didn't Deploy

The easiest servers to miss are the ones nobody thinks of as "PHP servers": NAS boxes, VPN and gateway admin panels, monitoring consoles, storage appliances and marketplace images. Inventory edge and management devices, check with vendors for firmware updates, and keep their admin interfaces off the public internet until they are patched.

Cloud Platform Notes

On Google Cloud, which says its own infrastructure isn't affected:

  • App Engine Standard: PHP 8.2+ runtimes are patched automatically; 8.1 and older must be upgraded and redeployed.
  • App Engine Flexible: not patched automatically — redeploy, and rebuild custom runtimes.
  • Cloud Run / Cloud Run functions: patched if automatic base-image updates are on; otherwise rebuild and redeploy.
  • GKE: update images with a fixed PHP version and roll your pods.

The same logic applies on any cloud: managed runtimes may patch themselves, but anything you built into an image is your responsibility to rebuild.

A Note on How These Were Found

Google credits AI models, including Gemini, with a major role in discovery, exploit development, attack detection and patch validation alongside Wiz's researchers. Whatever you think of the hype, the practical takeaway is real: AI-assisted vulnerability research is shrinking the gap between a bug existing and a working exploit existing. Patch windows measured in weeks are becoming windows measured in days.

Your Checklist

  1. Find every PHP install, including appliances and container images.
  2. Upgrade to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35, or move off 8.1 and older.
  3. Restart the real serving processes and rebuild images.
  4. Apply the php.ini and WAF hardening anywhere you can't patch immediately.
  5. Alert on oversized PHPSESSID cookies and shells spawned by the web server.
  6. Lock workers down: read-only roots, no outbound internet, no shell spawning.

This post summarises Google Cloud's public guidance and the PHP security advisories; always follow the official advisories and your vendor's instructions for your exact environment.

Let's Talk-Knight's Gambit-Game On-
Consultant