PHP2Shell: millions of attempts so far. Is your server next?

Two critical PHP flaws let an unauthenticated request run code on a PHP server before your application's login page loads. Patched releases are out, and anything you run on PHP, from billing front ends to phpMyAdmin, needs them now.

Share
PHP2Shell: millions of attempts so far. Is your server next?

Every self-hosted panel has a login page, and most people think of it as the front door, but PHP2Shell walks round it. The two flaws PHP patched on 21 October are triggered by the PHP runtime while it is still handling the request, so your panel's password, its two-factor prompt and its rate limiter never get a say.

The panels most people use to run Xray or sing-box aren't written in PHP, which helps, but plenty of what surrounds them is, and it tends to be the part nobody has looked at since installation.

What broke

PHP2Shell is the name given to CVE-2026-109618 and CVE-2026-109619, fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. The PHP Foundation's post-mortem scores them 9.2 out of 10 under CVSS 4.0. Both come down to PHP mishandling its own memory when a request ends badly.

The first concerns what happens when a request hits memory_limit. PHP abandons whatever it was doing and jumps straight to its clean-up code, and in some places that leaves half-built internal data behind, which the clean-up then treats as valid. A crafted multipart upload can drive memory use past the limit while staying within the configured size limits, and PHP parses that body before your script starts. On Apache with mod_php, the Foundation says, full code execution was demonstrated against default settings by an unauthenticated request, before any application code ran.

An unauthenticated upload pushes PHP past memory_limit before your script starts; clean-up trusts half-built data, causing memory corruption demonstrated as code execution on Apache with mod_php.
The first bug fires before your application runs a single line.

The second is about state that survives between requests. A PHP worker handles many requests in turn and keeps some global bookkeeping between them. The advisory explains that a fatal error at the wrong moment can leave that bookkeeping pointing at memory that has since been freed, and the next request to the same worker can trip over it. One of the routes in is session upload progress, which is on by default and doesn't need the application to start a session.

A fatal error frees request 1's memory but the worker still points at it; request 2 on the same worker reads the stale pointer, a use-after-free.
One bad request leaves a trap for the next one on the same worker.

None of this lives in your application. Datadog's researchers reproduced the first bug on PHP 8.5.11 with a single unauthenticated POST and note that "any PHP script exposes the vulnerable path, even one that only prints a response". Wiz, one of the teams credited with the discovery alongside Calif and OpenAI, says it achieved unauthenticated code execution against the stock WordPress, Drupal and Joomla container images.

How worried to be

Apache with mod_php is the worst case, and the only setup where code execution before the application runs has been shown. PHP-FPM, the usual partner for nginx, is harder to hit because FPM kills a worker whose request startup fails, but the Foundation says code execution was still confirmed there through application code paths. php-cgi, FrankenPHP and LiteSpeed are also listed as affected in third-party advisories. Version coverage is wide: Datadog says PHP 5 through 8.1 are vulnerable too, and those branches get no official fixes, although PHP published minimal source backports for 7.4, 8.0 and 8.1.

Exploitation is the murkier question. watchTowr says it is observing exploitation attempts against both bugs, and Cloudflare told reporters it was seeing early attacks and had deployed temporary firewall rules. Cloudflare's chief executive, Matthew Prince, later put numbers on it: thousands of attempts across Cloudflare's network in the 10 hours before the bugs were announced, tens of thousands in the 45 minutes after, hundreds of thousands within two hours, and millions by the following night. Those are attempts, not confirmed break-ins, but the wave that started before the announcement tells you the details were already circulating. PHP's own security team found no functional public exploit at release time, and neither CVE was in CISA's Known Exploited Vulnerabilities catalogue on 21 October. That gap is unlikely to last, since the CVE identifiers leaked a day early, Calif accidentally published a video of its exploit running, and Chainguard shipped patched container images more than six hours before the coordinated release, which PHP says let anyone study the fix in binary form. Chainguard's chief executive says he made the call because exploits had already appeared in the wild. Whoever is right about the embargo, the effect is the same for you: plan as though working exploits exist.

Which panels matter

The common Xray panels are not PHP. 3x-ui and the original x-ui are Go programs with their own web server; Marzban and Hiddify Manager are Python. If one of those is all that faces the internet, PHP2Shell doesn't reach the panel itself.

The exposure sits around them:

  • Subscription and billing front ends. Xboard is a Laravel application and V2Board, which it descends from, is PHP too. Neither project can fix this for you: the runtime underneath is what needs patching.
  • Firewall and router interfaces. The web GUIs of OPNsense and pfSense are PHP, and plenty of people terminate WireGuard or OpenVPN on them. I haven't confirmed which PHP setup each uses or whether a fixed build has shipped, so check the vendors' security announcements. These interfaces should never face the open internet anyway.
  • Database tools. phpMyAdmin is still installed beside a remarkable number of panel databases, often on a guessable path.
  • Everything else on the box. That includes the WordPress install you forgot about, and any monitoring dashboard or NAS interface running PHP.

A compromised billing site hands over customer records and, usually, the API credentials for your nodes. Wiz also describes a memory-disclosure route that needs no code execution at all and could leak whatever secrets the PHP process holds.

Check and patch

Run php -v, and php-fpm -v or the Apache module listing if the web server uses a different binary. Then check the package, because distributions backport fixes without changing the upstream version number.

  • Debian: the security tracker lists php8.4 8.4.27-1~deb13u1 for trixie (DSA-6574-1) and php8.2 8.2.35-1~deb12u1 for bookworm (DLA-4848-1) as fixed. Testing and unstable were still vulnerable at the time of writing.
  • Ubuntu: USN-8948-1 fixes php8.3 8.3.6-0ubuntu0.24.04.13 on 24.04 and php8.1 8.1.2-1ubuntu2.28 on 22.04, among others. Note that 24.04 still reports PHP 8.3.6 afterwards, so compare the package version with apt policy, not php -v.
  • RHEL and derivatives: Red Hat rates the bugs Important rather than Critical, because RHEL defaults to FPM. Check its CVE page for errata before assuming you are covered.
  • Remi's repository: if you get PHP from the volunteer-run Remi repository, common on RHEL, Alma and Rocky, the PHP Foundation warned that it would not have updates on release day and urged users to move to distro packages or build from source. Check that a fixed build has landed before assuming you are patched.
  • Containers: the official php images were rebuilt on 21 October. Rebuilding your own image on a freshly pulled base is the patch; an image built last week keeps the old runtime. Confirm the version inside the running container after you redeploy.

After updating, restart every PHP worker. Reloading the web server alone can leave old FPM children running vulnerable code.

If you can't patch today

PHP is blunt about this: updating is the only complete fix. The UK's NCSC told organisations to patch or take affected systems offline, which is sound advice for anything that doesn't strictly need to be online. If a machine must stay up, the advisories offer partial measures, all to be set where the application can't override them (php.ini, the FPM pool or server config), followed by a restart of every worker:

  • Set memory_limit=-1 to remove the predictable out-of-memory trigger, and rely on a container or cgroup memory limit instead.
  • Keep post_max_size low, cap request size at the reverse proxy, and block multipart uploads there if the application doesn't need them.
  • Set session.upload_progress.enabled=0. It removes one demonstrated route and leaves others, which is why Datadog says flatly that it "doesn't protect you". PHP recommends leaving it off even after patching.
  • On PHP 8.5, set fatal_error_backtraces=0.
  • If performance allows, pm.max_requests=1 stops an FPM worker carrying state into another request.

The cheapest mitigation of all is access control in front of PHP. A billing site has to be public; an admin area, phpMyAdmin or a firewall GUI doesn't. Put those behind your VPN or an IP allow-list.

What to look for in the logs

A successful attack need not crash the worker, so a quiet error log proves little. The signs researchers point to are unusually large or deeply nested multipart submissions, particularly to scripts that don't accept uploads; "Allowed memory size exhausted" errors during request startup; repeated HTTP 500s and odd worker churn; and, most telling of all, shell processes or curl and wget started by the web user (www-data, apache or nginx).

The verdict

PHP bugs rarely get more serious than this one, which needs no login, doesn't depend on the application and has been shown working against default configurations. For the typical Xray operator the panel itself is safe, because it is written in Go or Python. The PHP around it is not, and it is usually the part of the stack with the least attention and the most credentials.

What to do

  1. List every machine that runs PHP: billing sites, firewalls, phpMyAdmin, dashboards, NAS boxes, the WordPress install you forgot about. command -v php php-fpm frankenphp on each host and container is a quick start.
  2. Update the PHP package or rebuild the container, restart every worker and confirm the package version.
  3. Take admin interfaces off the public internet while you're at it.
  4. Apply the configuration mitigations to anything you can't update today, and consider taking it offline instead.
  5. Search the past fortnight of logs for the signs above, and look for unexpected processes owned by the web user.

If the only things on your server are a Go panel and an Xray core, this week's more pressing job is the HTTP/3 version check. If there is PHP anywhere on the box, update it tonight.