Sitemap
Sandeep Vishwakarma

Sandeep Vishwakarma

An Information Security Consultant specializing in WAPT, MAPT, NPT, WPT, and DFIR.

Testing PHP2Shell in Local PHP Lab

7 min readOct 24, 2026

--

Press enter or click to view image in full size

Greetings, I’m Sandy. I built a small PHP lab to understand what a version-based PHP2Shell check can show. My target ran on 127.0.0.1 inside a dedicated local environment. I also read the public research and PHP advisories about CVE-2026–109618 and CVE-2026–109619. The central lesson is that a version finding is a useful starting point for investigation, while exploitability depends on the runtime and request lifecycle.

Why PHP2Shell drew attention

The name PHP2Shell is used for research and tooling around two critical PHP issues whose impact can include remote code execution. The PHP advisories describe separate but related classes of unsafe runtime state: one involving out-of-memory handling during request processing, and another involving request-owned state that can remain reachable after a request ends. In either case, the important idea is that PHP workers process requests through several initialization and cleanup phases. If an interruption leaves internal state inconsistent, later cleanup or a later request can encounter state it should no longer use.

Two vulnerability themes

Out of memory and request cleanup

CVE-2026–109618 concerns PHP handling an out-of-memory condition while request data is in a partially initialized state. The official advisory explains that the request shutdown path can then encounter invalid state. It confirms remote code execution in affected Apache mod_php circumstances. The advisory discusses FPM separately: a child process exits when request startup fails, which changes that particular path; other conditions may still be relevant, but the deployment model must be assessed on its own.

Stale state across requests

CVE-2026–109619 concerns request-owned state that remains referenced by a persistent worker after the request memory is released. A later operation can consume that stale state before normal initialization resets it. The advisory describes a producer-and-consumer lifecycle: one operation leaves state behind, and a later operation on the same worker reaches it. This is why application routes, configuration, and which requests land on the same worker matter to the assessment.

Where multipart and session handling fit

Multipart uploads are relevant because PHP processes parts of a request before the application script begins. Session upload progress can also interact with session and decoder state during this early phase. The research article discusses this timing alongside session handling, serialization and cleanup behaviour, and other application-specific state. These are not interchangeable recipes: a path that depends on a particular application, extension, or SAPI should not be described as universal exposure.

The Substack report labels five scenarios as chains A through E. Here is a brief conceptual summary so readers can distinguish them. These are the report author’s findings, not results from my version-check lab. The descriptions below omit payloads and execution steps; each scenario depends on specific application, configuration, or runtime conditions.

Press enter or click to view image in full size

Chain A Session data and file inclusion

The report describes a combination of PHP session handling, upload-related data, and an application file-inclusion weakness. The important qualifier is that the application-level file-inclusion condition is part of the scenario; this is not a claim that every PHP upload endpoint provides code execution.

Chain B Deserialization cleanup and re-entry

This scenario concerns memory lifetime during object deserialization and cleanup. The report says that nested parsing triggered during object destruction can encounter data that has already been released. Whether an application can reach such a state depends on its deserialization behaviour and other runtime conditions.

Chain C Session format mismatch

The article discusses applications that use different session serialization formats in different parts of one server. Data written under one format may be interpreted differently by another handler. This is a configuration-specific interaction and should be checked against the actual session settings and application boundaries.

Chain D Out-of-memory handling in Apache mod_php

This path focuses on memory exhaustion during multipart request processing and the request shutdown behaviour of Apache mod_php. PHP’s official advisory confirms the underlying out-of-memory corruption issue and identifies mod_php as a confirmed susceptible configuration. The advisory also cautions that PHP-FPM has different worker behaviour, so the same startup-failure sequence cannot simply be assumed there.

Chain E Worker state and PHP-FPM

The report separately describes state left by one request being reached during later session upload-progress handling in a PHP-FPM worker. This is a cross-request lifecycle scenario. PHP’s stale-state advisory explains why producer and consumer operations must line up on the same worker; exact applicability depends on the application and configuration.

The source article reports exploit outcomes for its own lab. I have not reproduced those chains. PHP’s advisories are the primary references for the vulnerability descriptions, confirmed scope, affected branches, and fixes. This overview is intentionally not an exploit guide.

Get Sandeep Vishwakarma’s stories in your inbox

Join Medium for free to get updates from this writer.

The safest way to read a report that presents multiple chains is to separate confirmed advisory impact from the conditions required for each path. In particular, Apache mod_php and PHP-FPM have different worker and startup behaviour. A statement about one SAPI does not automatically apply to another, and a candidate path is not the same as a demonstrated exploit for every deployment.

What I checked in my local lab

My exercise was much narrower than the source-level research. I wanted to compare the endpoint’s disclosed version with the runtime inside the container. The first screenshot is from a run where the endpoint reported PHP 8.4.27. After I changed the lab image and restarted the container, the next run reported PHP 8.4.26.

Press enter or click to view image in full size
Earlier run: the local endpoint disclosed PHP 8.4.27, which the scanner compared with its fixed-version threshold.
Press enter or click to view image in full size
Later run: the endpoint disclosed PHP 8.4.26, below the scanner’s PHP 8.4.27 threshold.

A version banner is useful for triage, but it may be hidden, altered, or different from the vendor package version. Operating-system vendors can also backport security fixes while keeping a different version string. For systems you administer, check the package vendor’s security notice and installed package details along with the HTTP response.

Confirming the container version

I checked PHP from inside the running container and then used the passive fingerprinting mode against the loopback endpoint:

docker exec p2s-target php -r “echo PHP_VERSION, PHP_EOL;”

python3 detect.py http://127.0.0.1:18110/index.php

The container returned 8.4.26, and the scanner classified that version as below the PHP 8.4.27 threshold. This confirms which version string the lab exposed and which runtime the container reported. It does not test whether a vulnerable code path was reachable, whether a particular SAPI condition was met, or whether code execution succeeded.

What the PoC screenshot shows

I kept the cropped screenshot from the PHP2Shell tool because it is part of my lab record. Its visible output identifies PHP 8.4.26 as a pre-fix runtime and shows the tool beginning stage 1 of 8.

Exploit : https://github.com/spade-kk/php2shell-exploit

Exploit : https://github.com/ikow/php2shell-poc

Press enter or click to view image in full size
The screenshot shows the tool’s version verdict and the start of its first stage. It contains no completed exploit result or command output.

A startup message is not proof of RCE. The screenshot does not show a completed run, a shell, or a command result. My article therefore treats it as a tool-start observation and keeps the lab evidence separate from the exploit claims in external research.

Fixed versions and mitigation

PHP lists 8.2.35, 8.3.36, 8.4.27, and 8.5.12 as the fixed releases for the affected maintained branches. Updating to a release containing the fixes is the complete remediation. After updating, restart PHP workers and verify the package and active runtime so old processes are not left serving requests.

The advisories discuss temporary configuration measures for specific paths, such as disabling upload progress when it is not needed. They also make clear that such measures do not cover every stale-state or out-of-memory path and are not a replacement for upgrading. Memory and request-size controls can reduce risk, but administrators should use the official guidance for their SAPI and platform rather than copy a setting without considering its operational impact.

During a controlled audit, review PHP, web-server, reverse-proxy, and application logs for repeated warnings, deprecations, fatal errors, and failures around upload, shutdown, or extension code. Logs help identify configuration and lifecycle issues, but a quiet log is not proof of safety. The advisory recommends a wider configuration and application-flow review as part of mitigation.

Keeping the exercise safe and repeatable

For a classroom or personal exercise, keep the intentionally vulnerable service bound to loopback, use a disposable container, and avoid exposing it to a public network. Confirm the container version before scanning, use only the passive check shown here, and stop the lab when finished. Test systems you own or have clear permission to assess.

For a production system, use the fixed package supplied by your platform, restart the relevant workers, and verify the active version. A passive scan can help prioritise a review, but it cannot replace package verification or the platform vendor’s remediation advice.

Takeaway

The research around PHP2Shell covers more than a version number: request startup and shutdown, memory exhaustion, persistent worker state, multipart uploads, session handling, application-specific conditions, and differences between server APIs all affect the analysis. My screenshots demonstrate only a local version comparison. PHP 8.4.27 was reported as patched by the scanner, while 8.4.26 was reported as below its threshold. The cropped PoC image shows the tool starting, not a successful exploit. Use the official advisories to patch and assess your actual deployment.

References

· PHP advisory for CVE-2026–109618: RCE via OOM corruption

· PHP advisory for CVE-2026–109619: RCE via stale global state

· PHP news archive for the fixed release announcement

--

--

Sandeep Vishwakarma
Sandeep Vishwakarma

An Information Security Consultant specializing in WAPT, MAPT, NPT, WPT, and DFIR.