Security Research

PHP2Shell: Two Critical RCE Flaws in PHP Itself

Key takeaways
  • PHP2Shell is two bugs in the PHP interpreter, not in any application, framework or plugin. CVE-2026-109618 and CVE-2026-109619 are both rated 9.2 on CVSS v4.0 and both give unauthenticated remote code execution as the web server user.
  • Fixed releases landed on 21 October 2026: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. The PHP security team also published source backports for the end-of-life 7.4, 8.0 and 8.1 branches.
  • Apache mod_php is the worst case. The PHP advisory records a demonstrated exploit where an unauthenticated request triggers the condition before any application code runs, so a hardened application is no defence.
  • Updating is the only complete fix. Every published workaround narrows one attack path and leaves the class open, and two of the most widely repeated workarounds carry side effects worth reading before you apply them.

PHP2Shell is the name for two critical vulnerabilities in the PHP interpreter itself, CVE-2026-109618 and CVE-2026-109619, both rated 9.2 on CVSS v4.0. Either one gives an unauthenticated attacker remote code execution as the web server user, and neither needs a bug in your application. PHP shipped fixed releases on 21 October 2026. Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35.

This one is worth reading carefully even if you have already patched, because the two bugs have different triggers, different reachable configurations and different partial workarounds, and most of the early coverage mixed them up.

What PHP2Shell is

Two separate memory-safety defects in the PHP engine, disclosed together under one name. Both were published as GitHub Security Advisories by the PHP project on 21 October 2026, and both carry the same severity and the same CVSS v4.0 vector.

CVEPHP advisory titleAdvisory IDWhat it gives an attacker
CVE-2026-109618RCE via OOM corruptionGHSA-w8hw-w9fx-j79wUnauthenticated RCE as the web worker account
CVE-2026-109619RCE via stale global stateGHSA-6wmf-4rv5-p3rpUnauthenticated use-after-free, memory disclosure and RCE

Both are scored CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N, which reads as network reachable, no privileges, no user interaction, full confidentiality, integrity and availability impact on the affected host, with high attack complexity and attack requirements present. Both are filed against the same five weaknesses: CWE-416 use after free, CWE-755 improper handling of exceptional conditions, CWE-824 access of uninitialized pointer, CWE-1325 improperly controlled sequential memory allocation and CWE-1419 incorrect initialization of resource.

High attack complexity is the only comfort in that vector, and it is a thin one. The advisory for CVE-2026-109618 states that full remote code execution was validated over HTTP against mod_php using default PHP input and memory limits and a normal prefork configuration. Additional workers and unrelated traffic raise the number of attempts an attacker needs. They do not remove the condition.

How the PHP2Shell vulnerabilities work

Both bugs come from the same structural weakness: PHP can be interrupted part way through handling a request, and the code that runs afterwards assumes the interruption never happened.

CVE-2026-109618: an out-of-memory error that skips cleanup

When PHP hits an out-of-memory condition it calls longjmp() to leave the current context. In some contexts, request memory is reachable before it has been fully initialised. If an allocation fails at that point, PHP does not account for the early exit, and request memory is left in an invalid state. Request shutdown then reads or branches on that state, and that is the path to code execution.

What makes this reachable from ordinary traffic is that the allocation which fails does not have to be related to the memory that ends up corrupted. The advisory lists several ways a request can reach the memory limit while staying inside post_max_size:

  • Multipart requests with no usable declared content length.
  • An unlimited number of headers per multipart part.
  • Empty file controls, which do not count towards max_file_uploads but still create file metadata arrays.
  • Application-specific inputs that cause large allocations.

The fix replaces the immediate longjmp() with a delayed soft out-of-memory condition handled at the next safe point in the virtual machine, plus a hard out-of-memory path that calls _exit() at a higher threshold.

CVE-2026-109619: state from one request reused by the next

PHP keeps global state that a worker holds between requests, covering error, session, object and extension state. Some of that state can point at memory owned by a previous request. When a fatal error interrupts a request before its cleanup runs, or a diagnostic repopulates error state after shutdown has started, the request heap is freed while the global still references it. An operation that runs before normal request initialisation then uses the stale value. The result is a use-after-free that a crafted heap layout can turn into code execution.

Exploiting it needs two requests that land on the same worker: a producer that leaves the dangling state behind, and a consumer that reads it.

RoleConfirmed cases from the PHP advisory
ProducerParse and compile failures, uncaught errors, a failed require, "Maximum execution time exceeded", and out-of-memory conditions. Nonfatal diagnostics during later shutdown stages can also repopulate error state.
ConsumerINI diagnostics that run before request initialisation, including warnings from invalid or deprecated INI values even when they are never displayed or logged. Also session upload progress handling, which can run before ordinary session initialisation.

On PHP 8.5 the advisory names one confirmed producer directly: with fatal_error_backtraces enabled, fatal error reporting stores request-owned backtrace state that is never reset on shutdown. The advisory describes that as the basis of the confirmed mod_php exploitation.

A second path runs through the session lifecycle. An out-of-memory condition during input processing can leave session and decoder globals pointing at freed memory, and the advisory records that this one was validated against both mod_php and PHP-FPM.

Which PHP versions are affected by PHP2Shell

The two advisories list affected and patched versions only for the branches PHP still supports. Treat the older branches as affected too, because the project went to the trouble of backporting the fix to them.

BranchAffectedFixed release
8.5below 8.5.128.5.12
8.4below 8.4.278.4.27
8.3below 8.3.368.3.36
8.2below 8.2.358.2.35
8.1, 8.0, 7.4end of life, no binariesminimal source backports published by the PHP security team
7.3 and older, 5.xnot covered by any advisorynothing from the project

On the question of how far back the defect reaches, the vendor is quiet and the researchers are not. Sansec dates the earliest affected code to 2012 and names PHP 5.4.0 as the first affected release, and watchTowr described the exposure as spanning PHP 5 through 8. Neither figure appears in the GitHub advisories, so treat the pre-7.4 range as credible reporting and not as a vendor statement. Either way the practical consequence is the same. If you are running anything below 8.2 you have no binary fix, and the answer is a distribution backport or an upgrade.

PHP 8.2 reaches end of life on 31 December 2026. If patching PHP2Shell means landing on 8.2.35, treat that as a stop on the way and not a destination.

Why mod_php is the worst case

Before you decide how urgent your own exposure is, check how PHP is wired into your web server. The same two bugs behave very differently depending on the answer.

SAPIStatus in the PHP advisories
Apache mod_phpWorst case. Shutdown runs even when startup fails, so the condition is reachable before any application code runs. Full RCE validated over HTTP.
PHP-FPMThe child exits when startup fails, which blocks that path. A complete FPM exploit for CVE-2026-109618 was not demonstrated, though source analysis suggests susceptibility, and the session lifecycle path in CVE-2026-109619 was validated against FPM.
FrankenPHPLikely susceptible on source analysis, because it calls shutdown unconditionally. No early consumer was established in its default configuration.
LSAPIModelled on FPM. Application-specific exploits considered likely, not validated.

There is a detail inside the mod_php row that decides whether a workaround even reaches the vulnerable code. For CVE-2026-109619, .htaccess and the Apache php_value, php_flag and php_admin_* directives apply before request startup, and php.ini does not. Under FPM the early consumer is only exposed if the frontend passes PHP_VALUE or PHP_ADMIN_VALUE parameters, and pool settings and .user.ini do not provide the same early consumer. A setting applied in the wrong place looks like a mitigation and is not one.

How to patch PHP2Shell

  1. Inventory every PHP runtime you operate, not every site. One host often runs several interpreters: the distribution package, a vendor build under a control panel such as cPanel or Plesk, a container image, and a copy inside an appliance. The advisory applies to each of them separately.
  2. Update to the fixed release for your branch, meaning 8.5.12, 8.4.27, 8.3.36 or 8.2.35. On 7.4, 8.0 or 8.1, ask your distribution or LTS vendor for the backport, since Linux distributions and LTS vendors received the patches in advance of the public release.
  3. Restart every worker. Both bugs live in state a worker holds across requests, so a running worker keeps the old behaviour until it is replaced. Restart Apache or PHP-FPM. Then confirm with php -v against the binary the web server actually loads. That is not always the one on your shell path.
  4. Check your package source before you trust it. Sansec reported on release day that the volunteer-maintained Remi repository did not yet carry the fix, and the php.net announcement said the same. A package manager reporting no update available is not the same as being patched.
  5. Then go back and look for compromise. Patching stops the next attempt. It tells you nothing about the previous one, and there is credible reporting of exploitation before the patch was public.

Mitigations if you cannot patch PHP today

Take the official framing seriously here. The php.net advisory states that only an update is a complete solution, and that the measures below reduce exposure to specific attack paths without making an installation safe. Each one closes a named path and leaves the underlying class open.

For CVE-2026-109618, set memory_limit = -1 in a way the application cannot override. That means php_admin_value in the FPM pool or in the Apache configuration. Keep requests small with post_max_size = 8M for applications with no file uploads, or 32M where uploads are needed, with upload_max_filesize kept below it. If you bound memory at the container level, use a cgroup memory.max limit, and do not substitute RLIMIT_AS or RLIMIT_DATA, because those trigger the vulnerable allocation failure instead of preventing it.

memory_limit = -1 is the one mitigation to think twice about. It removes the limit that the attack abuses, and it also lets a single request consume all the memory on the server. On a shared or memory-tight host you may be trading a hard-to-exploit memory bug for a trivially reachable denial of service.

For CVE-2026-109619, set session.upload_progress.enabled = 0 and restart every worker, and on PHP 8.5 also set fatal_error_backtraces = 0 and restart again. Apply both through php.ini, an FPM pool or the server configuration, and do not rely on ini_set() or an FPM .user.ini, because neither runs early enough. Three more things help. Remove invalid or deprecated per-request settings that emit diagnostics before initialisation. Fix the fatal errors and execution timeouts an attacker can reach. And if your performance budget can absorb it, set pm.max_requests = 1, which stops a worker carrying state from one request into the next at all.

Two things that are widely recommended and do not work. Hiding diagnostics with error_reporting, display_errors or the @ operator does not help, because the stale state is read whether or not anything is printed. And a CMS maintenance mode does not help either, because PHP parses the request before the application decides to show a maintenance page. If you need to take an application off the air, stop PHP-FPM and serve a static page, or reject everything except GET and HEAD at the web server.

One broad control does have real coverage: block multipart/form-data at the frontend. Every known trigger reported so far arrives as a multipart request, and the advisory suggests this for any application that does not use multipart. It also breaks every file upload and every multipart form, so test a real submission afterwards.

How to tell whether PHP2Shell was exploited

Neither advisory publishes file hashes, addresses or paths, so detection is behavioural. The signal is the crash pattern the attack produces while it is being tuned, because the technique needs repeated attempts and the failures are noisy:

  • Unusual multipart requests followed by memory limit errors or repeated HTTP 500 responses.
  • A spike in PHP fatal errors around memory_limit, particularly from multipart requests.
  • Unexpectedly large or malformed PHPSESSID response cookies. That is the shape memory disclosure takes on the upload-progress path.
  • PHP worker restarts or churn with no deployment or traffic event to explain them.

The public record on exploitation is genuinely mixed. It is worth being precise about who claims what. Cloudflare reported attacks against PHP sites at 07:43 UTC on 21 October, ahead of the 14:00 UTC release, and deployed WAF rules. watchTowr said it confirmed remote code execution on 20 October and later reported observations from its honeypot network. Patchstack said it had confirmed active exploitation incidents. Against that, NCSC-NL stated it had no indication of active exploitation. Sansec expected mass exploitation once the writeups and tooling were public, which is a forecast and not an observation. Read the disagreement as a reason to go looking in your own logs, since none of these parties can see your traffic.

How far back to look is a judgement call. Aikido advised hunting for compromise going back at least to June 2026. The reports to PHP were made on 11 August and 24 August 2026, and the underlying code is years older, so there is no date before which the bug did not exist. There is only a date before which nobody is known to have been using it.

What PHP2Shell changes about your attack surface

For most teams, "our attack surface" means the application: the endpoints, the authentication, the business logic an attacker can reach from outside. That is where exploitable bugs usually live and where testing should be hardest. PHP2Shell is a reminder that the surface also includes the thing running the application. A clean application buys no immunity when the interpreter underneath it can be driven into a use-after-free by a request that never reaches your code.

The other direction is the part that gets lost in a week like this one. A patched interpreter does not make an application safe. The reason PHP2Shell is frightening is that it needs no application bug, and the reason most real compromises happen is that there was one. Both statements are true at the same time. A team that spends the next month on PHP versions and none of it on its own authorisation logic has moved its risk around instead of reducing it.

The discipline is the same in both places: a finding counts once you have watched the system behave, not when a version number or a feed says so. An interpreter is not vulnerable because a scanner matched a banner, and an endpoint is not safe because a scanner found nothing. That is what we do for the half of the surface an attacker can reach right now, the running application and its APIs: authenticated, context-aware tests that validate each finding with a repeatable exploit before it reaches your pipeline.


Related reading: arrayref: 86 Minutes of Compromise - Vulnerability Prioritization with CVSS and EPSS

Frequently asked questions

Am I affected by PHP2Shell?

If you run PHP 8.5.11, 8.4.26, 8.3.35, 8.2.34 or anything older on an internet-facing server, assume yes until you have updated. The flaws are in the PHP interpreter, so no application, framework, CMS or plugin version makes you safe, and the PHP advisory records a demonstrated exploit against Apache mod_php that runs before any application code. PHP-FPM is harder to attack and is not exempt, because one of the two paths was validated against it.

Which PHP versions fix PHP2Shell?

PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, all released on 21 October 2026. The PHP security team also published minimal source backports for the end-of-life 7.4, 8.0 and 8.1 branches for people building from source or relying on an LTS vendor. There are no official binaries for those branches, and nothing at all for PHP 5 or 7.3 and earlier.

Does updating my CMS or my plugins fix PHP2Shell?

No. The vulnerability is in the PHP interpreter that runs your CMS, so the fix has to be applied to PHP itself by whoever controls the server or the hosting plan. A fully updated WordPress, Magento, Laravel or Drupal install on an unpatched PHP build is still exposed.

Is disabling session upload progress enough on its own?

No. Setting session.upload_progress.enabled to 0 removes one confirmed consumer of stale state in CVE-2026-109619, and the advisory recommends keeping it off if you do not use the feature. It leaves the other confirmed consumer, the INI diagnostics path, and it does nothing for CVE-2026-109618. The PHP advisory is explicit that updating is the only reliable mitigation.

We patched the same day. Do we still need to look for compromise?

Yes. Cloudflare reported attacks against PHP sites several hours before the fixed releases were published, so same-day patching does not cover the window before the patch existed. Check your logs for unusual multipart requests followed by memory limit errors or repeated 500 responses, oversized or malformed PHPSESSID response cookies, and unexplained PHP worker restarts.

Who found PHP2Shell?

The PHP advisories credit four reporters, working from the research teams at Wiz, Areion and OpenAI alongside an independent researcher, with the fixes developed by the PHP project maintainers. PHP has said it worked with Wiz, Cloudflare and OpenAI on the patches, and Cloudflare deployed WAF rules ahead of the public release.

Related posts
Security Research The #1 WordPress Hacker This Month Wasn't Human Security Research Benchmarking Intrudify against two top-tier AI pentesters AI Security Reviewing and Testing Code Written with AI Assistants
Back to all posts