Search
moon
sun

PHP2Shell: Full-Chain RCE Exploitation of CVE-2026-109618 and CVE-2026-109619

태그
PurpleHound
Property
php2shell-intro.jpg
작성날짜
2026/10/22

1. Overview

In October 2026, two PHP core remote code execution (RCE) vulnerabilities leaked before the embargo was lifted and spread under the nickname php2shell. The name originated from an exploit tool shown in leaked screenshots and is not an official PHP designation. Both vulnerabilities were assigned a CVSS v4.0 score of 9.2 (Critical) and were reported to allow unauthenticated remote code execution.
Many hypotheses published immediately after the leak pointed to a Use-After-Free (UAF) vulnerability in the session.upload_progress handler. However, the vendor did not disclose the detailed mechanism, and many early proofs of concept could not be reproduced.
This report analyzes an attack chain that begins with a dangling pointer (UAF) caused by longjmp at the time of an out-of-memory (OOM) condition in the official php:8.4.26-apache environment (mod_php, prefork). We reproduced the complete chain from information disclosure and ASLR bypass to heap reoccupation and arbitrary command execution with www-data (web server) privileges.
Because the vulnerability is triggered during request parsing before application code is executed, it affects deployments regardless of the framework in use, including WordPress.

2. Root Cause Analysis

2.1. Key Information

Item
Details
CVE
CVE-2026-109618 / CVE-2026-109619
Type
Use-After-Free (CWE-416); the underlying cause is improper handling of exceptional conditions (CWE-755)
Severity
CVSS v4.0 9.2 (Critical), no authentication required
Affected components
ext/session (upload progress), main/rfc1867.c (multipart parser)
Affected configurations
session.upload_progress.enabled=On, mod_php / lsphp (SAPIs with persistent workers)
Confirmed version
PHP 8.4.26 (before the fix)

2.2. Abnormal Control-Flow Escape During OOM

When a request body exceeds memory_limit, PHP 8.4.26 immediately raises a fatal error at the allocation site and transfers control through zend_bailout().
*/* Zend/zend_alloc.c : zend_mm_safe_error() */* heap->overflow = 1; zend_try { zend_error_noreturn(E_ERROR, format, ...); */* "Allowed memory size ... exhausted" */* } zend_catch {} heap->overflow = 0; zend_bailout(); */* longjmp to the setjmp site, escaping through the current call stack */*
JavaScript
복사
[Code 1] On an out-of-memory (OOM) condition, control flow immediately escapes through longjmp.
The problem occurs when this allocation failure takes place inside the multipart parser (rfc1867_post_handler()). The parser is abandoned while processing a part, so its resources are not cleaned up and the session-side completion event (MULTIPART_EVENT_END) is never executed.
Furthermore, this parsing occurs inside php_hash_environment(), before zend_activate_modules() is called. Therefore, when the longjmp unwinds the control flow, PG(modules_activated) is still 0.
*/* main/main.c : php_request_shutdown() */* if (PG(modules_activated)) { */* still 0 */* zend_deactivate_modules(); */* ext/session RSHUTDOWN is skipped entirely */* }
JavaScript
복사
[Code 2] Because the escape occurs before the modules are activated, request-shutdown cleanup for the session module is skipped.

2.3. Creation of the Dangling Pointer (UAF)

During session initialization, pointers are stored in the session global structure. Representative examples include PS(id), which points to the session ID string, and PS(http_session_vars), which holds the reference containing $_SESSION.
When a request is interrupted through the control flow shown in Code 1 and Code 2, the heap objects referenced by these pointers are freed, but the pointers in the session globals are not updated. Because the RSHUTDOWN backstop is also skipped, pointers to the freed memory remain alive until the next request.
With mod_php (prefork), the worker process survives the OOM request. If the next request reaches the same worker, the freed memory is referenced again. This is the Use-After-Free condition at the root of the vulnerability.
In our testing, the worker had to survive between requests for the dangling pointers to persist. Under php-fpm, the same OOM condition restarted the worker and cleared the dangling state, so this exploitation path did not succeed. This path is therefore limited to SAPIs with persistent workers, such as mod_php and LiteSpeed lsphp.

2.4. Vulnerability Demonstration Video

3. Attack Impact

Starting from a single Use-After-Free condition, the attack proceeds through the following stages.

3.1. UAF-Based Information Disclosure → ASLR Bypass

The attacker reoccupies the freed region still referenced by the dangling PS(id) pointer with attacker-controlled data. By placing a fake zend_string with an excessively large length field in this region, the server returns adjacent heap memory when it writes the session ID to the Set-Cookie response header, resulting in a heap out-of-bounds read.
The following information can be recovered from the leaked bytes:
•
The base address of a Zend MM 2MB chunk (heap base)
•
The libphp.so load base, identified through repeated occurrences of the zval_ptr_dtor pointer
This defeats ASLR and allows the actual address of zif_system, PHP’s internal system handler, to be calculated as libphp_base + <static offset>.

3.2. UAF Reoccupation → Type Confusion → Arbitrary Function Call

The freed page still referenced by the dangling PS(http_session_vars) pointer is reoccupied with an attacker-crafted fake zend_reference → fake array (pDestructor = zif_system) plus fake execute_data.
When the reference is later destroyed, the engine calls pDestructor during the array destruction routine. Because this value has been replaced with zif_system, system() is ultimately invoked with the attacker-controlled command string as its argument.

3.3. Threat Scenario

•
RCE before authentication and application code execution: Command execution occurs during session and request parsing, before login or authorization checks. It is independent of the framework in use.
•
Confirmed in testing: We confirmed command execution as uid=33(www-data) through the request chain against the official php:8.4.26-apache image.
•
Memory-safety mitigations bypassed: Although this is a memory-corruption vulnerability, the information leak bypasses ASLR. Therefore, ASLR, DEP, and similar mitigations do not prevent exploitation.
•
Preconditions and limitations: Successful exploitation requires:
1.
session.upload_progress.enabled=On
2.
Uploads capable of exceeding memory_limit
3.
A SAPI whose workers survive OOM, such as mod_php or lsphp. Exploitation is particularly stable when a single worker makes heap grooming deterministic. Under php-fpm, the worker restart caused by OOM prevented this exploitation path.

3.4. Indicators of Compromise and Attack Artifacts

Source
IOC
PHP error log
Repeated Allowed memory size ... exhausted errors
Apache error log
Repeated child Segmentation fault entries
Processes
apache2 spawning sh, id, or sleep child processes
Session storage
sess_attacksessionid0001, sess_aaaaaaaaaaaaaaaaaaa
upload_tmp_dir
Rapid increase in orphaned php* temporary files
Access log
Burst of 12MB multipart POST requests and 500 responses, no User-Agent, and GET /out/z
Request body (WAF)
Boundaries BNDRY12345 / S×17, SPRAY###, and binary field 1999
•
[Table 1] Summary of Key Indicators of Compromise (IOCs)
3.4.1. Web Server Access Log (access.log)
•
Flood of oversized POST requests: The _oom() field is 12MB, making nearly every request a POST request larger than 12MB to /index.php. Hundreds of requests may arrive from a single IP address within a short period.
•
Mixed response codes: OOM requests return 500, while leak and rotate requests return 200. The fetch option issues GET /out/z, which initially returns repeated 404 responses and then returns 200 after successful command execution.
•
Connection: close, no User-Agent (), and Content-Type: multipart/form-data.
3.4.2. PHP Error Log (log_errors / stderr)
PHP Fatal error: Allowed memory size of 8388608 bytes exhausted
(tried to allocate N bytes) in Unknown on line 0
•
rotate, stage1, stage2, stage3, and baseline prime all trigger OOM conditions, producing dozens to hundreds of log entries per attack.
3.4.3. Apache Error Log and Crash Artifacts
•
Worker segmentation faults: Numerous entries such as [core:notice] AH00052: child pid N exit signal Segmentation fault (11).
•
Core dumps and crash reports: Depending on core_pattern, /var/crash/apache2* files generated by apport or a core file may be created. Depending on the system configuration, dmesg may also contain apache2[...] segfault entries.
3.4.4. Session File Creation
When use_strict_mode=0, an attacker-supplied session ID is used directly as the filename under session.save_path, such as /var/lib/php/sessions.
•
sess_attacksessionid0001 — the SID used by the exploit
•
sess_aaaaaaaaaaaaaaaaaaa — "a"*19
•
The files may contain remnants such as upload_progress_...|a:5:{...}, including the filename a.txt and attacker-controlled fields. The fixed session IDs are strong signatures on their own.
3.4.5. Upload Temporary Files (upload_tmp_dir)
•
The file part of each request (f=a.txt, containing 64 bytes of AAAA…) is interrupted by the OOM condition and loses its normal cleanup path. As a result, php****** temporary files accumulate in proportion to the number of requests and remain unremoved, leaving disk and inode artifacts.
3.4.6. Command-Execution (system) Artifacts — Most Critical
•
File: cmd 'id>/var/www/html/out/z' creates /var/www/html/out/z. Its contents include uid=33(www-data)..., and the file is owned by www-data. No output file is created in sleep mode.
•
Process lineage: apache2 → sh -c "<cmd>" → id/sleep/.... www-data spawning a shell is an abnormal parent-child relationship and a key IOC for EDR and behavior-based detection.
•
auditd / execve: execve records may show /bin/sh -c, id, sleep 5, and similar commands. These executions may also remain in process accounting records such as sa and lastcomm.
3.4.7. Network and Request-Body Signatures (WAF / IDS)
•
Multipart boundaries: ---… (S × 17), BBBBBBBBBB (B × 10), and BNDRY12345 are distinctive values.
•
Field-name patterns: PHP_SESSION_UPLOAD_PROGRESS with the value pwn, f0000…f1000, p0000…, filler fields 1000…1892 (v0000x…), array fields a0000[0]…, and spray fields s000…s399.
•
Spray body: Thorough mode (_spray_value) contains the ASCII markers SPRAY000…SPRAY399, which are clearly identifiable during request-body inspection. Packed mode, which is the default, contains no markers and instead uses . × 4071 followed by binary forged structures, including zend_reference and HashTable byte patterns such as 0x307 and 0xFFFFFFF8.
•
Field 1999: Non-printable bytes followed by an L marker and an oversized 8-byte length value (0x1000 / 0x20000). This is a signature of the out-of-bounds read trigger.
•
Timing: When sleep-oracle is used, one successful request is delayed by sleep 5, resulting in a response time of approximately five seconds or more.
3.4.8. Memory (Volatile)
•
The forged zend_reference, HashTable, and execute_data structures, along with the leaked addresses, exist only in worker memory and disappear when the worker exits. However, if a core dump is captured after a crash, it may contain remnants of the attack payload.

4. Mitigations and Recommendations

4.1. Patch

The fundamental mitigation is to patch the PHP runtime. Changes to applications such as WordPress or Laravel cannot prevent this vulnerability.
Branch
Before the Fix
Fixed Release
PHP 8.4.x
8.4.26
8.4.27
PHP 8.3.x / 8.2.x / 8.5.x
The immediately preceding release of each branch
The corresponding security release published at the same time
PHP 8.1 and earlier (EOL)
—
No official patch
In the fixed versions, PHP no longer aborts the operation abruptly when it runs out of memory. Instead, it safely terminates multipart request processing and exits after properly cleaning up the memory and session data in use.
This prevents invalid pointers to freed memory from remaining behind. The related session-data processing path that could trigger the same issue has also been secured.

4.2. Response Procedure

1.
Inventory all PHP installations across the organization, including servers, containers, shared hosting environments, and appliances.
2.
Immediately update to the fixed release for the relevant branch, and then restart PHP-FPM or Apache.
3.
As a temporary mitigation before patching, set session.upload_progress.enabled = Off. This prevents the callback from running and removes the point at which the dangling pointer is created.
4.
PHP 8.1 and earlier are end-of-life branches without official patches. Isolate affected systems, restrict access, and plan an upgrade.
5.
For detection, monitor PHP workers that spawn child processes during the early stages of request handling or restart after an abnormal termination or crash.
Changing session.serialize_handler or relying on memory-safety mitigations such as CFG, DEP, or ASLR does not provide effective protection against this vulnerability. The former does not remove the affected path, while the latter can be bypassed through the information leak.

4.3. Detection Methods

The following detection rules were tested by PurpleHound. They identify multipart POST requests containing the PHP_SESSION_UPLOAD_PROGRESS form field, which is a prerequisite for exploiting this vulnerability.
However, this field is also used by PHP’s legitimate upload-progress functionality. Therefore, a rule match does not by itself confirm an attack or successful code execution, and the possibility of false positives must be considered.
These rules are supplementary detection and blocking measures rather than complete protection against every attack. The fundamental remediation is to upgrade PHP to a version containing the security patch.
4.3.1. Snort
alert tcp any any -> any $HTTP_PORTS (msg:"purplehound: multipart POST with PHP_SESSION_UPLOAD_PROGRESS (CVE-2026-109618, CVE-2026-109619)"; flow:established,to_server; content:"POST"; http_method; content:"multipart/form-data"; http_header; nocase; content:"PHP_SESSION_UPLOAD_PROGRESS"; http_client_body; fast_pattern; pcre:"/(?i:content-disposition)[ \t]*:(?:[^\r\n]|\r?\n[ \t])*?\;\s*(?i:name)\s*=\s*(?:\x22PHP_SESSION_UPLOAD_PROGRESS\x22|PHP_SESSION_UPLOAD_PROGRESS(?=[\;\s]))/P"; reference:cve,2026-109618; reference:cve,2026-109619; classtype:web-application-attack; sid:1499040051; rev:1;)
JavaScript
복사
4.3.2. Suricata
alert http any any -> any any (msg:"purplehound: multipart POST with PHP_SESSION_UPLOAD_PROGRESS (CVE-2026-109618, CVE-2026-109619)"; flow:established,to_server; http.method; content:"POST"; http.content_type; content:"multipart/form-data"; nocase; http.request_body; content:"PHP_SESSION_UPLOAD_PROGRESS"; fast_pattern; pcre:"/(?i:content-disposition)[ \t]*:(?:[^\r\n]|\r?\n[ \t])*?\;\s*(?i:name)\s*=\s*(?:\x22PHP_SESSION_UPLOAD_PROGRESS\x22|PHP_SESSION_UPLOAD_PROGRESS(?=[\;\s]))/"; reference:cve,2026-109618; reference:cve,2026-109619; classtype:web-application-attack; sid:1499040052; rev:1;)
JavaScript
복사
4.3.3. Snort Detection and Suricata Blocking Results by Attack Stage
Attack Stage
Requests
Snort Detections
Suricata Blocks
Exploitability
Worker replacement
4
2/4
2/4
Partially feasible
Information leak
6
3/6
6/6
Not feasible — failed to obtain the base address required to bypass ASLR
RCE Stage 3
2
2/2
2/2
Not feasible
Total
12
7/12
10/12
RCE prevented
[Detection Result]
10/22-17:18:07.297678 [**] [1:1499040051:1] purplehound: PHP session upload progress field in multipart POST request (CVE-2026-109618, CVE-2026-109619) [**] [Classification: Web Application Attack] [Priority: 1] {TCP} 127.0.0.1:28286 -> 127.0.0.1:8088
•
Detection result for a multipart POST request containing PHP_SESSION_UPLOAD_PROGRESS
These detection rules were tested by 78ResearchLab’s PurpleHound. They detect multipart POST requests containing the PHP_SESSION_UPLOAD_PROGRESS form field, which is a prerequisite for exploiting this vulnerability.
Because this field is also used by PHP’s legitimate upload-progress functionality, a rule match alone cannot confirm an attack or successful code execution, and false positives must be considered. These rules should therefore be treated as supplementary detection and blocking measures rather than a complete solution. The fundamental remediation is to upgrade PHP to a version containing the security patch.

5. Conclusion

php2shell (CVE-2026-109618 / CVE-2026-109619) is not a flaw in a specific application. It is a memory-corruption vulnerability in the PHP runtime itself.
The root cause is a Use-After-Free condition in which a longjmp during OOM leaves session pointers referencing freed heap memory. By chaining this condition with information disclosure for ASLR bypass and heap reoccupation for type confusion, an attacker can achieve unauthenticated remote code execution.
This analysis confirmed that the complete attack chain succeeds in the official PHP 8.4.26 environment. Exploitation is limited to configurations where upload_progress is enabled and a persistent-worker SAPI is used.
The vulnerability is rated Critical, and PHP hosts exposed to the internet should be patched immediately. Until the patch is applied, setting session.upload_progress.enabled=Off is an effective temporary mitigation.

6. References

•
•
PHP Security Release Announcements — https://www.php.net/
•
CERT-EU Security Advisory 2026-018
This exploit must be used solely for vulnerability assessment and incident prevention. The user assumes full responsibility for any consequences arising from its use for any other purpose.