PHP2Shell: Why Patching Means Restarting Every PHP Worker

PHP2Shell (CVE-2026-109618/109619) breaks PHP's request lifecycle via OOM and stale state. What changed, which versions fix it, and how to verify.

PHP2Shell: Why Patching Means Restarting Every PHP Worker
In this essay

PHP2Shell is a pair of pre-authentication remote code execution flaws in PHP’s Zend Engine. Its key lesson is architectural. The bug lives in the request lifecycle beneath your application, so the fix is not done until every PHP worker in memory has been replaced by a patched process.

What was disclosed, and how fast it moved

The two identifiers are:

  • CVE-2026-109618: a memory-safety failure during out-of-memory (OOM) handling.
  • CVE-2026-109619: a use-after-free caused by stale global state.

Both score 9.2 (Critical) under CVSS v4.0. Under CVSS v3.1 they score 9.8 and 8.1 respectively.

Researchers from Calif.io, OpenAI and Wiz reported the issues on 1 August 2026, with AI models assisting the automated testing. The timeline then compressed sharply:

  • 17 October: Amazon’s MadPot honeypot network recorded exploitation attempts via anonymizing networks. No patch existed yet.
  • 21 October, 14:00 UTC: the PHP Group shipped emergency releases.
  • 26 October: CISA added CVE-2026-109618 to its Known Exploited Vulnerabilities catalog after observing broad scanning and attacks.

The detail to weigh most is that exploitation came before disclosure. On release day, defenders were already behind.

Root cause: cleanup code that trusts a half-built request

Close-up of a computer memory module, illustrating how PHP worker processes keep state in RAM between requests

Both flaws trigger before any application code loads. WordPress, Laravel or your own input validation never gets a chance to intervene.

OOM unwinding through longjmp (CVE-2026-109618)

While parsing input such as multipart form data, the Zend allocator (zend_alloc) checks allocations against memory_limit. When the limit is hit, PHP does not unwind the stack step by step. It calls the C runtime’s longjmp() to jump out of the current context and abort the request.

A non-local jump skips whatever initialization code had not yet run. As a result, some internal request structures are left partially initialized. The request shutdown phase (RSHUTDOWN) then assumes those pointers and objects are valid. Freeing or walking uninitialized pointers corrupts the heap.

The general lesson is that any cleanup routine running after an abrupt abort must treat state as possibly incomplete.

Global state outliving its request (CVE-2026-109619)

Some PHP features keep temporary global pointers into per-request memory. Upload progress tracking (session.upload_progress) is the cited example. When a request ends abnormally, that global pointer may not be reset, even though the memory it references has already been released with efree.

The result is a dangling pointer. When the same worker accepts the next request, the stale global can be read before it is reinitialized. That produces a use-after-free.

Why your SAPI changes the exposure

The Server API (SAPI) decides how long a process lives. That, in turn, decides how long bad state can survive.

  • Apache mod_php: PHP runs inside the httpd process, and the faulty cleanup path runs unconditionally even when startup fails. Memory corruption can occur before the app loads, which makes this the highest-risk setup.
  • PHP-FPM: each worker serves many requests in sequence, up to pm.max_requests. If an OOM does not terminate the worker, the dangling global from CVE-2026-109619 can carry over to the next request on the same PID.
  • FrankenPHP and LiteSpeed worker modes: persistent processes or threads trade isolation for speed. Reusing memory across cycles keeps heap state more stable, so stale references between requests matter more.

The takeaway is that worker reuse is a performance feature that quietly turns request cleanup into a security boundary.

Patching correctly: what runs in RAM is what counts

The fixed releases are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Every branch from 5.4 through 8.5 before those releases is affected. For end-of-life branches (8.1, 8.0, 7.x), the PHP team published source patches only, with no packaged binaries. Teams on those branches must build the patches themselves or rely on a distribution vendor.

Upgrade through your OS package manager, then restart the service:

sudo apt-get update && sudo apt-get --only-upgrade install php8.3-fpm php8.3-cli php8.3-common
sudo systemctl restart php8.3-fpm   # or php-fpm / httpd / frankenphp

Then verify the processes, not the CLI. php -v reports the binary on disk, not workers that were started last week. Confirm that worker start times are newer than the upgrade:

ps -o pid,lstart,cmd -C php-fpm8.3   # process name varies by distro

On shared hosting or a managed platform, ask the provider whether workers were restarted. Knowing that packages were updated is not enough.

Stopgaps and detection while you test

If compatibility testing delays the upgrade, two settings make attacks less reliable:

session.upload_progress.enabled = 0
client_max_body_size 8M;

The first disables the upload-progress surface tied to CVE-2026-109619. The second trims oversized bodies before they reach PHP. The PHP Security Team is clear that neither is a fix. Upgrading the runtime is the only complete defense.

For detection, use runtime monitoring such as eBPF-based tools or Falco. Alert when php-fpm or httpd workers spawn /bin/sh, /bin/bash or python under the web user, such as www-data or nobody. That behavior is rarely legitimate.

The broader lesson

PHP2Shell is not a reason to abandon persistent workers. It is a reason to treat “patched” as a property of running processes. It is also a reason to inventory every place PHP runs, including old containers and forgotten mod_php hosts. Attacks started before the fix existed, so that inventory belongs on this week’s list.

Sources

  1. en.wikipedia.org
  2. dynatrace.com
  3. aws.amazon.com
  4. nvd.nist.gov
  5. sara-open.sirp.io
  6. success.trendmicro.com

AI-assisted · Reviewed by PKN

0

Responses

Loading comments…