PHP2Shell and Drupal: A Practical Incident-Response and Patching Guide for Site Owners

Tim Rabbetts | October 23, 2026

Drupal teams should treat the PHP security release published on 21 October 2026 as an infrastructure incident, not merely as another dependency update. Two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, can allow remote code execution under certain conditions. The National Cyber Security Centre (NCSC) is urging UK organisations to identify PHP installations, prioritise internet-facing systems, update urgently and consider taking systems offline if they cannot be mitigated promptly. ([ncsc.gov.uk](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php))

The Drupal application may be fully patched while the PHP runtime underneath it remains vulnerable. Conversely, upgrading Drupal without checking PHP-FPM pools, Apache modules, containers, hosting control panels and background workers can leave part of the estate exposed. This guide provides a repeatable response process for Drupal developers, technical leads and site reliability teams.

What the PHP vulnerabilities mean for Drupal

The PHP Foundation describes CVE-2026-109618 as an out-of-memory corruption issue. In a specific failure sequence, PHP can leave internal request data partially initialised and later treat it as valid. CVE-2026-109619 involves stale global state: memory belonging to a previous request can remain referenced by a later request handled by the same worker. Both issues affect deep parts of PHP's memory management and request handling rather than a particular Drupal function. ([thephp.foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/?embed=true))

The practical consequence is that a Drupal site can be at risk even when:

  • Drupal core and contributed modules are up to date.
  • No vulnerable PHP extension appears in composer.lock.
  • The site has no custom PHP code that obviously handles memory allocation.
  • The public website appears to work normally.

PHP is the execution engine for Drupal. A vulnerability in that engine sits below application-level permissions, input filters and route access checks. The PHP Foundation reports that an unauthenticated request can trigger the issue before application code runs when PHP is used with Apache's mod_php. It also confirmed remote code execution through application-level paths on PHP-FPM. That does not mean every Drupal deployment is exploitable in the same way, but it does mean that “our Drupal login is protected” is not a sufficient mitigation. ([thephp.foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/?embed=true))

Fixed PHP versions and support boundaries

The PHP project released fixes for the supported 8.2, 8.3, 8.4 and 8.5 branches. The fixed versions listed by the PHP Foundation are:

  • PHP 8.2.35
  • PHP 8.3.36
  • PHP 8.4.27
  • PHP 8.5.12

Use the package supplied by your operating-system vendor or hosting provider where possible. Distribution packages may include backported security fixes while retaining a different upstream version string, so do not decide that a server is vulnerable solely from a superficial version comparison. Confirm the vendor's security advisory and package changelog as well as the runtime version. The PHP project also states that PHP 8.2 reaches end of life on 31 December 2026, making this an important opportunity to plan a move to a newer supported branch rather than extending reliance on an ageing runtime. ([php.net](https://www.php.net/?=PHPE9568F35-D428-11d2-A769-00AA001ACF42&utm_source=openai))

Older PHP branches are a particular concern. The NCSC advises organisations using older PHP versions to move to a fully supported version. If an old platform cannot be upgraded immediately, treat it as an exception requiring compensating controls, vendor confirmation and a defined retirement date—not as a permanently acceptable state. ([ncsc.gov.uk](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php))

Step one: inventory every PHP execution path

Start with discovery. Do not inspect only the server hosting the main Drupal web container. A typical Drupal estate may contain PHP in the following places:

  • Apache with mod_php.
  • Nginx or Apache forwarding requests to PHP-FPM.
  • Container images used by web and queue workers.
  • Drush cron and scheduled command-line jobs.
  • Deployment runners and build servers.
  • Preview, staging and UAT environments.
  • Managed hosting platforms and control-panel PHP selectors.
  • Monitoring, search, migration or media-processing services that bundle PHP.

On a conventional Linux host, begin with the command-line binary:

php --version
php --ini
command -v php
dpkg-query -W -f='${Package}\t${Version}\n' 'php*' 2>/dev/null || true

These commands identify the CLI binary and Debian-family packages, but they do not prove that the web server uses the same binary or configuration. A Drupal administrator can expose a temporary diagnostic endpoint through the hosting platform, but never leave phpinfo() publicly accessible. If you need to use it, restrict it by IP or authentication and remove it immediately after the check.

For PHP-FPM, inspect the running service and pools:

systemctl list-units --type=service --all 'php*-fpm.service'
systemctl status php8.3-fpm --no-pager
ps -ef | grep '[p]hp-fpm'
grep -R "^[[:space:]]*listen[[:space:]]*=" /etc/php/*/fpm/pool.d/ 2>/dev/null

The exact service name varies by distribution and installed branch. Replace php8.3-fpm with the service actually present on the host. For containers, inspect the image rather than assuming the host's PHP is relevant:

docker compose ps
docker compose exec php php --version
docker compose images
docker image inspect example-drupal-php:production \
  --format '{{.RepoTags}} {{.Id}}'

For Kubernetes, check both the deployed image and the process inside a running pod:

kubectl -n production get deploy -o wide
kubectl -n production get pods -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image
kubectl -n production exec deploy/drupal-php -- php --version

These are inventory commands, not universal deployment commands. Adapt the resource names to your environment and make sure the resulting inventory is recorded. A useful minimum data set is hostname or workload, public exposure, PHP execution mode, version, owner, patch method, backup status and last verified date.

Step two: patch PHP using the correct supply chain

Distribution-managed PHP

On Ubuntu or Debian, first refresh package metadata and inspect available versions. Do not force a package version copied from a different distribution release.

sudo apt-get update
apt-cache policy php8.3-cli php8.3-fpm
apt list --upgradable 2>/dev/null | grep -E '(^|/)(php|libapache2-mod-php)' || true

After confirming that your configured repositories contain the vendor's security update, update the relevant PHP packages:

sudo apt-get install --only-upgrade \
  php8.3-cli \
  php8.3-fpm \
  php8.3-common \
  php8.3-opcache

The package names depend on the installed branch and extensions. If your site uses packages such as php8.3-mysql, php8.3-gd, php8.3-intl or php8.3-xml, update those packages through the same repository. Avoid replacing a distribution PHP installation with an unrelated third-party repository during an emergency unless that repository is already approved and tested by your organisation.

Restart or reload the relevant service after the package update, then verify the process that serves requests:

sudo systemctl restart php8.3-fpm
sudo systemctl reload nginx

php --version
systemctl is-active php8.3-fpm
journalctl -u php8.3-fpm --since "15 minutes ago" --no-pager

A restart is intentionally shown for PHP-FPM because old worker processes must not continue serving requests indefinitely. A web-server reload alone may not replace those workers. If using Apache with mod_php, restart the Apache service according to your operating system's service layout and verify that the loaded module points to the patched installation.

Containerised PHP

For containers, updating a running host package does not patch a PHP process inside an image. Update the base image or PHP image reference, rebuild, scan and redeploy. A simplified Compose workflow might look like this:

docker compose pull php
docker compose build --no-cache php
docker compose up -d --force-recreate php
docker compose exec php php --version
docker compose ps

Use your normal immutable-image process if the image is built in CI. The important properties are that the Dockerfile resolves a patched PHP base, the resulting image is tested, the image digest is recorded and old containers are drained rather than left serving traffic. Do not assume that docker compose pull changes an already-running container.

For Kubernetes, build and publish a new image, update the deployment and wait for readiness:

kubectl -n production set image deployment/drupal-php \
  php=registry.example.invalid/drupal-php:2026-10-23-patched

kubectl -n production rollout status deployment/drupal-php --timeout=10m
kubectl -n production get pods -l app=drupal-php \
  -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image

The image name and label selectors are examples of deployment identifiers, not Drupal or Kubernetes defaults. Replace them with values from your manifests.

Step three: update Drupal and Composer dependencies

PHP patching is the urgent control, but Drupal should also be brought to the latest supported security and maintenance release compatible with the project. Drupal's security team published a specific advisory telling site owners to reserve time for the PHP release and stating that updating to a PHP release containing the fix is the only complete mitigation for these vulnerabilities. ([drupal.org](https://www.drupal.org/security/p))

Before changing dependencies, take a database backup, confirm that configuration and files are included in your recovery plan, and check the working tree:

git status --short
composer show drupal/core-recommended --latest
composer outdated "drupal/*" --direct
drush status --fields=drupal-version,uri,db-status

For a Composer-managed Drupal project using the recommended core packages, the core update commonly follows this pattern:

composer update \
  drupal/core-recommended \
  drupal/core-composer-scaffold \
  drupal/core-project-message \
  --with-all-dependencies

drush updatedb:status
drush cache:rebuild
drush config:status
git diff -- composer.json composer.lock

Run this in a development or staging environment first. Review the lock-file changes, run automated tests and perform a smoke test covering anonymous pages, authenticated administration, forms, file uploads, search, cron and any bespoke integrations. Commit the resulting composer.lock; do not update production by running an unconstrained Composer command directly on the server.

Do not remove packages simply because they appear in a security-related incident report. The Drupal security advisory list also contains separate core and contributed-module advisories, including a recent critical Field UI issue and other October 2026 reports. Determine whether your site uses an affected project, read the individual advisory and update or disable the relevant component according to the maintainer's instructions. ([drupal.org](https://www.drupal.org/security/p))

Verification: prove that the patched runtime is serving traffic

Verification should test the complete request path. Checking php --version on the shell is insufficient if Nginx points to a different FPM socket, Apache loads another module, or the load balancer still sends requests to an old container.

Use several independent checks:

  1. Confirm the package or image digest installed on every web node.
  2. Confirm the PHP-FPM or Apache processes were replaced after patching.
  3. Request a harmless application endpoint through the real load balancer.
  4. Check logs for startup errors, pool failures and unexpected worker crashes.
  5. Verify that queue workers and cron jobs use the same supported PHP policy.
  6. Record evidence in the incident or change ticket.

For a Drupal deployment, a useful post-deployment sequence is:

drush status
drush cr
drush updatedb:status
drush config:status
drush cron
curl --fail --silent --show-error --head https://www.example.org/
curl --fail --silent --show-error https://www.example.org/user/login | head

The final request is only a basic availability check. It is not a substitute for functional tests, cache testing or authenticated checks. Be careful with drush cron in environments where cron performs irreversible business operations; use your normal staging and production approval process.

If patching was delayed: investigate before declaring success

The NCSC advises organisations that cannot immediately update to assess systems for evidence of compromise before upgrading or applying mitigation. This matters because patching removes the vulnerability but does not remove a web shell, altered Drupal code, rogue account or persistence mechanism that may already exist. ([ncsc.gov.uk](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php))

Preserve evidence before making destructive changes where your incident-response process requires it. At minimum, retain relevant reverse-proxy, web-server, PHP-FPM, WAF, authentication, deployment and cloud audit logs. Search for:

  • Unexpected POST requests to normally read-only endpoints.
  • New PHP files in public or writable directories.
  • Changes to index.php, settings.php, services YAML or custom modules.
  • Unexpected Drupal users, role changes or administrator logins.
  • Outbound connections from web or PHP-FPM processes.
  • Repeated worker crashes, abnormal memory exhaustion or unexplained restarts.
  • Changes to Composer files, deployment manifests or container images.

Use version control to identify application-file changes, but remember that uploaded files and generated directories may not be tracked:

git diff --exit-code -- web/modules/custom web/themes/custom
find web/sites -type f -name '*.php' -print
find web/sites/default/files -type f -name '*.php' -print
drush sql:query "SELECT uid, name, status, created, access FROM users_field_data ORDER BY access DESC LIMIT 25"

The database query is Drupal-specific but should be reviewed before execution on a sensitive production system. Do not paste credentials or personal data into an incident ticket. If you find credible indicators of compromise, isolate the affected workload, preserve logs and involve an incident-response provider. The NCSC explicitly recommends specialist assistance where an organisation is not confident in assessing compromise itself. ([ncsc.gov.uk](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php))

Temporary mitigation is not a replacement for patching

The PHP Foundation provides temporary mitigations for organisations that cannot update immediately, and the NCSC points administrators towards that guidance. Treat those measures as a short-lived bridge. They may reduce exposure for a particular execution mode or configuration, but they do not create the same assurance as running a fixed PHP release. ([ncsc.gov.uk](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php))

If a public-facing Drupal service cannot be patched or effectively mitigated, taking it offline may be safer than leaving it exposed. A controlled maintenance page, network isolation or restricted access through an approved administrative path is preferable to an untested assumption that a WAF rule will block every attack variant.

Governance lessons for Drupal teams

This incident illustrates why application dependency management and operating-system patch management must be connected. Composer can tell you which Drupal packages your project requires; it cannot tell you whether the PHP-FPM workers in a managed hosting platform have been replaced.

Organisations should maintain:

  • An asset inventory covering PHP runtimes, execution modes and owners.
  • A supported-version policy for PHP and Drupal.
  • An emergency patch procedure with named approvers and rollback criteria.
  • Repeatable image builds and deployment records for containerised sites.
  • Monitoring for web-worker crashes, unusual outbound traffic and file changes.
  • Backups tested through restoration, not merely reported as successful.
  • A clear rule for when an unpatchable internet-facing system is isolated.

Build pipelines should fail or warn when the PHP base image is outside policy. Runtime checks should be performed after deployment, and the result should be visible to operations staff. For managed hosting, obtain written confirmation of the patched PHP version or package revision and retain it with the change record.

A concise response checklist

  1. Identify every PHP runtime, including web, CLI, FPM, container and worker processes.
  2. Prioritise internet-facing Drupal sites and systems using Apache mod_php or PHP-FPM.
  3. Apply the vendor's fixed PHP package or rebuild from a patched PHP base image.
  4. Restart or replace old workers and verify the real serving path.
  5. Update Drupal core and affected contributed projects through the project's normal Composer workflow.
  6. Run functional, security and deployment smoke tests.
  7. Review logs and files for compromise if exposure existed before patching.
  8. Isolate systems that cannot be patched or mitigated promptly.
  9. Record versions, evidence, decisions and any remaining exceptions.

Conclusion

The correct Drupal response to PHP2Shell is not simply “run Composer update”. The vulnerable component sits beneath Drupal, so the response must cover the PHP runtime, web-server integration, FPM workers, containers, operating-system packages, managed hosting and incident monitoring.

Patch first where exposure is highest, verify the runtime that actually handles requests, then investigate any period during which the site was exposed and unpatched. Once the immediate work is complete, turn the inventory and verification steps into automation. The next PHP or Drupal security release will then be a controlled operational task rather than an emergency search through undocumented infrastructure.

Authoritative advisories

This article is based on the PHP Foundation's 21 October 2026 security release, the UK NCSC alert published on 21 October 2026, and Drupal's PHP update security announcement. Always follow the latest vendor advisory for your operating system, hosting provider, PHP branch and Drupal version before executing production changes. ([thephp.foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/?embed=true))