BlogVulnerability Research

PHP2Shell: Decade-Old PHP RCE Chain Explained

On 21 October 2026, Wiz Research disclosed PHP2Shell — a pair of use-after-free bugs in PHP's fatal-error handling that chain into unauthenticated remote code execution against every WordPress, Drupal and Joomla site built on PHP 5.4 or later. CVSS up to 9.2. Here is what the bugs are, why they survived a decade of eyes on the PHP source, and what to patch and detect in the next 48 hours.

10 min readBy TigerGate Research, Security Research TeamPublished Updated

TL;DR

  • · Two CVEs: CVE-2026-109618 (upload-progress unserializer UAF) and CVE-2026-109619 (fatal-error backtrace UAF).
  • · Root cause: PHP's out-of-memory fatal-error path destroys the request heap but leaves global pointers dangling, so the next request on the same worker reuses the stale state.
  • · Chains into unauthenticated RCE on vanilla WordPress, Drupal and Joomla container images.
  • · Affects PHP 5.4 through all current branches. Patched upstream on 21 Oct 2026.
  • · If your attacker-facing fleet runs PHP under FPM or Apache mod_php, patch now. Everything else below is detection and context.

What is PHP2Shell?

PHP2Shell is the name Wiz Research gave to a chain of memory-safety flaws in the PHP core engine. Each bug on its own is a classic use-after-free; stitched together across a worker's request lifecycle, they produce memory disclosure and arbitrary code execution without authentication. The flaws live in PHP itself, which means the application running on top — WordPress, Drupal, Joomla, Laravel, Symfony, or any first-party PHP code — is incidental. Clean, modern application code offers no defence.

The root cause: fatal errors that don't clean up

PHP processes HTTP requests on long-lived worker processes under FPM or mod_php. Each request has a short-lived "request heap" that is destroyed and recreated between requests. When a fatal allocation failure (out-of-memory) interrupts a request mid-flight, PHP tears down the request heap — but a handful of global state pointers are left dangling at process scope:

  • BG(unserialize) — state for the upload-progress unserializer
  • EG(last_fatal_error_backtrace) — the backtrace captured when the fatal fired
  • CG(map_ptr_last) — OPcache's shared-memory frontier marker

The next request handled by the same worker reads from or writes through these pointers. The memory they reference has already been freed and may now hold attacker-controlled bytes, which turns ordinary PHP functionality into a UAF primitive. This is the single mechanism behind both CVEs; the two CVE IDs reflect two distinct exploitation surfaces.

The four exploitation primitives

1. Upload-progress unserializer corruption (CVE-2026-109618)

Stale BG(unserialize) state lets an attacker drive controlled deserialisation on the second request. The upload-progress feature is enabled by default on most distributions and does not require authentication to reach, which is why this one is the primary RCE primitive.

2. Memory disclosure via PHPSESSID cookies

Freed heap contents are reflected back in the next request's response cookies. Crucially, no session_start() call is required for the leak to work. For attackers this is the "read" side of the chain: leak pointers from the heap, defeat ASLR, prepare the second write.

3. Backtrace pointer reuse (CVE-2026-109619)

EG(last_fatal_error_backtrace) persists across requests, so a chain of two carefully timed requests gives the attacker a controlled read/write primitive through the dangling backtrace structure. Independent of the unserializer path — useful when upload-progress is disabled.

4. OPcache type confusion

Cross-worker drift in CG(map_ptr_last) advancement causes type confusion in OPcache's shared memory segment. Not required for RCE but hardens the chain against ASLR and heap-grooming entropy.

What's actually affected

Wiz demonstrated RCE against the latest container images of WordPress, Drupal and Joomla with no application-layer modification — just the vanilla PHP that ships with the image. Because the bug is in PHP, the real affected list is "any PHP application running under FPM or mod_php on a vulnerable PHP version." That includes:

  • WordPress, Drupal, Joomla, MediaWiki, Nextcloud, OwnCloud
  • Magento, PrestaShop, OpenCart, WooCommerce (via WordPress)
  • Laravel, Symfony, CodeIgniter, CakePHP applications in production
  • phpMyAdmin, Roundcube, SquirrelMail, Zabbix, Icinga web UI
  • Hosted PHP on Hostinger, Bluehost, SiteGround, Kinsta, WP Engine, GoDaddy

PHP versions affected: every branch released in the last decade, back to PHP 5.4. CVSS up to 9.2.

Timeline

DateEvent
19 Aug 2026Wiz Research discovers the first UAF primitive using Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol
20–21 Aug 2026RCE confirmed against Drupal and WordPress container images
24 Aug 2026Coordinated disclosure to the PHP security team
21 Sep 2026PHP acknowledges the reports and begins fix development
21 Oct 2026Patched PHP releases ship; public disclosure

Why did this survive a decade in the open-source PHP core?

The flaw sits at the intersection of three code paths that very few reviewers hold in their head at once: PHP's fatal-error handler, the global request-cleanup path, and specific extensions (upload-progress, OPcache) that interact with request-scope state. Static analysers do not model cross-request state. Fuzzers struggle because triggering the bug requires chaining two HTTP requests with a controlled out-of-memory failure between them.

Wiz credits frontier AI models for closing the gap. The quote from the writeup: "Frontier models substantially reduced that barrier, turning a longstanding flaw into an exploit." This is a worth-watching pattern — not that AI invents novel bug classes, but that it compresses the cost of adapting known-but-impractical primitives to specific heap layouts and PHP versions from months of specialist work into hours.

What to do in the next 48 hours

  1. Patch PHP. Apply the 21 Oct 2026 security releases from php.net or your distribution's package feed (Debian, Ubuntu, Red Hat, Amazon Linux, Alpine). Managed hosts (WP Engine, Kinsta, Hostinger, SiteGround) typically patch within hours — confirm your plan's SLA.
  2. Restart FPM pools. The dangling-state class means a worker process compromised before the patch stays compromised until it restarts. systemctl restart php8.2-fpm and watch for request errors during the drain.
  3. Audit OPcache contents. Clear OPcache (opcache_reset() via a CLI script or restart the FPM pool) in case cross-worker type confusion left poisoned entries.
  4. Hunt for exploitation. Look back 48 hours for: unusually large PHPSESSID cookies in access logs, bursts of HTTP 500s on multipart upload endpoints, repeated out-of-memory errors in php-fpm logs, and new outbound connections from webserver processes to unfamiliar IPs.
  5. Rotate secrets on any host where exploitation looks plausible — database passwords, API keys loaded into PHP via getenv(), OAuth client secrets.

How TigerGate helps

  • Server Vulnerability Monitoring flags vulnerable PHP builds on every Linux host and cloud VM, prioritized by CISA KEV and EPSS once the CVEs land on those feeds — and by eBPF runtime-reachability so you see which hosts are actually running PHP in production vs. just having it installed.
  • Container Security scans WordPress, Drupal, Joomla, Laravel and custom PHP images for the vulnerable PHP binary before they reach your registry and admission controller.
  • eBPF runtime detection watches for the exploitation side-effects: anomalous child processes spawned by php-fpm or Apache, unexpected outbound connections, writes to /tmp or /dev/shm followed by execution.
  • Compliance evidence maps the PHP patch to SOC 2 CC7.1 vulnerability lifecycle, PCI DSS 6.3.3 (critical patch within 30 days) and CERT-In VAPT reporting — the audit trail your next auditor will ask for.

Sources

Find vulnerable PHP across your fleet

14-day trial. Agentless scan on EC2, Azure VM, GCE and Oracle Cloud starts in minutes. The agent adds runtime reachability so you page on CVEs loaded by live php-fpm, not every host that merely has the PHP package installed.