PHP2Shell Postmortem: 25 Rooted Servers and 6 PHP Lessons

PHP2Shell postmortem: how a custom exploit chain rooted 25 servers running old PHP 8.0, which releases fix it, and six patching lessons for PHP teams.

TL;DR

  • What: Two unauthenticated RCE bugs in the PHP engine itself (PHP2Shell), now chained in real attacks.
  • Why it matters: Attackers rooted 25 servers by pairing it with a Linux kernel bug on firewalls running an old PHP 8.0 build.
  • What to do: Upgrade to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, then restart every PHP worker.
  • Key stat: Both CVEs score 9.2 on CVSS v4.0, per the PHP security advisory.

PHP2Shell (CVE-2026-109618 and CVE-2026-109619) lets an unauthenticated request run code on unpatched PHP. A suspected state-backed group chained it with a Linux kernel flaw to root 25 firewall servers. The fix is upgrading to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 and restarting workers; config changes only reduce risk.

PHP2Shell is the name for two memory-safety vulnerabilities in the PHP interpreter, CVE-2026-109618 and CVE-2026-109619, disclosed on 21 October 2026. One corrupts request data when PHP runs out of memory mid-request; the other reuses request state after its memory has been freed. Both allow unauthenticated remote code execution.

The PHP2Shell postmortem matters to every PHP team because the bug sits below your framework. Laravel, Symfony, Drupal and WordPress code can be clean and the server still falls. The PHP security advisory lists PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed as in scope. The scale is large: PHP runs on 70.8% of websites whose server-side language W3Techs can detect.

The real pain point is not “should I patch”. It is the follow-up questions developers keep asking. Which PHP binary actually serves my traffic? Did my distro patch it without changing the version number? Does the mitigation I set in .user.ini even apply? This week’s public write-up of a real compromise answers those questions better than any advisory. This post walks through that incident in third person, from the published reports, and turns it into six lessons for PHP developers.

What is PHP2Shell and which PHP versions are affected?

It is a pair of critical PHP engine bugs, and every release before 8.5.12, 8.4.27, 8.3.36 and 8.2.35 is vulnerable. The PHP project shipped those fixed releases on 21 October 2026 and said there are no complete mitigations.

The two bugs work differently:

  • CVE-2026-109618 (RCE via OOM corruption): out-of-memory handling interrupts PHP while request data is half built. Cleanup code then trusts the broken data.
  • CVE-2026-109619 (RCE via stale global state): request-owned state survives after its memory is freed. The next request on the same worker can act on it.

The vulnerable upload-progress code dates back to PHP 5.4.0, released in 2012, according to Sansec. That is why end-of-life branches are affected too. watchTowr confirmed that PHP 5 is vulnerable, and PHP 7.3 and older get no fix at all.

PHP 8.1 and older receive no official release. The PHP security team did publish minimal source backports for 7.4, 8.0 and 8.1, and distribution vendors ship patched packages.

How did the PHP2Shell attack root 25 servers?

The attackers chained the PHP bugs with a Linux kernel privilege escalation and aimed it at firewall management interfaces. The Israeli security firm Dream published the findings. The Register reports that the targets were Palo Alto Networks PAN-OS 12.1 and 12.2 devices running PHP 8.0.30. Palo Alto’s Unit 42 independently verified the tooling.

The funnel from scan to root

The timeline reported by The Hacker News reads like a sales funnel:

  1. The FOFA search engine identified about 19,000 internet-facing firewalls with exposed management interfaces.
  2. A custom version scanner got answers from about 5,000 of them.
  3. Filtering to the US, Japan, Taiwan and South Korea, on branches running PHP 8.0.30, left 240 targets.
  4. An automated batch runner exploited the chain, planted a PHP web shell and logged each root.
  5. 24 servers were rooted in the batch run, and a 25th was reached through an SSH tunnel into an internal network.

The four-stage exploit

The exploit targeted PHP’s session.upload_progress mechanism. According to The Register’s account of Dream’s report, it went like this:

  1. Flood the server with crafted multipart POST requests to corrupt memory and force a predictable layout.
  2. Forge a stale session ID length to trigger an out-of-bounds read that leaks the libphp.so base address.
  3. Exhaust memory again to create a stale reference, then forge it to run commands as nobody.
  4. Escalate to root with Fragnesia, the Linux kernel flaw CVE-2026-46300, and plant a web shell.

Dream says the full chain, from an unauthenticated HTTPS request to a persistent root shell, finished in under 15 minutes. After that, the attackers moved on to Active Directory: LDAP enumeration, password spraying and attempts to create backdoor accounts.

Why did a 2026 attack succeed against PHP 8.0?

It succeeded because the vulnerable PHP was buried inside a vendor appliance, where nobody’s normal PHP patching process reaches. PHP 8.0 is long out of support. The firewall OS still shipped PHP 8.0.30 with a high memory limit, which Dream says enabled the memory-corruption stages.

This is the insight most coverage skips. Generic PoCs mostly failed. A forum post quoted in the same report complained that real targets need libphp.so offsets unique to every build. An appliance flips that problem around. Every unit of a given firmware version ships the same build, so one lab copy gives the attacker offsets for thousands of devices.

The lesson for developers: your PHP inventory is bigger than your composer.json. NAS boxes, VPN gateways, monitoring tools and admin panels run PHP too. The list of vendors that published advisories includes Citrix, F5, QNAP, Synology, Splunk and Siemens.

Is PHP2Shell being exploited in the wild?

Yes, exploitation is confirmed. CISA added both CVEs to its Known Exploited Vulnerabilities catalog on 26 October 2026 and gave federal agencies until 29 October to patch.

The timeline was compressed by a leak. Calif reported the first bug to PHP on 11 August, and Wiz Research reported on 24 August. Details leaked on social media on 20 October, two days before the planned disclosure, per watchTowr. Sansec notes that Cloudflare reported early attacks at 07:43 UTC on 21 October, before the 14:00 UTC release.

Exposure remains wide. Censys estimated that more than 32.51 million PHP services still ran an affected version.

Reliability varies by setup. Mandiant CTO Charles Carmakal said validated PoCs achieved RCE against default mod_php deployments but not PHP-FPM. Treat that as “harder”, not “safe”. The PHP project still lists FPM as affected.

How do you patch PHP2Shell correctly?

Patch by upgrading the PHP that serves web traffic, then restart every worker. Most mistakes happen in the gap between those two steps.

  1. Find the runtime that serves requests. The CLI php -v and your FPM pool are often different installs.
  2. Check the package, not just the version. Backported fixes keep the version number. Sansec gives the example of a patched package that still reports 8.1.34.
  3. Upgrade to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or install your vendor’s patched build.
  4. Restart every PHP worker. A process started before the update keeps running vulnerable code.
  5. Verify through a web request, not on the command line.

These commands check the changelog for the CVE, adapted from Sansec’s guide:

# Debian / Ubuntu (swap 8.3 for your version)
zgrep CVE-2026-109618 /usr/share/doc/php8.3-common/changelog.Debian.gz

# RHEL / AlmaLinux / Rocky
rpm -q --changelog php-common | grep CVE-2026-109618

# Then restart the workers that serve traffic
sudo systemctl restart php8.3-fpm   # or: sudo systemctl restart apache2

A maintainable PHP codebase should also document which runtime serves which app, and who owns its updates.

Which PHP2Shell mitigations work if you can’t upgrade today?

Mitigations reduce specific attack paths but do not make PHP safe. The PHP advisory is explicit about that. The biggest trap is where you set them.

Broken: setting it at runtime

<?php
// bootstrap.php — this does NOT protect you
ini_set('session.upload_progress.enabled', '0');
// PHP parses the multipart body before this line runs.

The advisory says not to rely on ini_set() or FPM .user.ini. Upload-progress handling runs during request parsing, before your bootstrap file executes.

Fixed: set it before request parsing

; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[memory_limit] = -1   ; temporary, watch RAM
php_admin_value[post_max_size] = 8M  ; 32M if you need uploads
; PHP 8.5 only:
php_admin_flag[fatal_error_backtraces] = off

Each setting targets a different path. memory_limit=-1 removes the controllable out-of-memory trigger for CVE-2026-109618, but one request can then eat all server RAM. The advisory recommends a container or cgroup limit as the availability boundary, and warns against RLIMIT_AS, which can cause the same vulnerable allocation failure.

A third bug, CVE-2026-115560, also needs session.upload_progress.enabled=0. The PHP informational advisory says it only fires if an app switches session.serialize_handler at runtime. It will be fixed in the next regular release.

Sansec also suggests blocking multipart/form-data at nginx or Apache as a stopgap, because every known trigger uses it. That breaks uploads, so test checkout and admin flows first. Retries after a 403 are a good reason to make your endpoints safe to repeat, as covered in the idempotency keys guide.

Fix status by PHP branch (sources: PHP advisory, watchTowr, Sansec)
PHP branch Fixed version Official fix? Your option
8.5.x 8.5.12 Yes Upgrade and restart workers
8.4.x 8.4.27 Yes Upgrade and restart workers
8.3.x 8.3.36 Yes Upgrade and restart workers
8.2.x 8.2.35 Yes Upgrade and restart workers
7.4, 8.0, 8.1 None from php.net Source backports only Distro/LTS vendor package, or move to 8.3+
7.3 and older None No Upgrade; isolate from the internet now

What should PHP developers learn from the PHP2Shell incident?

The lesson is that runtime ownership is part of application security. Six concrete takeaways follow from the public record:

  1. Inventory every PHP runtime, including appliances and vendor tools, not just your apps.
  2. Never expose admin interfaces to the internet. Dream described the entry point as a management interface that “should never have been exposed”.
  3. Track EOL dates. Drupal 10 still allows PHP 8.1, which gets no official fix, so the Drupal security team’s PSA points site owners at PHP itself. If you’re already planning modern Drupal work like hooks as classes in Drupal 11, move to PHP 8.3 or 8.4 at the same time.
  4. Configure at the server layer. Anything security-critical belongs in php.ini or the pool config.
  5. Restart is part of the patch. Long-lived FPM, FrankenPHP or queue workers keep the old code in memory.
  6. Assume leaks shorten your window. Here the gap between leak and first attacks was hours.

The NexGismo verdict: PHP2Shell is less “mass worm” and more “targeted weapon”, and that is exactly what makes appliances and forgotten servers the real risk. My recommendation: patch and restart this week, set the pool-level mitigations as a second layer, and spend an hour listing every box on your network that runs PHP. The servers you don’t think of as PHP servers are the ones this attack found.

  • PHP2Shell is CVE-2026-109618 and CVE-2026-109619, both rated 9.2 on CVSS v4.0 by the PHP security team.
  • Fixed releases are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, published on 21 October 2026.
  • A reported attack chained the two PHP bugs with Linux kernel flaw CVE-2026-46300 to root 25 firewall servers running PHP 8.0.30.
  • CISA added CVE-2026-109618 and CVE-2026-109619 to its Known Exploited Vulnerabilities catalog on 26 October 2026.
  • Setting session.upload_progress.enabled=0 through ini_set() or .user.ini does not protect a server; it must apply before request parsing.
  • Patched distribution packages often keep the old PHP version number, so check the package changelog for the CVE.

Frequently Asked Questions

What is PHP2Shell?

PHP2Shell is the name for CVE-2026-109618 and CVE-2026-109619, two memory-safety bugs in the PHP interpreter disclosed on 21 October 2026. One abuses out-of-memory handling mid-request and the other abuses stale request state on a reused worker. Both allow unauthenticated remote code execution and are rated 9.2 on CVSS v4.0.

Which PHP versions fix PHP2Shell?

PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix both vulnerabilities. PHP 8.1 and older get no official release, though the PHP security team published source backports for 7.4, 8.0 and 8.1. Distributions and LTS vendors ship patched packages that often keep the original version number, so check the package changelog.

Is PHP-FPM affected by PHP2Shell?

Yes. The PHP security team lists PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed as affected. It rates the risk highest for mod_php, where an attack can succeed before application code runs. Mandiant reported validated exploits against default mod_php but not PHP-FPM, which means harder, not safe.

Can I mitigate PHP2Shell without upgrading?

Only partially. PHP suggests session.upload_progress.enabled=0, memory_limit=-1, a low post_max_size and, on PHP 8.5, fatal_error_backtraces=0. These must be set in php.ini or the FPM pool, not with ini_set() or .user.ini. PHP states that no configuration change fully mitigates the vulnerabilities, so upgrading remains the fix.

Is PHP2Shell being exploited in the wild?

Yes. CISA added both CVEs to its Known Exploited Vulnerabilities catalog on 26 October 2026. Security firm Dream also documented a suspected state-backed campaign that chained these bugs with a Linux kernel flaw to root 25 servers, including Palo Alto Networks firewalls tied to a US government-affiliated organisation.

PHP2Shell will fade from the headlines, but the conditions that made this breach possible won’t. An unsupported PHP build was baked into an appliance, the admin interface was open to the internet, and nobody owned the runtime. Fixing your own servers is the easy part: upgrade, restart, verify through a real request. The harder and more valuable work is finding the PHP you didn’t know you were running. If you want a deeper look at how PHP workers hold state between requests, the PHP Fibers guide is a good next read. Got a patching gotcha I missed? Share it in the comments so other teams can avoid it.

Sources