DEV Community

Noor Yasser
Noor Yasser

Posted on Originally published at nooryasser.com

PHP2Shell: Patch the PHP Runtime, Not Just Your App

Originally published on nooryasser.com.

PHP2Shell is the name used for two critical PHP runtime vulnerabilities: CVE-2026-109618 and CVE-2026-109619. Both sit below application code, inside PHP's request lifecycle and memory handling.

If you run PHP in production, the main action is simple: patch the PHP runtime and replace every running worker. Updating Laravel, WordPress, Drupal, Composer dependencies, Nginx, or a WAF does not patch the affected engine.

This article explains where the bugs live, which releases fix them, and how to prove that your production fleet actually changed. The vulnerability mechanics and versions come from the primary advisories. The rollout and investigation steps are defensive engineering recommendations.

Why these bugs are different from an application vulnerability

A PHP web request crosses several layers:

  1. the web server or proxy accepts the request;
  2. PHP allocates request state and initializes extensions;
  3. application and framework code run;
  4. PHP cleans up request memory;
  5. a persistent worker may stay alive for the next request.

PHP2Shell breaks assumptions inside that native lifecycle.

A framework login check is too late to protect a bug that can execute while PHP is preparing or cleaning request state. Input validation and authentication still matter, but they are not substitutes for a fixed runtime.

CVE-2026-109618: unsafe recovery from memory exhaustion

The advisory describes an abrupt longjmp() exit after an allocation failure. Some internal request data may still be incomplete, but cleanup later treats it as valid. What should have been a clean memory-limit error can therefore become memory corruption.

The fix changes the failure model. PHP now uses a deferred soft limit handled at a VM safe point, plus a higher hard limit that terminates the process immediately.

This matters operationally after patching. A hard-limit termination may skip shutdown callbacks and can look like exit status 137. Do not classify every post-update worker restart as an attack. Use stderr, container events, kernel signals, and platform telemetry to identify the actual cause.

Also avoid putting the only copy of important business state in a shutdown callback. Durable work needs a recoverable state transition before the worker can disappear.

CVE-2026-109619: request state outlives its memory

The second bug is a use-after-free pattern.

Request-owned state can remain referenced after the request heap has been released. If an interruption skips a reset, the next request handled by the same persistent worker may consume stale state before normal initialization replaces it.

The published research identifies concrete PHP internals, including session ID state, unserializer bookkeeping, and fatal-error backtrace state. These are engine globals, not vulnerable columns in your application database.

Researchers demonstrated remote code execution and separate process-memory disclosure in tested deployments. That proves serious technical impact. It does not mean every PHP installation has the same reachable exploit chain or that every exposed server was compromised.

Keep three findings separate:

  • Vulnerable: an affected runtime and reachable configuration existed.
  • Attempted exploitation: logs or telemetry show crafted traffic consistent with the vulnerable path.
  • Confirmed compromise: host, process, or business evidence shows unauthorized impact.

Fixed upstream releases

The PHP security announcement lists these fixed releases:

PHP line Fixed release
PHP 8.2 8.2.35
PHP 8.3 8.3.36
PHP 8.4 8.4.27
PHP 8.5 8.5.12

Older branches need special handling. PHP published minimal source backports for 7.4, 8.0, and 8.1, and some Linux distributions ship their own backported fixes.

That creates an important versioning trap: a distribution package may keep an older upstream base number while adding the security patch in the package revision.

For example, do not compare php -v with 8.3.36 and immediately decide that an Ubuntu package is vulnerable. Check the complete package revision against the security notice for your exact distribution release and repository.

“The package manager says latest” is not evidence unless the configured repository actually contains a fixed build.

Inventory the runtime that serves traffic

Start with read-only checks:

command -v php
php -v
php --ini
php -m
php -r 'printf("PHP=%s SAPI=%s\n", PHP_VERSION, PHP_SAPI);'
Enter fullscreen mode Exit fullscreen mode

These commands describe the CLI binary you invoked. They do not prove that PHP-FPM, Apache mod_php, CGI, FrankenPHP, LiteSpeed, a container, or a serverless image uses the same runtime.

For every production service, record:

  • hostname and alternate origin;
  • PHP SAPI and complete version;
  • package source and vendor advisory;
  • FPM socket or Apache module;
  • container image digest;
  • worker or instance identity;
  • extension compatibility;
  • restart and verification status.

A common failure mode is a patched CLI beside an older FPM service. Another is a new image tag with old Pods still serving traffic.

A safe production patch workflow

1. Map the real exposure

Include alternate hostnames, preview environments, old virtual hosts, direct-origin access, replicas outside the main load balancer, scheduled jobs, and autoscaling templates.

2. Select a trusted fixed build

Match both CVEs to either a fixed upstream release or a vendor notice covering the exact package revision. Keep the runtime and native extensions compatible.

3. Prepare recovery

Back up configuration and application data. Keep a known-good patched artifact available. A rollback that restores the vulnerable runtime is not a safe recovery plan.

4. Exercise a canary

Test login, sessions, uploads, imports, checkout, APIs, queues, scheduled jobs, and error handling. Watch error rate, latency, memory, worker churn, and extension compatibility.

Do not send exploit payloads to production to “prove” the bug.

5. Replace the workers

Updating files on disk is not enough. Drain and restart or replace every affected PHP worker and replica.

For an installation that really uses Ubuntu's PHP 8.3 FPM service, the operational sequence might include:

sudo /usr/sbin/php-fpm8.3 -t
sudo systemctl restart php8.3-fpm
sudo systemctl is-active php8.3-fpm
Enter fullscreen mode Exit fullscreen mode

An active service only proves that it is running. Verify the package revision and the runtime reached through the actual serving path.

Restarting Nginx alone does not replace a separate FPM process. Apache mod_php requires replacing the Apache processes that loaded the module.

6. Verify the whole fleet

For containers, rebuild from a fixed base image, publish an immutable digest, roll it out, and confirm that no old digest remains in:

  • serving replicas;
  • Jobs and CronJobs;
  • standby regions;
  • deployment templates;
  • rollback targets;
  • autoscaling definitions.

A successful image build is not proof that production stopped running the old image.

What if you cannot patch immediately?

Temporary containment reduces specific paths; it does not provide a general fix.

The official guidance discusses measures such as disabling session upload progress before request parsing and, for PHP 8.5, disabling fatal-error backtraces. Administrative pool configuration matters because a setting inside application code may run after vulnerable initialization work.

Isolation may be the safer short-term choice: restrict access, disable unneeded upload paths at the frontend, or temporarily remove the affected service while the runtime is repaired.

Treat every workaround as an owned exception with a deadline. Do not let a containment setting become a permanent substitute for upgrading.

Why a WAF is not the patch

Cloudflare reported deploying default protections and observing attack traffic. That is useful defense in depth, but it is not proof that your origin runtime is fixed.

Check that all traffic actually traverses the protected hostname and that no direct origin, alternate domain, preview environment, or internal path bypasses it.

Managed cloud does not automatically answer patch ownership either. Some platforms update supported runtimes; container-based services commonly require the customer to rebuild and redeploy. Get evidence for the exact service you use.

Investigate without overstating the evidence

Amazon Threat Intelligence reported an attempt dated October 17, before public disclosure, followed by broader attempts. Its report also said it was unclear whether those attempts would have succeeded against real systems.

That distinction matters. Preserve relevant evidence from at least October 17 where available:

  • web and proxy request metadata;
  • PHP worker crashes and restart reasons;
  • unexpected child processes;
  • file changes;
  • outbound connections;
  • container and kernel events;
  • secrets-access anomalies.

An empty Laravel or CMS log does not prove that native request processing was safe. If credible compromise evidence appears, preserve evidence, rebuild from trusted artifacts, rotate affected credentials, and involve incident response.

Applying the patch prevents the repaired bug from being reused. It does not remove an existing foothold.

Completion checklist

Close the patch task only when:

  • every serving PHP runtime has a documented fix for both CVEs;
  • all old workers and container images are replaced;
  • normal business flows pass the canary;
  • the live SAPI and package revision are verified per instance;
  • vulnerable rollback and autoscaling artifacts are removed;
  • investigation findings have a separate owner and evidence record.

References


Read the complete source-backed guide, including vendor-specific package notes and the full reference list: PHP2Shell: where the PHP flaws are and how to patch.

Top comments (0)