PHP2Shell Vulnerabilities: How to Patch PHP and Verify the Fix
By Francis Waithaka, Founder and CEO, Digital4Africa
For PHP developers and other production operations teams
Research date: 22 October 2026, Nairobi time (EAT)
Act in this order
- Identify every PHP installation, its web runtime and its owner.
- Assess exposure, prioritising public services, Apache with mod_php, administrative interfaces and sensitive applications. Record unknowns as unresolved.
- Prepare recovery with usable backups and a short compatibility check. If patching is blocked, consider isolation before PHP receives requests.
- Patch with a fixed upstream release or a vendor package explicitly covering both CVEs.
- Restart or redeploy every affected worker, service, container and replica.
- Verify the actual web runtime, the vendor build and critical application functions.
- Investigate, monitor and document the exposure period, results and remaining exceptions.
The minimum fixed upstream releases are PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, released on 21 October 2026. Use a later vendor-approved security release if one is available when implementing this guide. PHP’s announcement confirms the releases and update guidance.
What has been verified
PHP2Shell is the collective nickname for two PHP engine vulnerabilities. The official advisory titles and upstream severity are:
| Identifier | Official technical title | Upstream severity | Official advisory |
|---|---|---|---|
| CVE-2026-109618 | RCE via OOM corruption | Critical, CVSS v4.0 9.2 | GHSA-w8hw-w9fx-j79w |
| CVE-2026-109619 | RCE via stale global state | Critical, CVSS v4.0 9.2 | GHSA-6wmf-4rv5-p3rp |
RCE means remote code execution: an attacker makes the server execute code. OOM means out of memory. CVSS is a standard vulnerability severity scoring system; it does not measure the percentage of sites compromised.
Vendor assessments can differ. Red Hat rates these issues Important for its products and gives a CVSS v3.1 score of 8.1. That product assessment does not override PHP’s upstream rating or establish that a particular installation is safe. Consult the exact package and deployment assessment. Red Hat CVE record.
Evidence status on the research date:
- Verified vulnerability and laboratory exploitation: PHP’s advisories and the original researchers document memory corruption and RCE. These are demonstrated capabilities, not evidence that every affected deployment has been attacked.
- Reported attack activity: Cloudflare reports observing attack traffic and payload changes intended to evade its protections. This establishes reported attempts in its telemetry. It does not establish successful compromise of every targeted origin. Cloudflare’s account.
- Confirmed client compromises: no Digital4Africa client systems were examined for this article. No claim about a client’s compromise or safety follows from this research. The reviewed primary sources do not provide a verified total of successful compromises.
- Remaining uncertainty: exploitability depends on the PHP build, extensions, application behaviour, configuration and server interface. Vendor packages and guidance are changing quickly. Recheck them at implementation time.
PHP usage estimates must never be presented as the proportion of websites proven vulnerable or hacked.
The discovery background is brief: the PHP Foundation describes an OOM concern identified in 2024, followed by AI-assisted research from Calif, OpenAI and Wiz that established practical attack paths in 2026. Human maintainers developed and coordinated the fixes. PHP Foundation explanation.
Understand the two failures
CVE-2026-109618: interrupted initialisation and unsafe cleanup
PHP allocates memory while constructing internal request data. Some structures temporarily become reachable before construction is complete. Its vulnerable OOM handling uses longjmp(), an abrupt jump out of the current operation. Cleanup can subsequently treat unfinished data as valid and operate on invalid pointers.
An attacker needs a reachable way to cause allocation failure at a vulnerable point and influence the memory involved. Multipart form processing and application operations that consume memory provide different routes. A small-looking request is not necessarily cheap to process: input structures can expand in memory.
On mod_php, request cleanup runs even after startup fails. The demonstrated attack can therefore complete before application code executes. PHP’s fix changes OOM handling so incomplete structures are not processed through the old unsafe exit path. Official OOM advisory.
CVE-2026-109619: freed request memory survives in worker state
A worker is a process that handles requests. A use-after-free happens when software continues using memory after releasing it.
PHP workers can retain global fields pointing into one request’s memory. An interrupted operation or late cleanup diagnostic can leave such a field populated after that memory is freed. A later request on the same worker may consume it before normal initialisation resets it.
Exploitation requires both a state-producing operation and a later state-consuming operation. Confirmed examples include PHP 8.5 fatal-error backtraces when enabled, and session upload-progress handling. Session upload progress is enabled by default and can operate without an application calling session_start(). The complete fix resets a wider set of request-owned state. Official stale-state advisory.
Consequences and differences between deployments
Successful RCE initially has the web worker’s privileges, not automatically root or Administrator privileges. Sensitive files, application credentials and databases reachable through that account may nevertheless be exposed. The original Wiz research also demonstrates process-memory disclosure, worker crashes and representative attacks involving WordPress, Drupal and Joomla. Its grouping of individual variants spans the two CVEs. Original Wiz research.
A SAPI, or Server Application Programming Interface, is the interface through which a web server runs PHP.
| Deployment | Relevant exposure and operational consequence |
|---|---|
| Apache with mod_php (apache2handler) | Highest-priority deployment. Confirmed startup attacks can precede application execution. Replace the PHP module package and restart Apache workers. |
| PHP-FPM (fpm-fcgi) | FPM terminates children on startup failure, preventing that exact startup sequence from continuing. Application-stage paths remain relevant. The stale-state advisory confirms session-lifecycle RCE with suitable application behaviour and worker reuse. |
| CGI and FastCGI (cgi-fcgi) | PHP explicitly includes php-cgi in its patch guidance. If CGI genuinely exits after every request, the specific cross-request worker-reuse sequence cannot persist in that process. This inference does not establish protection from OOM or other variants. Replace the mapped runtime and any persistent FastCGI processes. |
| LiteSpeed/LSAPI (litespeed) | The advisories identify conditional paths; they do not establish universal immunity or identical exploit reliability. Update the actual LSPHP/vendor build and replace its workers. |
| FrankenPHP or another embedded runtime | Update the application/server distribution that bundles PHP. The OOM advisory identifies a likely startup exposure in FrankenPHP; do not assume a host’s separate PHP package updates its embedded engine. |
PHP’s broad deployment warning is documented by NCSC-NL. The detailed SAPI distinctions appear in the OOM advisory and stale-state advisory.
A source inconsistency matters: the OOM advisory’s FPM paragraph says no complete FPM exploit has been demonstrated there, while the Foundation’s explanation says application-level FPM RCE was confirmed. Record that difference rather than using either wording as proof of FPM safety. Both direct operators to update. Foundation explanation.
WordPress, Drupal and Joomla updates remain necessary for their own vulnerabilities, but cannot repair PHP’s engine. Laravel and custom applications also inherit runtime exposure, although reachable routes and conditions differ. Current application code, plugins and themes do not prove the interpreter is patched. A security plugin running inside PHP may execute after vulnerable request processing has begun.
Choose the correct fixed build
| PHP branch | Affected upstream releases in that branch | Minimum fixed upstream release | Vendor and legacy considerations |
|---|---|---|---|
| 8.5 | 8.5.0 through 8.5.11 | 8.5.12 | An explicitly fixed vendor backport can retain an older upstream number. |
| 8.4 | 8.4.0 through 8.4.26 | 8.4.27 | Check the complete package version, repository and advisory. |
| 8.3 | 8.3.0 through 8.3.35 | 8.3.36 | For example, Ubuntu 24.04 fixes these issues in 8.3.6-0ubuntu0.24.04.13. |
| 8.2 | 8.2.0 through 8.2.34 | 8.2.35 | Check distribution-specific revisions and support eligibility. |
| 8.1, 8.0, 7.4 | Unsupported by the PHP project | No normal upstream security release | Emergency source backports exist for both CVEs. Prefer a supported vendor package or migration to a supported branch. |
| Older branches | Do not assume safety from age alone | No universal upstream fixed release | Require a vendor’s explicit fixed/not-affected assessment for that package, or migrate. Record unknown builds as unresolved. |
Sources: PHP release guidance, CERT-EU, and Ubuntu USN-8948-1.
A backport applies a security fix to an older maintained codebase. Consequently, PHP 8.3.6 in a web response can belong to a fixed Ubuntu package. Version-only scanners can misclassify it. Correlate the serving runtime with the full installed package revision and an advisory covering both identifiers.
For comparison, Debian lists 8.2.35-1~deb12u1 for Debian 12 and 8.4.27-1~deb13u1 for Debian 13 as fixed security packages. Those numbers are not interchangeable with Ubuntu package revisions. Debian tracker for 109618, tracker for 109619.
An emergency source backport is a bridge, not renewed upstream support. A specialist must build the complete fix, preserve required extensions and configuration, test the resulting runtime, and own future updates. Ongoing distribution or commercial support is a separate commitment. Ubuntu, for example, publishes legacy fixes under its applicable Pro/ESM arrangements. Ubuntu USN-8948-2.
Changing a control panel’s selection from “PHP 8.2” to “PHP 8.3” proves only that a branch was selected. The selected build may still be vulnerable, a domain may override it, and old workers may still be running.
Numbered remediation procedure
Command conventions: Linux examples use Bash. Commands labelled Read-only do not intentionally change application configuration or packages. Changes system commands install packages, alter state or interrupt workers. Package metadata refreshes are identified separately. Replace uppercase placeholders before running commands. A configuration test starts a separate test process and may write normal diagnostic logs; it does not restart the serving service.
1. Build an inventory and assign ownership
What to do: reconcile hosting accounts, DNS records, virtual hosts, cloud instances, container workloads and billing records. Include production, subdomains, staging, old document roots, preview sites, webmail, management panels, embedded products and standby replicas. Include scheduled jobs and queue workers when planning runtime replacement.
For each entry record: hostname, origin/server identifier, application, public reachability, hosting provider, OS, package supplier, PHP branch/build, SAPI, pool or service, replicas, administrator and client contact. Mark the web runtime unknown until checked.
Why: a patched homepage does not close an exposed staging subdomain or a separate panel runtime. NCSC-NL specifically warns about products embedding PHP. Operational inventory guidance.
Evidence: a reconciled asset register with an owner and status for every entry, including installations that are retired or isolated.
2. Identify the runtime actually serving each site
What to do: inspect domain-to-handler mappings, web-server configuration, FPM pool sockets and container configuration. On systemd hosts, discover services rather than assuming their names.
Read-only, Linux/systemd:
cat /etc/os-release
systemctl list-unit-files –type=service –no-pager |
grep -Ei ‘php.*fpm|apache2|httpd|lsws|litespeed’
systemctl list-units –all –type=service –no-pager |
grep -Ei ‘php.*fpm|apache2|httpd|lsws|litespeed’
ps -eo pid,ppid,lstart,args |
grep -E ‘[p]hp-fpm|[a]pache2|[h]ttpd|[l]sphp’
Then inspect an actual discovered unit. The following name is an Ubuntu example:
FPM_SERVICE=php8.3-fpm.service
systemctl show “$FPM_SERVICE” -p ExecStart -p MainPID -p FragmentPath
systemctl cat “$FPM_SERVICE”
Unit files can contain sensitive environment settings. Keep captured output private. Use their actual binary/configuration paths in subsequent checks. Custom supervisors and containers require their own inventory. systemctl reference.
php -v, php –ini and php -m inspect a command-line installation. They are useful supporting checks, but do not identify what a specific domain is executing.
If the provider cannot supply sufficient runtime evidence, use a minimal diagnostic endpoint only after a VPN, network rule or web-server access rule restricts it to administrators. Confirm unauthorised requests are denied before reaching PHP. Do not rely on a secret filename or PHP-based authentication for this restriction.
Changes deployment: temporarily install this file in the relevant document root after applying that restriction. Read-only request: fetch it through the site’s actual PHP handler, bypassing CDN/page caches.
<?php
header(‘Content-Type: application/json’);
header(‘Cache-Control: no-store’);
echo json_encode([
‘php_version’ => PHP_VERSION,
‘sapi’ => PHP_SAPI,
‘pid’ => getmypid(),
‘binary’ => PHP_BINARY,
‘ini_file’ => php_ini_loaded_file(),
‘memory_limit’ => ini_get(‘memory_limit’),
‘session_upload_progress’ => ini_get(‘session.upload_progress.enabled’),
‘fatal_error_backtraces’ => ini_get(‘fatal_error_backtraces’),
], JSON_PRETTY_PRINT), “\n”;
For mod_php, map the loaded Apache module to its package; PHP_BINARY alone may identify Apache rather than the PHP library. Verify every origin privately. Repeated requests through a load balancer do not prove that every backend was reached. Save results securely, then remove the endpoint and its temporary access rule.
Why: CLI, FPM, LSAPI and panel PHP can have different versions and configuration.
Evidence: per-site web runtime/SAPI evidence correlated with its executable or module, package source, service/pool and origin.
3. Prepare a recoverable backup
What to do: use the established backup system to capture application files, uploads, databases and relevant PHP, pool, web-server and deployment configuration. Retain extension lists, package revisions, image digests and protected secrets configuration. Keep backups outside the web root and outside the affected account’s normal write access.
Verify that the backup completed, that files or database data can be read, and that the team knows the restore procedure and has access to the required credentials. Use a recent successful restore test where available; do not delay an urgent update for a lengthy full rehearsal when a quick recovery check is sufficient.
Why: a file archive alone cannot recover a database-backed application. A fresh snapshot is also not proof the captured system is free of compromise.
Evidence: backup identifiers, timestamps, scope, recovery checks and named recovery owner. For transactional sites, document how orders or payments created after the backup would be reconciled.
4. Make a proportionate compatibility and containment plan
What to do: prefer the security update within the currently supported upstream or vendor-maintained branch. Check extensions, database connectivity and critical journeys on a representative staging instance or a canary backend. Keep the check short and focused. Schedule any broader PHP migration separately when an existing-branch security fix is available.
Plan a brief interruption on a single server. On redundant services, drain, update and verify one backend at a time, while moving unpatched backends out of public reach. Confirm remaining capacity can carry traffic.
If updating is blocked, apply the temporary controls described below or isolate at the proxy/network layer. CERT-EU recommends considering isolation of public applications until a fix is applied and verified. CERT-EU advisory.
Why: urgent security updates need a recovery path, but an extensive unrelated upgrade project can prolong exposure.
Evidence: agreed patch target, short test plan, operator, traffic plan and recovery steps. If restoring a vulnerable runtime becomes necessary, isolate it; prefer reverting application/configuration changes while retaining the fixed PHP engine.
5. Install the fix using the relevant deployment path
What to do: follow the appropriate path below. Validate package signatures through the supplier’s supported mechanism. Confirm candidate packages address both CVEs before installation. Do not disable signature checks to work around repository failures.
Why: “already at the latest version” only means latest in the configured repository. It does not establish that the repository contains the fix.
Evidence: successful transaction/build records and the complete installed package revision or immutable image identity, matched to the supplier’s advisory.
Path A: shared or managed hosting, cPanel/WHM and Plesk
Who acts: the account developer inventories domains and verifies behaviour. The hosting provider or server administrator normally installs system PHP packages and replaces workers. A reseller/account login often cannot do this.
Open an urgent provider ticket identifying both CVEs and every affected domain. Request the full fixed build, package/advisory reference, confirmation that all relevant workers were replaced, and completion time. A general “our servers are secure” response is insufficient evidence.
cPanel/WHM:
- Use MultiPHP Manager to inventory each domain’s selected handler and inheritance. Include CloudLinux/alternative PHP where present. cPanel handler documentation.
- The administrator should apply the relevant EasyApache PHP package updates through the supported package-management/WHM workflow, reviewing the affected package set. Separately update cPanel’s internal PHP because the panel and webmail can have their own runtime.
- cPanel’s advisory lists EasyApache fixes for 8.2–8.5 and legacy examples ea-php74 7.4.33-19, ea-php80 8.0.30-12 and ea-php81 8.1.34-5. CloudLinux-supplied packages have a separate build list. Check the exact supplier and revision. Current cPanel advisory.
- Replace every affected FPM, Apache or LSPHP worker and verify each domain. Account-level branch switching alone is insufficient.
Plesk:
- Inspect Tools & Settings → PHP Settings and each domain’s PHP handler. On Linux, the following is a read-only administrator check:
plesk bin php_handler –list
- Apply the vendor’s PHP component updates and the relevant Plesk security update through its supported updater. Plesk currently lists PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12 for Linux and Windows. Its Linux legacy 7.4.33, 8.0.30 and 8.1.34 fixes retain the version number but have builds dated 21 October 2026.
- Update the panel’s separate sw-engine runtime too. Plesk identifies Obsidian 18.0.80 Update 11 and 18.0.81 Update 4 as fixed panel releases. Replace relevant PHP workers and verify domains and the panel separately. Current Plesk advisory, handler command reference.
LiteSpeed note: an LSWS restart can leave detached PHP workers alive. Use the provider’s Restart Detached PHP Processes action, then verify old LSPHP processes have exited. LiteSpeed’s restart-file mechanism is lazy, taking effect as handlers are next used; merely creating the file is not completion evidence. LiteSpeed documentation.
If a provider has no verified fix, keep the entry unresolved, request an ETA and decide whether to isolate or move the workload to a verified patched environment.
Path B: Ubuntu/Debian with PHP-FPM
Who acts: a root/sudo administrator. Managed servers still require provider coordination.
Assumptions: the example is Ubuntu 24.04 using distribution php8.3-* packages, /usr/sbin/php-fpm8.3, /etc/php/8.3/fpm/php-fpm.conf and php8.3-fpm.service. Debian commonly uses another branch. Substitute discovered names throughout. Do not apply these names to Plesk, cPanel, a PPA or source-built PHP without confirming its package mapping.
Read-only, installed packages and package origin:
dpkg-query -W -f=’${binary:Package}\t${Version}\t${db:Status-Status}\n’ \
‘php8.3*’
apt-cache policy php8.3-fpm php8.3-common php8.3-cli php8.3-opcache
apt-mark showhold
Refresh metadata, then inspect candidates against the applicable Ubuntu notice or Debian tracker:
# Changes local package metadata; does not install updates.
sudo apt-get update
apt-cache policy php8.3-fpm php8.3-common php8.3-cli php8.3-opcache
For Ubuntu 24.04 official packages, the verified minimum is 8.3.6-0ubuntu0.24.04.13. Ubuntu 22.04’s PHP 8.1 package is fixed at 8.1.2-1ubuntu2.28. Different branches/repositories require their own evidence. USN-8948-1.
Read-only simulation: use only package names present in your inventory; include other installed packages from the same PHP source release where required for a consistent update.
apt-get -s install –only-upgrade \
php8.3-fpm php8.3-common php8.3-cli php8.3-opcache
Review removals, dependency changes and versions. Stop if the candidate is not verified fixed. Then:
# Changes system: installs targeted PHP updates and required dependencies.
sudo apt-get install –only-upgrade \
php8.3-fpm php8.3-common php8.3-cli php8.3-opcache
# Configuration validation. Stop and resolve errors before restarting.
sudo /usr/sbin/php-fpm8.3 -t -y /etc/php/8.3/fpm/php-fpm.conf
Packages may restart services during installation; plan the traffic window accordingly. Complete Step 6 explicitly. An unchanged Nginx configuration does not require an Nginx restart just because FPM was updated. APT command reference, FPM command reference.
Path C: RHEL-compatible servers with PHP-FPM
Who acts: a root/sudo administrator using the actual OS/package supplier’s advisories.
Assumptions: RHEL 8/9-style DNF tooling, distribution packages named php-fpm and php-common, and an existing PHP stream. RHEL 10, Software Collections, Remi, cPanel and Plesk can use different package/service names. Discover them first. Rebuild distributions publish their own packages; a Red Hat advisory does not prove those packages have reached AlmaLinux or Rocky Linux repositories.
Read-only inventory:
rpm -qa –qf ‘%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n’ |
grep -Ei ‘php|lsphp|sw-engine’
dnf repolist
dnf info –installed php-fpm php-common
# RHEL 8/9 modular deployments only:
dnf module list php –enabled
Red Hat has issued branch-specific fixes, including RHSA-2026:87072 for RHEL 9 PHP 8.2, RHSA-2026:87079 for RHEL 9 PHP 8.3, and RHSA-2026:87080 for RHEL 8 PHP 7.4. Match the exact stream, OS release, architecture and subscription.
Example only for a host covered by RHSA-2026:87079:
# Refreshes metadata; does not install packages.
sudo dnf makecache –refresh
# Read-only: inspect this advisory and preview its transaction.
dnf updateinfo info RHSA-2026:87079
sudo dnf upgrade-minimal –assumeno –advisory=RHSA-2026:87079
# Changes system: install the applicable advisory’s updates.
sudo dnf upgrade-minimal –advisory=RHSA-2026:87079
# Read-only checks; use the actual FPM configuration if non-default.
rpm -q php-fpm php-common
sudo /usr/sbin/php-fpm -t -y /etc/php-fpm.conf
The supported advisory-scoped update method is documented by Red Hat. Do not reset/switch module streams or mix repositories just to force an update. If your supplier has no applicable fixed package, record the blocker and use isolation or a controlled migration. A missing CVE line in an RPM changelog is not proof either way; the complete package identity and vendor advisory are decisive.
Path D: Apache using mod_php
Who acts: the Apache/system administrator or hosting provider.
Read-only: use the wrapper installed by your distribution:
# Debian/Ubuntu example:
sudo apache2ctl -M
sudo apache2ctl -S
# RHEL-style alternative, run where httpd is the installed server:
sudo httpd -M
sudo httpd -S
Confirm a loaded PHP module and its configured LoadModule path. If PHP instead uses proxy_fcgi, follow the FPM path and update the selected backend.
On Ubuntu with mod_php 8.3, inspect libapache2-mod-php8.3 alongside the other PHP packages and verify the candidate against USN-8948-1. Then:
# Read-only preview:
apt-get -s install –only-upgrade libapache2-mod-php8.3 php8.3-common
# Changes system: targeted package update.
sudo apt-get install –only-upgrade libapache2-mod-php8.3 php8.3-common
# Configuration validation:
sudo apache2ctl configtest
# Changes system: full Apache restart after successful validation.
sudo systemctl restart apache2.service
A RHEL/custom installation must update the package owning its actual loaded module and restart the discovered Apache unit, often httpd.service. Updating only php-cli does not replace a loaded Apache module. A full restart, with worker replacement verified, provides clearer closure than assuming a graceful reload removed all old workers. Ubuntu explicitly requires an Apache restart for mod_php. Ubuntu guidance.
Path E: Docker and other container deployments
Who acts: the image maintainer and deployment administrator. A host-level PHP update does not change a PHP binary inside a container.
Keep the existing branch, SAPI and base distribution where practical. For example, the official image catalogue lists php:8.3.36-fpm-bookworm. Use it only if the application already uses that branch/FPM/Bookworm combination. Official PHP image catalogue.
Changes source: update the Dockerfile’s PHP-bearing base stage, preserving application and extension build instructions:
FROM php:8.3.36-fpm-bookworm
Use your current approved base tag if a later verified security build supersedes that minimum. If the base is pinned by digest, explicitly replace the old digest with the verified patched digest; pulling cannot update a pinned old digest. Check the final image, including any copied-in runtime from another build stage. Docker digest guidance.
Assumptions: Docker Compose v2, running in the correct project directory, with a service named php that has a build: configuration. Replace that service name with the one discovered below.
# Read-only:
docker compose config –services
docker compose ps -a
PHP_SERVICE=php
# Changes local images: build before interrupting running traffic.
docker compose build –pull “$PHP_SERVICE”
# Changes deployment: replaces this service’s running containers.
docker compose up -d –no-deps –force-recreate “$PHP_SERVICE”
# Read-only verification:
docker compose ps
for id in $(docker compose ps -q “$PHP_SERVICE”); do
docker inspect –format ‘{{.Id}} {{.Image}} {{.State.StartedAt}}’ “$id”
docker exec “$id” php -v
done
For an image:-only service, update its image reference, run docker compose pull “$PHP_SERVICE”, then recreate it. A restart of an old container retains its old image. Use –no-cache as needed if PHP is installed in a cached build step; it does not override an explicitly pinned vulnerable base. Compose build, Compose replacement behaviour.
Compose replacement can interrupt service and is not a rolling deployment guarantee. In Kubernetes, Swarm or another orchestrator, deploy the fixed immutable image through its normal rollout, verify all replicas’ image identities and update job/cron templates that could recreate old images. Replace PHP-bearing queue/scheduler services too. An interactive CLI check inside a container supports image verification; complete the private web-runtime check as well.
Path F: Windows/IIS
Who acts: the Windows/IIS administrator or managed-hosting provider. On Plesk, use Path A’s component updater.
PHP’s official guidance specifies Non-Thread Safe (NTS) builds for IIS FastCGI and maps PHP requests to php-cgi.exe. Windows fixed builds are available through PHP’s downloads page. Match architecture, extensions and runtime prerequisites. PHP IIS documentation, official downloads.
Read-only, elevated Windows PowerShell with IIS management tools:
Import-Module WebAdministration
Get-Website | Select-Object Name, ApplicationPool, PhysicalPath
Get-WebApplication | Select-Object Path, ApplicationPool, PhysicalPath
& “$env:windir\System32\inetsrv\appcmd.exe” list config /section:system.webServer/fastCgi
Get-CimInstance Win32_Process -Filter “Name = ‘php-cgi.exe'” |
Select-Object ProcessId, CreationDate, ExecutablePath
Inspect site-specific Handler Mappings too. Stage the verified patched PHP distribution in a separate directory, preserve reviewed configuration and compatible extensions, and change every affected handler/FastCGI mapping to it. Do not replace only one executable while retaining mismatched PHP DLLs.
Drain the affected site and stop/start every application pool using the old runtime. The following commands interrupt service; ACTUAL_POOL_NAME is a placeholder. Run the stop before the mapping/binary change and the start after it:
Stop-WebAppPool -Name ‘ACTUAL_POOL_NAME’
# Apply the reviewed PHP installation and handler changes here.
Start-WebAppPool -Name ‘ACTUAL_POOL_NAME’
Confirm old php-cgi.exe processes have exited and verify through the web handler. An overlapping recycle alone can leave old workers present temporarily. IIS FastCGI mappings, Stop-WebAppPool, Start-WebAppPool.
6. Replace every old worker and verify the active build
What to do: after configuration validation, restart the discovered FPM service or replace the corresponding containers. For an Ubuntu example:
# Changes system: briefly interrupts this service’s requests.
sudo systemctl restart php8.3-fpm.service
# Read-only:
systemctl is-active php8.3-fpm.service
systemctl show php8.3-fpm.service -p MainPID -p ActiveEnterTimestamp
ps -eo pid,ppid,lstart,args | grep -E ‘[p]hp-fpm’
For the distribution RHEL example, use the discovered unit, commonly php-fpm.service. Repeat for every installed branch and independent service. A service can host several pools; restarting one service does not restart another branch’s service. Verify detached LiteSpeed workers separately.
Compare pre-change and post-change process identities/start times. Repeat Step 2’s restricted web request on every origin and relevant handler. Match the result to installed-package evidence and the supplier’s advisory. Remove diagnostics after collection. Do not run exploit requests, force OOM or send production attack probes to “prove” the update.
Why: updating disk files leaves an exposure gap when old code continues running in memory.
Evidence: fixed package/image identity, worker replacement records, correct web SAPI/version and complete replica coverage. For backports, retain the vendor mapping because the web-visible version may remain unchanged.
7. Test the application and monitor service health
What to do: run controlled, ordinary business requests, using a test account or sandbox transaction where available.
| Check | Completion evidence |
|---|---|
| Login, logout, sessions and permissions | Expected access, sessions and cookies behave normally. |
| Forms and notifications | Submission saved correctly; expected email/webhook delivered. |
| Uploads and imports | Normal authorised files work within intended limits. |
| Checkout and payments | Sandbox or controlled transaction, callback and order state succeed without duplicate charges. |
| Scheduled jobs and queues | Expected job completes; restarted workers use the intended runtime. |
| Integrations | Database, cache, CRM, payment, SMS/email and API connections pass applicable checks. |
| Service health | Error rate, response time, memory and worker restarts remain acceptable against the service’s normal baseline. |
Why: the security fix, extension changes or temporary input restrictions can affect user journeys even when the homepage loads.
Evidence: timestamped results and monitoring links, with failures assigned an owner. Once patched and tested, replace emergency unlimited-memory settings with the application’s tested memory limits and remove unnecessary temporary performance restrictions.
8. Assess compromise and record closure or exceptions
What to do: follow the investigation workflow below, then record the final status per asset. Suggested statuses are Verified patched, Mitigated temporarily, Isolated, Awaiting provider, Unverified, or Incident under investigation. Track patch status separately from compromise status so a patched system can still have an open incident.
Why: installing a security update stops the corrected vulnerability from remaining available; it does not remove a web shell, stolen credentials or persistence already established.
Evidence: a completed record containing:
Asset/domain and origin:
Owner, provider and ticket/change reference:
OS, repository/package supplier, PHP branch and web SAPI:
Before build -> after full package revision or image digest:
Advisory evidence for CVE-2026-109618 and CVE-2026-109619:
Backup and recovery reference:
Restart/redeployment time and worker/replica evidence:
Web-runtime evidence and diagnostic removal:
Functional tests and monitoring reference:
Compromise assessment, reviewed period and limitations:
Patch status and separate incident status:
Exceptions, temporary controls, owner, next action and due time:
Completion time, timezone and reviewer:
Client communication reference:
Record times in UTC or EAT with the timezone explicit. Do not place database passwords, tokens or customer data in a general maintenance ticket.
Temporary protection while patching is blocked
Only installing the complete fix and replacing affected workers qualifies as remediation. The following controls reduce particular paths and must remain recorded as temporary.
| Control | Relevant path | Limitations and operational trade-off |
|---|---|---|
| Administratively enforce memory_limit=-1 | Removes the predictable PHP memory-limit OOM trigger for 109618 | Underlying allocation failure can still reach vulnerable handling. Unbounded request memory can exhaust the host. Assess capacity, constrain workers and monitor before using it. |
| Conservative post_max_size, with upload_max_filesize lower | Reduces input/OOM pressure | PHP recommends 8M where uploads are unnecessary, or 32M where needed. These emergency values can break legitimate workflows. They do not cover all input amplification or application-stage allocations. |
| Frontend limits on decoded body size, input/part/header counts and nesting | Reduces malicious input reaching PHP | Limits must handle requests without usable Content-Length. A simple wire-size cap or content-type rule is not universal coverage. Block multipart entirely only where the application does not need it. |
| Container/cgroup memory boundary | Availability protection when limiting PHP memory differently | It can kill workers or the whole workload and is not a guaranteed exploit prevention. PHP advises against substituting RLIMIT_AS or RLIMIT_DATA, which can induce the vulnerable allocation failure. |
| Disable session.upload_progress.enabled before request parsing | Confirmed session paths of 109619; also removes session-related attack surface | Restart all workers. Application ini_set() and FPM .user.ini are too late for the confirmed early path. Other stale-state variants remain. |
| On PHP 8.5, disable fatal_error_backtraces | Confirmed fatal-backtrace producer for 109619 | Restart workers. Does not cover other global/extension state. |
| Remove invalid/deprecated per-request INI settings and fix reachable fatal errors/timeouts | Removes particular stale-state producers/consumers | Hiding diagnostics does not stop the unsafe access. Fixing observed errors is incomplete coverage. |
| Consider FPM pm.max_requests=1 | Reduces cross-request worker reuse | Officially suggested where performance is not important. Process churn can be expensive. It does not fix 109618 or establish universal safety. |
Sources: PHP’s configuration guidance, CERT-EU’s mitigations, and the stale-state advisory’s additional guidance.
Apply settings through the responsible administrator at startup/server level. Confirm no pool, directory or application setting restores a finite PHP memory limit when using the emergency unlimited-memory control. For example, changing an FPM pool configuration can include:
; Emergency controls only, after evaluating resource and application effects.
php_admin_value[memory_limit] = -1
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 only, if this directive exists in the installed build:
php_admin_flag[fatal_error_backtraces] = off
Validate configuration, restart every affected worker and verify effective values through the restricted web runtime. Apply all relevant controls for both CVEs; disabling upload progress alone is not a complete PHP2Shell fix.
WAF protection
A WAF, or web application firewall, filters web traffic. Cloudflare reports automatic PHP2Shell protections for its Free and paid WAF users, including request/response transaction analysis, while still directing customers to patch PHP. Cloudflare guidance.
Confirm that each hostname is actually routed through the protected service, the rules are active, and skip rules/exclusions do not bypass them. Prevent direct public access to origins where practical. Review blocked legitimate uploads and business requests.
WAF coverage applies to recognised traffic patterns and supported inspection paths. Response blocking can reduce disclosure but does not mean origin execution never occurred. A generic ModSecurity ruleset or WordPress security plugin is not evidence of equivalent coverage. Retain WAF protections as an additional layer while installing the runtime fix.
When to isolate
Consider isolation when no verified fixed build is available, update timing is unknown, sensitive systems remain exposed, or compromise is suspected. Use the load balancer, reverse proxy, network firewall or provider controls to block untrusted requests before PHP. Include alternate hostnames, origin IPs and exposed management interfaces. A static maintenance response generated at the edge can preserve useful communication.
An application-level maintenance page may still execute PHP and still parse the request first. It is therefore insufficient for a pre-application attack. Follow CERT-EU’s isolation recommendation and verify the actual blocked origin path.
Investigation and incident response
Treat the following as triage signals, not standalone proof of PHP2Shell exploitation. Wiz highlights unusual multipart traffic correlated with memory-limit errors or HTTP 500s, malformed/oversized PHPSESSID response cookies, and unexpected worker churn. Original researcher guidance.
- Preserve evidence and define the window. Retain CDN/WAF, load-balancer, web access/error, PHP/FPM, application, OS authentication and available EDR/audit logs from before patching. Preserve backups and configuration/package history. If compromise is suspected, capture appropriate volatile evidence before restarts when feasible, with incident-response support. Keep core dumps private because they can contain credentials and customer data.
- Correlate events. Compare request times, hostnames, origins and request IDs with OOM errors, fatal errors, timeouts, crashes and unexpected restarts. Review log coverage and retention gaps. Missing errors are not proof of safety; vulnerable operations may run before useful application logging.
- Inspect changes and persistence. Compare code/configuration against trusted deployment artefacts. Investigate unexpected executable files in upload/cache directories, changed scheduled tasks/services, new CMS or server accounts, altered SSH keys, unexpected processes, outbound connections and suspicious database/account activity. These are general compromise indicators, not signatures unique to these CVEs.
- Contain when warranted. Isolate affected workloads and restrict unnecessary egress while preserving approved investigation access. Avoid deleting suspicious files or running cleanup tools before capturing them and relevant metadata.
- Recover from a trusted source. Where compromise is established, rebuild from verified patched images/packages and trusted application sources. Review restored data and uploads for persistence; do not blindly restore the suspect system snapshot. Correct the entry point before reopening service.
- Rotate exposed credentials. From a clean administrative environment, revoke/replace credentials and sessions accessible to the compromised worker: relevant database, application, API, payment, email and deployment credentials. Broaden the scope if evidence shows wider access. Rotation while the attacker still controls the host can expose the replacements.
The final three actions are operational incident-response recommendations, consistent with NIST SP 800-61 Rev. 3. Record confirmed findings, suspected findings and evidence limitations separately. A conclusion of “no indicators found in the reviewed evidence” is narrower and more accurate than “the site was never compromised.”
About the author
Francis Waithaka is the Founder, CEO and Lead Trainer at Digital4Africa. He has more than two decades of experience in information technology, digital marketing, business development and professional training. He works with organisations to apply AI, automate services, improve digital platforms and build the skills teams need to use technology effectively. His focus is on practical adoption, sound judgment and measurable business value.
Connect with Francis on X (@waithash).
Annotated further reading
- PHP security announcements: authoritative release targets, emergency legacy backports and configuration advice.
- PHP source security advisories: track amendments to the two official technical advisories.
- PHP Foundation explanation: discovery, confirmed impact and disclosure context.
- CERT-EU 2026-018: operational prioritisation, isolation and mitigation guidance.
- NCSC-NL PHP2Shell repository: expanding inventory of embedded products and operational material; it is not a complete product list.
- Wiz’s original research and Calif’s original research: technical context and demonstrated impact. No exploit reproduction is required for production patch verification.
- Ubuntu USN-8948-1 and USN-8948-2: distribution package revisions and legacy support requirements.
- Debian 109618 tracker and 109619 tracker: fixed source-package versions by Debian release.
- Red Hat PHP2Shell bulletin: product-specific assessment; follow the linked CVE records and applicable errata for package closure.
- cPanel advisory and Plesk advisory: hosting-package builds and separate control-panel runtimes.
- Cloudflare’s protection guidance: WAF coverage and observed attack traffic, alongside the requirement to patch.
- LiteSpeed process lifecycle: why detached PHP workers need explicit replacement.
Final checklist
Discovery and ownership
☐ All production sites, subdomains, staging sites, old installations, panels and embedded runtimes are inventoried.
☐ Every origin, PHP handler/pool, branch, container, replica and standby instance has an owner.
☐ OS, package supplier/repository, full build, web SAPI and public reachability are recorded.
☐ Unknown or unsupported systems have an explicit next action and due time.
Backup and preparation
☐ Files, uploads, databases and relevant configuration have recoverable backups.
☐ Recovery access and a proportionate restore check are confirmed.
☐ Compatibility checks, traffic handling, interruption and recovery steps are agreed.
☐ Suspected incidents have appropriate evidence preserved before disruptive work.
Patching and temporary protection
☐ Installed fixed packages/images are verified against advisories covering both CVEs.
☐ Panel PHP, web PHP and bundled runtimes have been handled separately where necessary.
☐ Temporary mitigations are recorded with their limitations, owner and expiry/review time.
☐ Unpatched systems are isolated or explicitly tracked with the chosen exposure controls.
Runtime verification and functional testing
☐ Every affected worker, pool service, container and replica has been replaced or restarted.
☐ Private web requests confirm the intended runtime on every relevant origin/handler.
☐ Backported builds are correlated with the exact vendor package revision and advisory.
☐ Login, forms, uploads, checkout, jobs and integrations pass applicable tests.
☐ Temporary diagnostic endpoints and access rules are removed; normal health is monitored.
Compromise assessment
☐ Relevant logs and available host/application evidence have been reviewed for a recorded period.
☐ Suspicious files, accounts, persistence and process/network activity are investigated.
☐ Findings and visibility gaps are documented without treating absence of logs as proof of safety.
☐ Any confirmed compromise has containment, trusted recovery and appropriate credential/session rotation.
Client communication and documentation
☐ Each client has a factual status: completed, temporarily protected, isolated or awaiting action.
☐ The record includes the full build/image identity, advisories, backup, replacement evidence, tests and completion time.
☐ Patch status and compromise status are recorded separately.
☐ Every unresolved asset has an owner, next action, due time and temporary protection decision.
Verified patched means that an authentic build containing the complete fixes for both CVEs is installed, every relevant web worker/replica is running that build, the web-runtime/package mapping is documented, and applicable functional checks pass. It is a patch-status conclusion, not certification that the system was never compromised or that every other vulnerability has been removed.


