Responding to PHP2Shell: Migrating Legacy PHP Services and Checking the Result
After hearing about PHP2Shell, I reviewed the runtime environments behind the PHP services I operate. I moved them to a supported release containing the security fixes, corrected compatibility problems in older code, and checked that actual web requests were reaching the new runtime. The work combined a security update with legacy maintenance.
A substantial part of the response was checking that the older services still did their jobs in the new environment. I am leaving out internal addresses and infrastructure diagrams, and recording the checks that may be useful to others maintaining similar services.
What is PHP2Shell?
PHP2Shell is the collective name for CVE-2026-109618 and CVE-2026-109619. These are two PHP core vulnerabilities that can allow unauthenticated remote code execution, publicly announced on October 21, 2026. Singapore's Cyber Security Agency lists a CVSS v4.0 score of 9.2 for both. CSA advisory
One involves internal state being left incompletely initialized when PHP handles an out-of-memory error. The other can leave a reference to freed request memory that a later request accesses. These issues are not confined to input-validation mistakes in a particular web application. Because the vulnerable behavior is in PHP's own request processing, the runtime itself needed attention. Out-of-memory handling advisory · Stale global state advisory
The official PHP advisory covers several web runtimes, including PHP-FPM. The particularly high risk identified for mod_php does not make other configurations exempt from checking. The releases fixing these two vulnerabilities are PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12. For older versions, distribution or long-term-support backports also need to be checked; a version family alone is not enough to establish that a deployment is patched. Official PHP security advisory
I first checked which PHP was serving web requests
Services hosted on the same long-running server were not necessarily using the same PHP runtime. Some followed the default configuration, while others had explicit version assignments for compatibility. Changing only the default PHP could leave some services on their previous runtime.
I therefore checked the active web-server configuration, per-site handler rules, and running PHP processes together. I compared the version reported on the command line with the runtime handling browser requests. I also distinguished old binaries remaining installed from old binaries actually serving websites.
The completion criterion was that the services in scope would use a patched, supported release, with no remaining web-handler configuration or running legacy worker routing their requests to an older version. Installing a new PHP binary was only one part of the transition.
I used the original behavior as the compatibility baseline
I preserved the original source and configuration, then ran the applications in a separate test environment. Changing many things at once makes it difficult to explain why an issue disappears. I first checked the original behavior, then narrowed down what changed under the new PHP runtime.
The older services encountered removed syntax and functions, stricter function-call requirements, and changes in type handling. I applied the compatibility corrections that were needed and checked the authentication and data-handling behavior that the existing code was meant to preserve.
Operations that write data, such as login, post editing, and attachments, were tested in a cloned environment. I did not change production account passwords or overwrite real posts for those tests. I also aligned the test database's operating conditions with production so that an environment mismatch would not be mistaken for a PHP compatibility defect.
I compared previously failing paths with errors introduced by the new runtime. Problems caused by the transition were corrected, while older defects repaired along the way were recorded separately. Attributing every old application error to an update would blur both the cause and the scope of the repair.
A response alone was not enough
An HTTP 200 response can still have an empty body. A response expected to contain JSON can return something else without a status-only check noticing. For features that consume data, I needed to parse the body and check the expected structure.
Conversely, rejecting an unauthenticated request or returning an error for invalid input can be correct behavior. I compared each request with its expected outcome rather than counting successful status codes.
| Check | What it establishes |
|---|---|
| PHP installation and syntax checks | Readiness of the executable and source syntax |
| Runtime handling actual web requests | Whether the site is connected to the new PHP runtime |
| Response body and expected data structure | Whether the tested API returns the required result |
| Login, editing, and uploads in isolation | Whether the tested write workflows work in the new environment |
I also kept API data checks, real-time connection checks, and complete browser workflows separate. Passing one of them did not establish that every feature had been verified.
I checked the follow-up advisory against its conditions
On October 23, PHP published a follow-up notice about session upload progress. It describes a particular combination of initial session serialization configuration and a change made while application code is running. PHP session upload progress notice
I reread the current PHP configuration and deployed source, then checked the running runtime through private diagnostic requests. The production and test environments inspected did not meet the notice's trigger conditions. As a result, this follow-up issue did not require an additional configuration change or service restart.
I distinguished ordinary file uploads from PHP's feature for recording upload progress in a session. The follow-up check used a small file to verify basic PHP multipart upload handling; I did not present that as a complete test of writing an attachment to a production post. The temporary diagnostic files were removed afterward.
What marked this response as complete
I moved the services in scope to a supported PHP environment containing the security fixes and confirmed that the older runtime was no longer serving their web requests. Compatibility corrections and testing preceded the production rollout, and I checked the subsequent advisory against the deployed configuration. Web-server security updates were handled separately as well.
This is a record of the PHP2Shell response and the associated service migration. It is not a finding that no earlier compromise occurred, nor a complete security audit of every application. I did not run exploit code against the production server.
Changing the runtime of a long-lived service can expose several compatibility problems at once. In this case, I compared the original and updated environments, separated write tests from production, and checked that actual web requests used the new runtime. I also retained change records and recovery points.