Blog / Security news

PHP2Shell: CVE-2026-109618 and the Runtime Boundary

PHP2Shell technical analysis: CVE-2026-109618, proposed memory-corruption stages, PHP version evidence, configuration dependencies, and fix verification.

Completed doctoral studies in Information SecurityUniversity defense passed · Final dissertation defense pending

The request reaches PHP before your controller can help

Your application can reject an upload after PHP has already processed part of its body. That ordering is the reason a runtime vulnerability deserves a different investigation from an unsafe upload controller. For CVE-2026-109618 and CVE-2026-109619, discussed publicly as PHP2Shell, the central question is where attacker-controlled request data crosses a boundary inside the interpreter.

I approach this incident through three layers: the confirmed security release, the runtime behavior visible in source, and the mechanism proposed by the reviewed implementations. This assessment is dated 21 October 2026 and uses static analysis of PHP 8.4.4, relevant PHP 8.5.4 code, proof-of-concept implementations reviewed for this analysis, reporting, and a reviewed demonstration. No independent exploitation was performed.

What PHP has confirmed

PHP’s 20 October notice confirms two critical vulnerabilities, each rated CVSS v4.0 9.2, with a security release scheduled for 21 October at 14:00 UTC. The retrieved advance notice reserves affected versions, fixed versions, and full technical details for that release. It does not itself identify the CVE numbers or upload progress as the root cause. The two identifiers and RCE description appear in the associated public reporting. [1, 2]

The Huntress report attributes reproduction of CVE-2026-109618 to watchTowr, while stating that Huntress had not reproduced the issues or observed related attacks in its partner environments as of 20 October. The reviewed demonstration shows effects consistent with command execution and file creation, including on WordPress and Drupal. The demonstration’s implementation has not been matched to the implementations reviewed here. These observations justify urgency while leaving the exact cause and deployment prerequisites to establish. [2]

Why upload progress belongs in the investigation

PHP can maintain session-backed progress information while parsing a multipart upload. The relevant state includes a request-wide byte counter, per-file counters, metadata, and completion flags. The feature works inside request processing, so a site’s visible upload form does not fully describe its runtime attack surface. PHP’s manual documents the feature and its configurable update frequency. [3]

In the reviewed source, FILE_START establishes file state, FILE_DATA updates counters, and END completes or cleans up tracking. The total-byte pointer is cached during initial construction; the current-file pointer is refreshed for each file. Counter writes precede the session-update helper. That order matters because authentication inside application code cannot automatically be assumed to protect earlier parsing. Access control at a proxy before PHP receives the request enforces a different boundary. [4]

Request path from HTTP through proxy, SAPI and multipart processing to the application, with session state beside the parser.
1: Request arrival. 2: Upstream access controls. 3: PHP integration. 4: Multipart parsing and session callbacks. 5: Application handling. This is the architectural path under investigation, not a confirmed CVE exploit trace.

The proposed chain, stage by stage

One reviewed proof of concept associates the first identifier with the total-byte counter; another associates the second with the current-file counter. Both propose a stale-pointer problem during repeated progress updates. These are the implementations’ proposed mappings, not mappings established by the retrieved vendor notice. Their causal model requires request reachability, creation of progress state, an allocation being freed before a later access, and useful control over the resulting corrupted state.

Turning that state into RCE also requires a reachable execution path and build-compatible addresses and structures. The demonstration displays an address-disclosure stage; the local framework’s symbol extraction does not establish an equivalent remote disclosure. Existing heap allocations do not prove later reuse of a particular freed slot, and two delayed socket writes do not prove two matching server callbacks. These are separate technical conditions. A successful chain has to connect them; the reviewed implementations leave that connection incomplete.

The lifetime question: who still owns the array?

A use-after-free means accessing an allocation after its lifetime has ended. Call the live progress array P and a separately decoded session array D. Equivalent contents do not make them the same allocation. On the ordinary successful update path in PHP 8.4.4, the helper increments the reference count before placing P into the session tree. Resetting that tree drops its reference, while the progress state still owns P. Replacing decoded D does not free P. The corresponding increment also exists in the reviewed PHP 8.5.4 helper. [4, 5, 6, 7]

This ownership model is the unresolved step in the proposed explanation. It does not clear either CVE or refute the separate demonstration. A complete root-cause account must identify a different path or condition that defeats the lifetime guarantee. Even end-of-request separation needs ordering analysis: with cleanup disabled, the old session tree still owns the original array at the final counter write, before the forced update and teardown. A pointer becoming stale alone is insufficient without a subsequent access. [4]

Three stages showing progress state retaining a reference to array P while the session acquires and releases its own reference.
Simplified ordinary-path ownership: 1: progress owns P. 2: the session holds an additional reference. 3: session reset releases its reference while progress still owns P. D denotes a separately decoded allocation. This is a static model, not a runtime trace.

Session poisoning crosses a different boundary

A reviewed third-party lab report describes a second route to execution: attacker-controlled session content reaching an application file-inclusion sink. The deliberately vulnerable test endpoint passes a request parameter directly to include(). The persistence profile disables progress cleanup and changes update timing and multipart limits. The lab report records execution under these conditions. This demonstrates the kind of application boundary to audit, but it does not independently establish either CVE’s memory-corruption mechanism.

The same report reports session-ID selection without a Cookie header, session fixation in a deliberately permissive login fixture, and a serialization failure associated with a pipe character in a progress key. These observations depend on the handler, serializer, session policy, and application behavior. Session content must remain data; include/require paths should come from a controlled allowlist, and authentication transitions should replace the session identifier. A progress record alone does not demonstrate a freed allocation being reused.

The transcript’s uid=0 result describes the identity of its lab process. The launcher starts the PHP development server without specifying a reduced user; that output does not demonstrate privilege escalation or host-root access. This separation matters operationally: fixing an unsafe include sink and session policy addresses application-level exposure, while a runtime memory-safety flaw still requires its own vendor fix.

Affected versions and exploit compatibility are different questions

The retrieved vendor notice does not provide a confirmed affected-version range or fixed-version matrix. Broad claims covering PHP 5 onward remain claims in public reporting. PHP 8.4.4 is our source-analysis baseline; PHP 8.5.4 appears in the reviewed material and a displayed demonstration profile. Neither observation establishes the full affected range. Treat this as an evidence snapshot, and use the detailed vendor advisory for package decisions. [1, 8]

A vulnerability may span several branches while a particular exploit supports one architecture, package build, or server integration. Conversely, a failing PoC does not establish that a deployment is safe. The demonstration includes profiles described as Apache prefork with mod_php and PHP-FPM; it does not establish universal SAPI coverage. Inventory the actual serving build and integration, rather than inferring exposure from the CMS version or a CLI binary alone.

The reviewed third-party experiment report reports no command execution in its memory-corruption attempts on php:8.4.4-cli, php:8.4.4-fpm, and php:8.2.34-fpm, despite locally resolved addresses. These are the report author’s results, not tests performed for this article. They neither establish safe versions nor justify extending the successful include-sink example to default PHP installations. The author also proposes a CVSS v3.1 score; that is a separate author assessment, not a conversion of PHP’s vendor-issued v4.0 rating.

Which settings change the conditions?

For the proposed path, session.upload_progress.enabled controls tracking; name selects the triggering field; freq and min_freq control update thresholds; cleanup controls removal of progress information after POST processing. Session handlers and serialization influence persistence. These settings identify what to inspect, rather than a confirmed prerequisite list for both CVEs. In particular, eventual cleanup cannot be assumed to prevent an earlier memory error. [3, 9]

The SAPI, server buffering, request-body limits, and timeouts affect when data reaches the parser. A pause at the client may not survive buffering as a pause between PHP callbacks. Inspect effective configuration in the web-serving process and each FPM pool; CLI configuration can differ. If upload progress is unused, evaluate disabling it as precautionary hardening. The evidence reviewed here does not establish that change as a complete workaround for either CVE. [3, 9]

The reviewed lab configuration also exposes a configuration detail worth checking: session.upload_progress.freq=1 is an absolute byte interval, while 1% is relative to request size. Its memory-corruption test profile uses the former even though comments describe stock timing. Its persistence test profile sets cleanup=0 and min_freq=0. Compare effective values, including units, before generalizing results between these fixtures and production. [4, 9]

The blast radius follows the worker’s permissions

Execution under www-data can already expose an application. Depending on permissions, the worker may read database credentials, modify writable content, or reach backend services. Shared service identities and writable directories can extend the impact across applications. These are consequences to assess against the deployment, not claims that every effect occurred in the demonstration. The useful question is what the process can read, change, and reach.

Huntress recommends watching unexpected child processes from PHP and web-server workers. Correlate process ancestry with requests, timestamps, new files, and outbound connections; legitimate applications also launch tools, and execution can occur without a new shell. The reviewed trigger’s stale-file checks and the framework’s incomplete FastCGI response handling further illustrate why a tool verdict needs attribution. Huntress’s earlier PHP incident example was explicitly unrelated to PHP2Shell and should not be relabeled as exploitation of these CVEs. [2]

Verify the fix where requests actually run

Map public endpoints to their serving PHP packages or images, SAPIs, extensions, and effective configuration. Include vendor software and hosting environments that bundle PHP. Match the detailed upstream advisory to distribution or vendor packages, accounting for backported fixes. Replace or restart affected workers, then confirm that the corrected build handles traffic. For installations without maintained security updates, establish an upgrade or exposure-reduction plan.

The technical objective is to connect the vulnerable operation, its reachable conditions, and the code that restores the required invariant. A clean result from these proof-of-concept implementations cannot substitute for that mapping. My remediation-verification approach follows the claim into the deployed process and checks that the relevant fix is actually in service. If you need that assessment for your PHP estate, the work starts with the running environment and its evidence.

Sources

Need this checked in your own product?

Remediation & fix verification

Want to learn to find vulnerabilities like this yourself?

Secure code review course, one to one
All articles

08Contact

Shipping something that handles user data?

Send me a short brief. I reply within three days with questions or a proposal outline, and the price is agreed in writing before work starts.