PHP2Shell is a family of critical vulnerabilities in core PHP, tracked as CVE-2026-109618 and CVE-2026-109619, that were recently patched with severity scores reaching CVSS 9.2. These flaws can allow unauthenticated attackers to achieve remote code execution or disclose sensitive process memory. The issues are particularly significant because PHP remains the dominant server-side language on the web, powering roughly 70% of websites with a known server-side technology, making the potential attack surface exceptionally large.

CVE-2026-109618 is a critical remote code execution vulnerability in PHP’s out-of-memory handling. Under specific conditions, memory exhaustion can interrupt request initialization and leave internal data structures in an inconsistent state, ultimately leading to memory corruption and potential code execution.

CVE-2026-109619 is a remote code execution vulnerability caused by stale request state persisting after its associated memory has been freed. When a subsequent request reuses the same worker, these dangling references can trigger a use-after-free condition, potentially resulting in memory disclosure, memory corruption, and remote code execution.

The official PHP security advisory identifies the following affected versions:

  • 8.2 releases before 8.2.35
  • 8.3 releases before 8.3.36
  • 8.4 releases before 8.4.27
  • 8.5 releases before 8.5.12

Every SAPI that serves web requests is affected: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed.

CVE-2026-109618

CVE-2026-109618 is a critical memory-safety vulnerability in PHP’s request lifecycle. When PHP encounters an out-of-memory condition, it uses longjmp() through zend_bailout() to immediately abort the current execution context. However, certain request-processing paths can expose partially initialized internal structures before they are fully constructed. If an allocation fails at exactly that point, execution is aborted prematurely, leaving request state in an inconsistent condition. During request shutdown, PHP later treats this corrupted state as valid, resulting in memory corruption that can ultimately lead to remote code execution.

Instead of unwinding the stack normally, longjmp() transfers control directly to a recovery point. While this allows PHP to recover quickly from fatal conditions such as OOM errors, it also means that the intermediate cleanup logic is skipped and partially initialized structures are not rolled back.

Request startup
    |
    +--> Allocate structure A
    |
    +--> Partially initialize structure A
    |
    +--> Another allocation fails
             |
             +--> zend_bailout()
                     |
                     +--> Skips remaining initialization
                     |
                     +--> Request shutdown executes
                                 |
                                 +--> Uses structure A
                                      believing it is valid

The problem is, request-owned memory can be reachable before it is fully initialized. If an allocation fails during that window (for example while the multipart parser is building request state), the longjmp() leaves that memory in a half-initialized, invalid condition. Request shutdown then runs and reads or branches on that invalid state. Because an attacker can steer which allocation fails (by tuning memory_limit against a crafted request body), the “invalid state” becomes attacker-influenced, and that is the corruption primitive.

/* php/php-src/Zend/zend_alloc.c - PHP 8.4.4 (vulnerable code) */
static ZEND_COLD ZEND_NORETURN void zend_mm_safe_error(zend_mm_heap *heap, const char *format, size_t limit, /* ... */, size_t size)
{
    heap->overflow = 1;
    zend_try {
        zend_error_noreturn(E_ERROR, format, limit, /* ... */, size);
    } 
	zend_catch {
    }  zend_end_try();
    heap->overflow = 0;
    zend_bailout();
    exit(1);
}

The danger is the zend_bailout() on a stack where request memory is only partially set up. The jump skips the code that would have finished initializing or validating that memory, and later shutdown consumes it.

According to the PHP security advisory, researchers identified several ways to trigger memory exhaustion during multipart HTTP request processing:

  • Multipart requests without a usable Content-Length
  • Excessive multipart headers
  • Empty file-upload controls and nested fields

CVE-2026-109619

While CVE-2026-109618 is about partially initialized state caused by OOM bailouts, CVE-2026-109619 is about stale state surviving after its memory has been freed.

PHP workers are long-lived processes that maintain global state across multiple requests, some of which temporarily reference request-specific memory. CVE-2026-109619 occurs when this state is not properly cleared, either because fatal errors bypass the normal cleanup process or because shutdown routines inadvertently restore previously reset data. As a result, global references can persist after the underlying request memory has been freed, leaving behind dangling pointers that may later be dereferenced by subsequent requests, resulting in a use-after-free condition.

When the same worker later processes a new request, those stale references may be dereferenced before normal request initialization has an opportunity to rebuild or clear them. This can occur in several early-execution code paths, including the fatal_error_backtraces functionality in PHP 8.5. As a result, PHP may access memory that no longer belongs to the original request, triggering a use-after-free vulnerability.

Request A
    |
    +--> Allocate request memory
    |
    +--> Global state references request data
    |
    +--> Request ends
    |
    +--> Request memory freed
    |
    +--> Global reference survives
           (dangling pointer)
                  |
                  v
Request B
    |
    +--> Reuses same PHP worker
    |
    +--> Accesses stale global state
    |
    +--> Use-after-free
    |
    +--> Memory disclosure / corruption
    |
    +--> Potential RCE

According to the PHP security advisory, researchers identified two distinct way to trigger the use-after-free vulnerability:

  • Fatal error backtraces (in PHP 8.5 against Apache mod_php while fatal_error_backtraces is enabled)
  • Session upload progress (against Apache mod_php and PHP-FPM)

Proof of Concept (PoC)

Multiple exploitation techniques have been developed for PHP2Shell, demonstrating that both vulnerabilities can be weaponized through different code paths and runtime conditions. For researchers interested in understanding the original disclosure and exploitation methodology, the first publicly released proof-of-concept was published by @acd421 and @Archknight23 at https://jackalsyn.com/posts/php-up-2026.html.

1

While subsequent research has produced more reliable and mature exploitation techniques, a more recent and stable implementation can be found in the Metasploit Framework module at https://github.com/rapid7/metasploit-framework/pull/22036, which serves as a useful reference for defenders and security researchers studying the practical impact of these flaws.

Mitigation and Detection Strategies

The first priority would be to identify every PHP server in the environment and, where a system is vulnerable, patch it immediately (version 8.5.12, 8.4.27, 8.3.36 and 8.2.35). Both vulnerabilities are exploitable without authentication, and some attack chains require no application-level flaw, so a single unpatched or forgotten host is enough to give an attacker a foothold.

Asset discovery should extend beyond production web servers:

  • Bare-metal and virtual hosts running Apache mod_php or PHP-FPM
  • Container images and base layers, including outdated tags that remain deployed
  • Third-party and self-hosted applications built on PHP, such as CMS platforms, admin panels and ticketing systems

An high fidelity scanner to identify such vulnerable applications have already been published by Searchlight Labs at https://github.com/assetnote/php2shell-scanner.

Configuration changes reduce exposure but don’t remove the bug. The memory corruption behind CVE-2026-109618 lives in the Zend engine itself. If you can’t patch today, apply these as a stopgap:

session.upload_progress.enabled = Off
session.use_only_cookies = On

Regarding detection strategies, the quickest control is to check and block suspicious network communications with unexpected fields like the presence of PHP_SESSION_UPLOAD_PROGRESS or PHPSESSID (usually set as a Cookie header) in POST bodies.

alert http $EXTERNAL_NET any -> $HOME_NET any ( \
    msg:"PHP2Shell exploitation attempt - multipart request with PHP_SESSION_UPLOAD_PROGRESS in POST body."; \
    flow:established,to_server; \
    http.method; content:"POST"; \
    http.content_type; content:"multipart/form-data"; \
    http.request_body; content:"PHP_SESSION_UPLOAD_PROGRESS"; \
    sid:1000001; rev:1; \
    priority:10; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

alert http $EXTERNAL_NET any -> $HOME_NET any ( \
    msg:"PHP2Shell exploitation attempt - multipart request with PHPSESSID in POST body."; \
    flow:established,to_server; \
    http.method; content:"POST"; \
    http.content_type; content:"multipart/form-data"; \
    http.request_body; content:"name=|22|PHPSESSID|22|"; \
    sid:1000002; rev:1; \
    priority:10; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

As discussed during the CVE-2026-109618 technical analysis, extra checks with a lower severity can be added to monitor and block possible attempts to trigger the OOM bypass like checking for abnormally long filenames during PHP_SESSION_UPLOAD_PROGRESS requests or checking for missing Content-Length fields.

alert http $EXTERNAL_NET any -> $HOME_NET any ( \
    msg:"PHP2Shell exploitation attempt - multipart PHP_SESSION_UPLOAD_PROGRESS request with abnormally long filename."; \
    flow:established,to_server; \
    http.method; content:"POST"; \
    http.content_type; content:"multipart/form-data"; \
    http.request_body; content:"PHP_SESSION_UPLOAD_PROGRESS"; \
    http.request_body; content:"filename=|22|"; \
    isdataat:10000,relative; \
    sid:1000003; rev:1; \
    priority:5; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

alert http $EXTERNAL_NET any -> $HOME_NET any ( \
    msg:"PHP2Shell exploitation attempt - missing Content-Length field."; \
    flow:established,to_server; \
    http.method; content:"POST"; \
    http.content_type; content:"multipart/form-data"; \
    http.header; content:"Transfer-Encoding"; content:"chunked"; \
    sid:1000004; rev:1; \
    priority:5; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

Obviously, it’s also possible to check for the CVE-2026-109618 OOM bypass attempts based on the server responses and checking for OOM-related errors like “Allowed memory size of %zu bytes exhausted”.

alert http $HOME_NET any -> $EXTERNAL_NET any ( \
    msg:"PHP2Shell OOM bypass attempt."; \
    flow:established,to_client; \
    http.response_body; content:"Allowed memory size"; \
    content:"exhausted"; distance:0; within:100; \
    sid:1000004; rev:1; \
    priority:10; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

alert http $HOME_NET any -> $EXTERNAL_NET any ( \
    msg:"PHP2Shell OOM bypass attempt."; \
    flow:established,to_client; \
    http.response_body; content:"Session object destruction failed"; \
    sid:1000005; rev:1; \
    priority:10; \
    metadata: tlp green, author Andrea Naspi, created_at 2026_10_24, updated_at 2026_10_24; \
    reference:cve,2026-109618; \
    reference:cve,2026-109619;
)

In conclusion, network-level detection should be complemented by host-based telemetry, since exploitation attempts that evade or bypass perimeter controls will still leave traces on the system. The priority would certainly be to monitor for shells spawned by PHP processes itself: an attacker can abuse an exploited Apache mod_php or PHP worker to spawn a shell as a child process for subsequent remote code execution activities.

title: PHP2Shell post-exploitation - shell through PHP process
id: aef58c14-c269-401f-871b-4735d37eee49
status: experimental
description: Detects a shell spawned as a child process of a PHP worker
author: Andrea Naspi
date: 2026-10-24
logsource:
  product: linux
  category: process_creation
detection:
  selection_php:
    ParentImage|endswith:
      - '/php-fpm'
      - '/php-cgi'
      - '/php'
  selection_rce:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/python'
      - '/perl'
      - '/ruby'
      - '/nc'
      - '/ncat'
      - '/curl'
      - '/wget'
  condition: selection_php and selection_rce
level: high
falsepositives:
  - PHP applications that legitimately exec shell commands (cron jobs, image processing)
tags:
  - tlp.green
  - attack.execution
  - attack.t1059

Another good coverage for the host-based telemetry to directly spot PHP log patterns associated with the PHP2Shell exploitation attempts is available through the official SigmaHQ repository at https://github.com/SigmaHQ/sigma/pull/6506.