php2shell: Inside the Leaked Critical PHP Flaws Triggering Emergency RCE Patching (CVE-2026-109618 & CVE-2026-109619) | Lorikeet Security Skip to main content
Back to Blog

php2shell: Inside the Leaked Critical PHP Flaws Triggering Emergency RCE Patching (CVE-2026-109618 & CVE-2026-109619)

Lorikeet Security Research Team October 21, 2026 14 min read
php2shell: Leaked Critical PHP Flaws (CVE-2026-109618 & CVE-2026-109619)

URGENT SECURITY ADVISORY (P0): Two unauthenticated Remote Code Execution vulnerabilities in PHP's Zend Engine—dubbed php2shell—leaked publicly ahead of coordinated disclosure embargoes on October 20, 2026. Tracked as CVE-2026-109618 and CVE-2026-109619, both carry a maximum severity CVSS v4.0 rating of 9.2. Emergency patches are now live for all active PHP release branches: 8.5.12, 8.4.27, 8.3.36, and 8.2.35. Every internet-facing PHP deployment must be patched and reloaded immediately.

In application security, the most terrifying class of vulnerabilities is not an authorization bug inside custom code, but a memory corruption primitive inside the underlying runtime engine itself. When such a flaw allows unauthenticated remote code execution (RCE) against default web server configurations, the threat landscape shifts from isolated incidents to ecosystem-wide panic.

On October 20, 2026, the vulnerability disclosure embargo surrounding two severe bugs in PHP's core memory allocator and request handling engine fractured. Public discussions, candidate commit diffs, and functional exploit mechanics circulated openly across Hacker News and X/Twitter under the collective label "php2shell".

Within hours of the leak, security research teams—led by a rapid-response advisory from Huntress and independent confirmation by watchTowr Labs—verified that both flaws were reproducible against stock Linux web servers running PHP-FPM, Apache mod_php, and FrankenPHP. Facing active exploitation risks and widespread reconnaissance, the PHP Security Team abandoned their standard release schedule to drop immediate out-of-cycle emergency patches.

This technical report provides the industry's definitive analysis of the php2shell incident: the embargo timeline, deep Zend Engine C source code breakdowns, the two-stage heap corruption mechanics, enterprise detection rules (Suricata & auditd), and what this means for organizations operating long-lived PHP infrastructure.


The CVEs at a Glance

Metric CVE-2026-109618 CVE-2026-109619
Component Zend Engine / SAPI Request Startup (main/SAPI.c) Zend Memory Manager / Error Bailout (Zend/zend_alloc.c)
Vulnerability Class Heap Memory Corruption via Unwound Out-of-Memory (OOM) Use-After-Free (UAF) via Dangling Request Pointers in Persistent Workers
CVSS v4.0 Score 9.2 (Critical) 9.2 (Critical)
CVSS Vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Authentication Required None (Unauthenticated Remote Attacker) None (Unauthenticated Remote Attacker)
Primary Attack Vector Single HTTP request triggering boundary OOM allocation Two-phase worker grooming (Stage 1: Fatal abort; Stage 2: UAF trigger)
Fixed Upstream Versions 8.5.12, 8.4.27, 8.3.36, 8.2.35 8.5.12, 8.4.27, 8.3.36, 8.2.35
End-of-Life Status PHP 7.0–7.4, 8.0, 8.1: Unpatched / Vulnerable PHP 7.0–7.4, 8.0, 8.1: Unpatched / Vulnerable

Chronology of an Embargo Collapse

The standard Coordinated Vulnerability Disclosure (CVD) pipeline exists to prevent zero-day mass exploitation by giving downstream package maintainers (Debian, Red Hat, Canonical, Alpine) and cloud infrastructure providers time to test backported patches. With php2shell, that safety buffer evaporated in less than 12 hours.

October 16, 2026 – Private Notification
Vendor Disclosure & Patch Drafting

Security researchers privately submitted two separate memory corruption flaws to the PHP security response team. Maintainers agreed on critical severity and began authoring candidate pull requests under a private security branch scheduled for public disclosure on October 24.

October 20, 2026 (04:12 UTC) – The Breach
Public Leak on Hacker News & Social Platforms

A pseudonymous account posted detailed references, commit SHA diffs, and CVE designations (CVE-2026-109618 & CVE-2026-109619) to developer forums and social media. The post coined the term "php2shell" and outlined how the error recovery handlers could be manipulated to gain system execution.

October 20, 2026 (09:45 UTC) – Verification
Independent Validation & PoC Confirmation

Research engineers at Huntress and watchTowr successfully reproduced the bugs against standard Ubuntu 24.04 and Alpine container environments running PHP-FPM 8.3. Huntress published an emergency rapid-response bulletin warning of immediate exposure.

October 20, 2026 (17:30 UTC) – Mobilization
PHP Security Team Emergency Out-of-Cycle Tagging

Acknowledging that the embargo was dead, the PHP core team bypassed the formal test cycle to push git tags and source tarballs for PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 directly to upstream mirrors.

October 21, 2026 – Global Advisory
CERT-EU & CISA Flash Notices

European and North American cyber agencies issued alerts as commercial honeypots detected the first automated reconnaissance scans querying web servers for PHP version banners and specific request headers.


Technical Deep Dive: The Flaws Inside the Zend Engine

To understand why php2shell is so devastating, one must examine how PHP manages memory and runtime exceptions in C. PHP is written in C, a language that lacks native exception handling or automated RAII (Resource Acquisition Is Initialization) semantics.

To simulate exceptions across deeply nested function calls (such as parsing incoming HTTP headers, populating superglobals, or evaluating Zend opcodes), PHP relies on the C standard library's setjmp() and longjmp() primitives, wrapped in internal macros: zend_try, zend_catch, and zend_bailout().

/* Zend Engine Bailout Primitive (Zend/zend.h) */
#define zend_try \
    { \
        JMP_BUF *__orig_bailout = EG(bailout); \
        JMP_BUF __bailout; \
        \
        EG(bailout) = &__bailout; \
        if (SETJMP(__bailout) == 0) {
#define zend_catch \
        } else { \
            EG(bailout) = __orig_bailout;
#define zend_end_try() \
        } \
        EG(bailout) = __orig_bailout; \
    }

When an unrecoverable condition occurs—such as a memory exhaustion error or fatal parser exception—the engine invokes zend_bailout(), which executes LONGJMP(*EG(bailout), 1). This non-local jump immediately restores CPU registers and leaps backwards up the call stack to the enclosing zend_catch block. It completely skips all standard return paths, function cleanup routines, and local variable deallocation.

This architectural decision is the structural foundation of both php2shell vulnerabilities.

CVE-2026-109618: Out-of-Memory (OOM) Request Startup Corruption

When an incoming HTTP request arrives at PHP-FPM, the runtime calls php_request_startup(). This routine initializes the Zend Memory Manager (Zend MM), clears globals, configures error handling, and executes sapi_activate() to parse HTTP query parameters, headers, and multipart form bodies.

The flaw in CVE-2026-109618 occurs during early request-body ingestion. If an attacker delivers a request designed to exceed PHP's configured memory_limit precisely during the initialization of the SG(request_info) hash tables, an allocation inside _zend_mm_alloc() returns NULL, triggering zend_error(E_ERROR, "Allowed memory size exhausted").

That error triggers zend_bailout(), leaping directly into the outer shutdown handler php_request_shutdown():

/* Simplified Representation of Vulnerable Shutdown Logic in main/main.c */
void php_request_shutdown(void *dummy) 
{
    /* ... */
    /* FLAW: The engine unconditionally assumes SG(sapi_headers) and 
       internal symbol tables were completely initialized */
    if (SG(sapi_headers).headers) {
        zend_llist_destroy(&SG(sapi_headers).headers); // <-- CRASH / CORRUPTION
    }
    
    /* Iterates over partially initialized request tracking tables */
    zend_hash_destroy(EG(symbol_table)); 
    /* ... */
}

Because the bailout occurred mid-initialization, pointers inside SG(sapi_headers) and internal linked lists held uninitialized stack or heap garbage rather than valid heap chunks. When zend_llist_destroy() attempts to walk the list and invoke free handlers on each element, it dereferences attacker-influenced memory offsets. By manipulating the size and shape of preceding HTTP headers, an attacker grooms the heap so that the uninitialized pointers point to memory they control, transforming an OOM abort into an arbitrary write primitive.

CVE-2026-109619: Persistent Worker Contamination & Use-After-Free

While CVE-2026-109618 relies on an OOM trigger, CVE-2026-109619 is even more insidious: it weaponizes the persistence of worker processes across sequential requests.

To avoid the enormous overhead of fork-exec per request, web servers use long-lived worker pools. In PHP-FPM, a pool of workers sits in a loop: accept request, execute, tear down request memory, wait for next request.

The Zend Memory Manager enforces two allocation tiers:

Under CVE-2026-109619, a fatal error occurring during specific stream filter or session operations caused zend_bailout() to tear down the request-scoped Zend memory manager without notifying persistent global engine structures. Specifically, a persistent pointer inside the global execution context (EG(current_execute_data) or stream context wrappers) continued to reference a memory address that had been released back to the allocator's free bin list.


The Two-Stage Exploit Chain (Visual Architecture)

The following diagram illustrates how an unauthenticated remote attacker weaponizes a shared PHP-FPM worker process to transition from a dangling pointer to full interactive shell execution:

[ ATTACKER ]                                               [ PHP-FPM WORKER POOL ]
     │                                                               │
     │  1. HTTP POST /index.php (Malformed Stream Payload)           │
     ├──────────────────────────────────────────────────────────────>│ [ Worker #3 Active ]
     │                                                               │  ├─ Allocates Request Struct (Heap 0x7fff10)
     │                                                               │  ├─ Fatal Error Triggered
     │                                                               │  ├─ zend_bailout() called via longjmp()
     │                                                               │  ├─ Request memory freed back to Zend MM bin
     │  <── HTTP 500 Fatal Error Response ───────────────────────────┤  └─ BUG: EG(stream_ctx) STILL POINTS TO 0x7fff10
     │                                                               │
     │  2. HTTP POST /index.php (Grooming / Shellcode Payload)       │
     ├──────────────────────────────────────────────────────────────>│ [ Worker #3 Reused ]
     │                                                               │  ├─ Incoming request reallocates 0x7fff10
     │                                                               │  ├─ Attacker body overwrites 0x7fff10 with:
     │                                                               │  │   [ Fake vtable ptr -> system() ]
     │                                                               │  │   [ Argument pointer -> "/bin/sh -c ..." ]
     │                                                               │  └─ Engine calls stale handler on 0x7fff10
     │                                                               │        │
     │                                                               │        ▼
     │  <── Reverse Shell Spawned (TCP Connect-Back) ────────────────┼─── [ system("/bin/sh") EXECUTED ]
     │                                                               │

Because the attacker can send both requests over the same persistent HTTP/1.1 or HTTP/2 keep-alive connection, modern reverse proxies (such as Nginx upstreaming to fastcgi_pass) naturally forward both packets to the exact same backend worker process. The exploit does not require guessing PIDs or spraying dozens of threads; it is a deterministic, two-step surgical strike.


Architectural Blast Radius: Runtime & SAPI Matrix

Not every PHP installation is equally vulnerable. The risk surface is heavily dictated by the Server API (SAPI) architecture and whether processes survive across request boundaries:

Architecture CVE-2026-109618 (OOM) CVE-2026-109619 (UAF) Overall Risk & Exploit Feasibility
Apache mod_php Vulnerable Vulnerable Critical (Highest Blast Radius): PHP runs directly inside the Apache master process space. Successful RCE gains immediate execution with the full permissions of the web server daemon without traversing IPC boundaries.
PHP-FPM (FastCGI) Vulnerable Vulnerable Critical: The default architecture for Nginx, Caddy, and Apache event MPMs. Persistent worker pools provide an ideal target for cross-request heap grooming.
FrankenPHP / Swoole / RoadRunner Vulnerable Vulnerable Critical: These modern application servers keep the entire application booted in memory across requests (worker mode). Residual memory corruption can destabilize or compromise all concurrently handled coroutines.
PHP CLI / Ephemeral CGI Vulnerable Mitigated Moderate: Because the process exits immediately when the HTTP request finishes, the cross-request Use-After-Free flaw cannot be triggered. However, single-request OOM memory corruption remains theoretically exploitable.

Detection & Threat Hunting Rules

Enterprise SOCs and threat hunters should immediately query their endpoint telemetry and network monitors for indicators of php2shell weaponization.

1. Linux `auditd` Rules: Catching Child Shell Spawning

Web servers should almost never spawn interactive shells. Place the following audit rules in /etc/audit/rules.d/web-exploit.rules to flag child process execution originating from web daemon accounts:

# Monitor execution of shells from web server user accounts
-a always,exit -F arch=b64 -S execve -F euid=33 -k web_shell_exec
-a always,exit -F arch=b32 -S execve -F euid=33 -k web_shell_exec

# Monitor writes to writable web directories and temp locations
-w /tmp -p wa -k web_tmp_write
-w /dev/shm -p wa -k web_shm_write

2. Falco Rule: Containerized PHP RCE Detection

For Kubernetes and containerized Docker environments running PHP:

- rule: PHP Worker Spawned Shell
  desc: Detects an interactive shell spawned by a PHP-FPM or Apache worker
  condition: >
    spawned_process and 
    proc.name in (bash, sh, dash, zsh, ksh) and 
    proc.pname in (php-fpm, apache2, httpd, php)
  output: >
    "Critical: Shell spawned by PHP runtime (user=%user.name command=%proc.cmdline parent=%proc.pname container_id=%container.id)"
  priority: CRITICAL
  tags: [security, php, rce, mitre_execution]

3. Suricata / Snort IDS Signature: Anomaly Detection

Detecting anomalous multipart requests designed to trip Zend Engine OOM limits:

alert http $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
    msg:"ATTACK-RESPONSES PHP Zend Engine OOM Heap Grooming Attempt (CVE-2026-109618)";
    flow:to_server,established;
    content:"POST"; http_method;
    content:"multipart/form-data"; http_header;
    pcre:"/Content-Length\x3a\s*(?:1[0-9]{8,}|[2-9][0-9]{7,})/Hi";
    threshold:type both, track by_src, count 5, seconds 60;
    classtype:web-application-attack;
    sid:2026109618; rev:1;
)
Web Server Log Telemetry Signatures - Repeated HTTP 500 responses paired with exact zero-byte or 1-byte responses on non-API endpoints. - PHP-FPM error logs containing: WARNING: [pool www] child <PID> exited on signal 11 (SIGSEGV) or signal 6 (SIGABRT). - Sudden spikes in outbound connections to non-standard TCP ports (4444, 1337, 8888, 9001) originating from www-data, apache, or nobody. - Modification of /var/log/php-fpm.log or crash dumps appearing in /var/crash/ or /tmp/core.*.

Enterprise Hardening & The EOL Dilemma

While upgrading to patched packages is mandatory, millions of enterprise applications remain handcuffed to unsupported End-of-Life PHP runtimes. If your organization cannot upgrade today due to application dependencies, apply these compensating controls immediately:

Mitigation Layer Configuration Directive Security Mechanism
PHP-FPM Worker Recycling pm.max_requests = 10 Forces each PHP-FPM worker process to terminate and respawn after handling 10 requests, neutralizing multi-stage Use-After-Free heap retention (CVE-2026-109619).
System-Level Isolation ProtectSystem=strict
NoNewPrivileges=true
In the Systemd unit (php-fpm.service), marks the entire filesystem as read-only and prevents privilege escalation even if code execution occurs.
Dangerous Function Disabling disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, dl, show_source Restricts the attacker's ability to pivot from an arbitrary PHP function call to an interactive shell or external network beacon.
Edge WAF Restrictions Max Request Body: 8MB
Max Headers: 50
Blocks payloads engineered to consume megabytes of memory before application code can evaluate input sanitization rules.

The Continuous Pentesting Reality Check

Vulnerabilities like php2shell reveal a harsh truth about modern security assurance: point-in-time vulnerability scans and annual penetration tests are structurally incapable of protecting organizations from runtime zero-days and embargo collapses.

When CVE-2026-109618 and CVE-2026-109619 leaked, traditional scanners had zero detection plugins available. Commercial scanning engines take days—sometimes weeks—to write, test, and distribute reliable checks for memory-corruption bugs that require precise heap grooming.

More critically, traditional testing fails on three operational fronts:

  1. Shadow & Embedded Runtimes: Most organizations don't realize they have PHP exposed. It sits embedded inside printer firmware, VPN management consoles, remote monitoring agents (RMMs), and internal staging sites that developers pushed live to test marketing landing pages.
  2. The Post-Disclosure Patching Lag: Between the moment a PoC drops on GitHub and the moment an enterprise finishes change-management approval to patch production, attackers have an average window of 14 days to exploit unpatched systems with automated mass scanners.
  3. Architectural Blindspots: Vulnerability scanners cannot tell you that your PHP-FPM workers are running with pm.max_requests = 0 (unlimited lifetime), leaving them permanently primed for Use-After-Free attacks. Only penetration testing provides that contextual analysis.

How Lorikeet Security's PTaaS Changes the Equation:
Lorikeet Security pairs continuous external attack surface management (EASM) with continuous automated and human-led penetration testing. When a critical runtime vulnerability like php2shell drops, our platform correlates your live external footprint against active proof-of-concept indicators within hours.

Instead of waiting months for your next annual pentest, your engineering team receives verified, asset-specific findings with remediation instructions before commodity threat actors turn leaked code into a corporate breach.


Sources & Authoritative References

Know Your Vulnerable Footprint Before Attackers Do

Don't let unpatched PHP instances and shadow web applications compromise your perimeter. Lorikeet Security provides continuous penetration testing and attack surface management to detect, validate, and help you remediate critical runtime exposures in real time.

196 views
Link copied!
Lorikeet Security

Lorikeet Security Team

Penetration Testing & Cybersecurity Consulting

Lorikeet Security helps modern engineering teams ship safer software. Our work spans web applications, APIs, cloud infrastructure, and AI-generated codebases — and everything we publish here comes from patterns we see in real client engagements.

Lory waving

Hi, I'm Lory! Need help finding the right service? Click to chat!