Security research6 min read

Critical PHP Vulnerabilities Discovered October 2026

Three security vulnerabilities fixed in PHP 8.5.12 and beyond — two critical RCE vectors via OOM corruption and stale global state, plus an out-of-bounds read in the HTTP stream wrapper.

Critical PHP Vulnerabilities Discovered October 2026

On October 21-22, 2026, the PHP development team released PHP 8.5.12 along with security updates for PHP 8.4.27, 8.3.36, and 8.2.35. Three CVEs were addressed.

CVE Severity Title Affected Versions Patched In
CVE-2026-109618 Critical RCE via OOM corruption < 8.2.35, < 8.3.36, < 8.4.27, < 8.5.12 8.2.35, 8.3.36, 8.4.27, 8.5.12
CVE-2026-109619 Critical RCE via stale global state < 8.2.35, < 8.3.36, < 8.4.27, < 8.5.12 8.2.35, 8.3.36, 8.4.27, 8.5.12
CVE-2026-93682 Moderate Out-of-bounds read in HTTP stream wrapper < 8.2.34, < 8.3.35, < 8.4.26, < 8.5.11 8.2.34, 8.3.35, 8.4.26, 8.5.11

CVE-2026-109618: RCE via OOM Corruption

Severity: Critical • CVSS 7.5 (AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H)

Summary

PHP uses longjmp() for out-of-memory (OOM) errors to exit the current context and abort request handling. In certain scenarios, when memory allocation fails at an early initialization point, the request memory is left in an invalid state. An attacker can trigger multiple OOM strategies through carefully crafted input, leading to remote code execution.

How the Vulnerability Works

The bug exploits PHP's memory management during request processing. Multiple OOM pathways can be triggered:

  • Multipart requests without usable content length: A request without a declared content length may pass the initial post_max_size check and continue supplying decoded body data.
  • Unrestricted headers in multipart parts: A multipart part can contain an unrestricted number of headers, consuming substantially more memory than their wire representation.
  • Application-specific pathways: Attacker-controlled input can cause large allocations or repeated memory use to reach OOM.

When OOM is triggered, PHP's longjmp() exits the current context without properly cleaning up request memory, leaving it in an invalid state. Subsequent request operations can then read or branch on this corrupted memory, and with a strategically crafted heap layout, this leads to full remote code execution.

Exploitation Requires Producer and Consumer

  • Producer: A PHP operation that stores state owned by one request in a location accessible later in a subsequent request.
  • Consumer: A subsequent PHP operation that reads, frees, branches, or otherwise acts on the stale state.

State can survive because:

  • A fatal error (including one caused by memory exhaustion) can abandon an operation before it performs its matching reset.
  • A warning or other nonfatal diagnostic can also repopulate error state after that state was already cleared during shutdown.
  • The request heap is then freed while the worker global field still refers to it.
  • A later operation that runs before normal request initialization can consume the stale value.

Patch Summary

The fix ensures proper reset of global state before request memory is released, and adds validation that state is properly initialized before being consumed in subsequent requests.


CVE-2026-109619: RCE via Stale Global State

Severity: Critical • CVSS 7.5 (AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H)

Summary

This vulnerability stems from improper handling of PHP's global state across request boundaries. When certain operations fail to reset global state, stale pointers can be consumed by subsequent requests, leading to use-after-free conditions and ultimately remote code execution.

How the Vulnerability Works

PHP contains various global state variables (error state, session state, object state, and extension state) that are retained by a worker process but point to memory owned by a specific request. This state must be properly reset before the request memory is released and the next request begins.

The failure scenario:

  1. A fatal error or warning occurs during request processing
  2. The error/warning handler repopulates global state (e.g., $php_errormsg, $_SESSION, error handlers) after that state was already cleared during shutdown
  3. The request heap is freed while the worker global field still refers to the old memory location
  4. A subsequent request — before normal initialization completes — consumes the stale value
  5. Successful exploitation requires a strategically crafted heap layout to convert the use-after-free into RCE

Triggering Vectors

  • Fatal errors (including those caused by memory exhaustion) that abandon operations before matching reset
  • Warnings and other nonfatal diagnostics that repopulate error state after it was already cleared
  • Session-related operations that don't properly reset state
  • Error handling code paths that run before normal request initialization

Patch Summary

The fix adds comprehensive reset of all global state at request shutdown, ensures state is only accessed after proper initialization, and adds debugging assertions to catch state leaks during development.


CVE-2026-93682: Out-of-Bounds Read in HTTP Stream Wrapper

Severity: Moderate • CVSS 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Summary

A vulnerability in PHP's HTTP stream wrapper. When following a redirect with an empty Location header, the code reads one byte past the end of a heap buffer, which determines the redirect target. While this doesn't directly enable RCE, it can cause non-deterministic behavior and is detectable by security tools.

How the Vulnerability Works

The HTTP stream wrapper (php_stream_http_response_headers_parse()) stores the Location header value in a heap buffer of exactly location_len + 1 bytes. When the Location header has an empty value:

  1. The buffer is allocated with emalloc(last_header_value_len + 1) where last_header_value_len = 0
  2. The buffer contains only the NUL terminator
  3. During redirect handling, the code reads the second byte of the location without proper bounds checking
  4. This reads one byte past the intended buffer, an out-of-bounds read

The read byte decides which redirect target is built:

  • If the byte is NUL (0), the client is redirected to the host root (/)
  • If the byte is any other value, the client is redirected to the current directory path

An attacker hosting a malicious server can control which target the client is sent to, potentially facilitating open redirect attacks.

Proof of Concept

$ctx = stream_context_create([
    'http' => ['follow_location' => 1]
]);
file_get_contents("http://host/a/b", false, $ctx);

A server responding with:

HTTP/1.1 302 Found
Location:
Content-Length: 0
 

Will cause non-deterministic redirect behavior depending on stray memory contents.

Mitigation and Recommendations

Immediate Actions

  1. Upgrade PHP Immediately

  2. Verify Current Version

    php -v
    # Should show 8.5.12 or newer for 8.5 series
  3. For Load Balancers and Proxies

    • Ensure reverse proxies (NGINX, Caddy, HAProxy) strip or validate Location headers in 302 responses
    • Monitor for unexpected redirect behavior in application logs
  4. For Multipart Form Submitters

    • Limit post_max_size and max_upload_filesize to appropriate values
    • Validate Content-Type and Content-Length on incoming requests
    • Consider adding proxy-level multipart parsing validation

Long-Term Hardening

  • Enable opcache with opcache.enable=1 and opcache.revalidate_freq=2 to reduce re-entry points
  • Use PHP-FPM with pm.max_children set to prevent memory exhaustion across children
  • Monitor PHP error logs for repeated warnings about state corruption
  • Implement health checks that verify PHP version and configuration after each deployment

Detection

Security teams can detect exploitation attempts by:

  • Looking for 302 Found responses with empty Location headers in web server logs
  • Monitoring for unusual redirect patterns in application analytics
  • Checking for use-after-free crashes in PHP error logs (CVE-2026-109618/109619)

Affected PHP Versions Summary

Series Before Fix After Fix
PHP 8.5 < 8.5.11 8.5.12
PHP 8.4 < 8.4.26 8.4.27
PHP 8.3 < 8.3.35 8.3.36
PHP 8.2 < 8.2.34 8.2.35
PHP 8.1 Not affected (these CVEs affect 8.2+) N/A

References

Continue reading