PHP2Shell: What you need to do now — and why static sites stay calm

Two critical PHP flaws, an attack without login, websites taken offline: what is behind PHP2Shell, a step-by-step guide, a checklist and the lessons for architecture.

by ·

The first CMS I built myself was a PHP program, and my WordPress plugins still are. The newest one, JPKCom Harden & Trim, hardens WordPress against a whole range of attack paths. Against PHP2Shell it does nothing. No WordPress plugin helps here, and neither does an update of WordPress, TYPO3 or Drupal, because the attack hits PHP itself before a single line of application code runs.

The pages of this knowledge base, on the other hand, I did not have to touch. They sit as finished files on an Apache server, and no PHP starts when they are delivered. That is not superiority on principle but a consequence of the architecture I have already written about in Sustainable websites and Why 11ty. PHP2Shell shows why it is worth so much in a week like this one — and where its limit lies: it does not automatically make the server the files sit on secure. I explain why further down.

This article covers both: first what is urgent now — background, the current situation, a step-by-step guide for admins, notes for site owners and a checklist. Then the question of what you take from it for the future. If you are short on time, jump straight to the guide or the checklist.

This article reflects the state of 22 October 2026, one day after the patches. On 23 October I added an update: exploitation is harder than feared, and two of the fixed versions have a crash bug. The situation is still moving; where sources contradict each other, I say so.

The short version

What Status
Flaws CVE-2026-109618 ("RCE via OOM corruption") and CVE-2026-109619 ("RCE via stale global state"), together "PHP2Shell"
Severity CVSS 4.0: 9.2 (critical), both; Germany's BSI rates it criticality 3 / orange
Attacker needs no login, no user interaction, no upload form in your application
Affected every PHP installation serving web requests: mod_php, PHP-FPM, php-cgi, FrankenPHP, LiteSpeed; highest risk with mod_php
Fixed in PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 (21 October 2026)
PHP 8.1 and older no official release; source backports for 7.4, 8.0 and 8.1, otherwise only packages from distributions or LTS vendors
Workarounds only partly effective; the PHP team calls updating "the only safe choice"
Exploitation attack attempts observed since 17 October, public proofs of concept in circulation, according to analyses many of them only working with a second vulnerability; confirmed successful compromises had not been reported as of 23 October
Known issue 8.4.27 and 8.5.12 can crash with SimpleXML (zend_mm_heap corrupted) — no reason to downgrade, see the update

Update of 23 October

Two days after the patches, two things have changed that matter to you.

Exploitation is harder than feared. Nine security firms told NBC News (opens in a new tab) that as of the evening of 22 October (US time) they had not seen a single successful attack: CrowdStrike, ESET, the Google Threat Intelligence Group, Huntress, Palo Alto Networks, Recorded Future, Secureworks, Sophos and Trail of Bits. Palo Alto says it is examining more than 200 proofs of concept, and Sophos thinks the flaws are "harder to exploit than folks thought at first". According to Elias Taylor (opens in a new tab), many of the public proofs of concept don't work against stock PHP but require a second vulnerability through which an attacker can include local files (local file inclusion). The threat intel forum IFIN (opens in a new tab) withdrew its "exploited in the wild" label on 22 October because no direct evidence had been presented, only third-party claims.

The US agency CISA wrote in its alert (opens in a new tab) of 21 October that it had not observed exploitation. In version 1.2 of its warning (opens in a new tab) (in German) of 22 October, Germany's BSI knows of no confirmed compromised systems in regulated industry or the federal administration. It considers reports that the patches can be bypassed refuted: patching the two flaws protects against their exploitation. watchTowr (opens in a new tab), on the other hand, speaks of exploitation of both CVEs in its honeypots but does not say whether it succeeded.

To me, this is still not an all-clear. For Apache with mod_php, the PHP team itself points to a demonstrated exploit without login. VulnCheck (opens in a new tab) says it has had a working exploit for CVE-2026-109618 since 22 October that achieves code execution on x86-64 with ASLR enabled and display_errors=off — a typical production configuration — tested against PHP 8.4.10 to 8.4.26. VulnCheck has not published it. And since the morning of 22 October, the security firm ELLIO (opens in a new tab) has seen attack requests carrying working malware, including web shells and DDoS bots; whether any of them succeeded is unknown. Exploits get better over time, not worse. Patching remains mandatory; there is just less reason to panic.

The fixed versions 8.4.27 and 8.5.12 have a crash bug. An ordinary bug fix from the same release, not the security fix, can make the garbage collector free the property table of SimpleXML objects twice (php-src#24488 (opens in a new tab)). The result is crashing workers with the message zend_mm_heap corrupted. It is triggered by code that reads a SimpleXML object's properties as an array — for example via json_encode(), get_object_vars() or (array) — while the object hangs off a reference cycle that the garbage collector cleans up. According to the Docker issue (opens in a new tab), 8.3.36 and 8.2.35 are not affected, but the official images with 8.4.27 and 8.5.12 are. The fix has been in the source code of both branches since 22 October; there was no new release as of 23 October.

Whether your PHP is affected depends on where it comes from. Debian 13 ships the complete 8.4.27 as 8.4.27-1~deb13u1 and therefore carries the bug; according to a user in the issue, the same goes for the packages from deb.sury.org. Ubuntu's security packages for 8.3.6 and 8.5.4, by contrast, only backport the security fix according to their changelogs and are not affected. When in doubt, check your package's changelog.

If you see crashes like these: do not go back to 8.4.26, 8.5.11 or another older version, as the person who reported the issue did for their CI tests — on a production system that reopens PHP2Shell. As a stopgap, one user in the issue successfully tested zend.enable_gc = 0. It switches off reference cycle collection, so the memory use of long-running workers can grow. Keep an eye on it and install the next release as soon as it comes out.

I have also added to step 1 devices and control panels for which vendors or the BSI have since published notices, and updated the state of Remi and deb.sury.org.

What PHP2Shell is

Behind the name are two CVEs that, according to Wiz Research (opens in a new tab), actually bundle four bugs. They share a root: how PHP deals with fatal errors in the middle of processing a request, above all the "out of memory" error.

CVE-2026-109618 concerns the handling of memory exhaustion. On an out-of-memory error, PHP jumps out of the current processing via longjmp(). If that happens while internal request data is only half initialised, the clean-up at the end of the request treats the broken data as valid. The GitHub advisory (opens in a new tab) describes several ways to drive a request into this state deliberately, all within PHP's apparent limits: missing or unusable length information, an unlimited number of headers per part of a multipart/form-data request, empty file fields that don't count towards max_file_uploads, and nested field names that multiply the array work.

CVE-2026-109619 concerns state that survives a request. A PHP worker handles many requests in a row. After a fatal error, internal state can be left behind whose memory has long been freed, and a later request to the same worker uses it before normal initialisation resets it — a classic use-after-free. According to the advisory (opens in a new tab), two paths are confirmed: diagnostics from INI settings raised before initialisation, and session upload progress. On PHP 8.5 there is also the fatal_error_backtraces path, according to the advisory the most strongly demonstrated case.

What makes it dangerous is the timing. PHP parses a multipart/form-data request before any script starts. Your application does not need an upload form; a single crafted request to any PHP file can be enough. With Apache and mod_php, according to the advisory, the clean-up runs even when request startup fails. The PHP team (opens in a new tab) rates the risk highest there and points to a demonstrated exploit in which an unauthenticated request triggers the attack "before any application code runs". With PHP-FPM, code execution is confirmed via the session path of CVE-2026-109619, depending on a suitable operation in the application; for CVE-2026-109618, according to the advisory, no complete FPM exploit has been demonstrated yet. In its write-up (opens in a new tab), Calif shows a video against nginx with PHP-FPM.

How far back it reaches is remarkable: according to Wiz, every PHP version released in the last decade is affected by at least one of the bugs, back to PHP 5.4. The name is deliberate — according to the BSI (opens in a new tab) (in German), it is meant to recall Log4Shell.

And why does it hit so many? According to W3Techs (opens in a new tab), PHP runs on about 69 % of all websites whose server-side language is known. The BSI names WordPress, Drupal, TYPO3, Joomla, Magento, Nextcloud, pfSense and OPNsense as examples. Calif lists confirmed unauthenticated code execution against, among others, WordPress, Drupal, Joomla, MediaWiki, Roundcube, Magento, phpMyAdmin, Nextcloud, Laravel and Symfony.

Found by AI — and almost found once before

The flaws were found with AI assistance, by several teams independently of each other. Calif was part of OpenAI's "Patch the Planet" initiative; according to its own write-up, it found several independent paths to code execution on 1 August and reported the first bug to PHP on 11 August. According to the PHP Foundation (opens in a new tab), OpenAI independently reported the same on the same day. Wiz found the first bug on 19 August with Gemini 4 Argon and GPT-5.6 Sol, confirmed code execution against Drupal and WordPress within two days and reported on 24 August.

The genuinely uncomfortable part of the story is in the PHP Foundation's post: a core developer had found the first signs of the memory problem back in 2024, while fixing a finding from an external audit by Quarkslab. An attack path was only theoretical at the time, and for months the security team could not decide between competing fixes. The Foundation itself calls this "a structural failure" and announces that decisions can now be made by majority and that complex fixes will be prioritised. I find it remarkable that it writes this down openly — and it is a reminder that security-critical infrastructure is often carried by a few volunteers. The Foundation explicitly asks companies, hosting providers, distributions and governments for sustained funding.

A disclosure that fell out of step

A coordinated release was planned for Thursday, 22 October. It turned out differently. Times in UTC, from the accounts of the PHP Foundation, The Register (opens in a new tab), Cloudflare (opens in a new tab) and Amazon (opens in a new tab):

Time (UTC) What happened
17 October Amazon's MadPot honeypot network records a first attack attempt, four days before disclosure
20 October, ~15:00 The two CVE IDs circulate in forums and on social media, with a wrong time and a wrong score (10.0)
20 October, 19:30 PHP publishes the advance notice; the release moves to Wednesday, 14:00 UTC
20 October, ~20:00 According to PHP, Calif mistakenly publishes a video of its exploit
21 October, 03:02 Cloudflare switches on WAF rules, including on the free plan
21 October, 07:41 Chainguard publishes built images containing the fix — more than six hours before the release
21 October, 14:00 Coordinated release: PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35

About Chainguard, the PHP Foundation writes that the company "unilaterally" broke the embargo; anyone could then study the fix in binary form while everyone else was still waiting for the release. Speaking to The Register, Chainguard CEO Dan Lorenc does not dispute the timeline but says there was no leak: coordinated disclosure only applies as long as there is no evidence of active exploitation. I can't judge from the outside who is right. What counts for you as an admin is the consequence: 23 hours passed between the CVE IDs first circulating and the patch, and according to heise (opens in a new tab) (in German), the early release put the maintainers in a tight spot. Debian shipped after 21:00 German time, Fedora, Gentoo, Ubuntu and Red Hat also still on release day; Slackware and SUSE followed later. According to PHP, the widely used third-party repository rpms.remirepo.net had no updates at all on release day; they arrived one day later (opens in a new tab). According to DDEV (opens in a new tab), deb.sury.org lacked the ARM64 packages — in the Debian repository still for PHP 8.1 to 8.4 on 23 October, while the Ubuntu PPA ppa:ondrej/php has had them since release day.

Amazon itself is cautious about the early attempt on 17 October: how the attacker got the information is unclear; heise also considers it possible that it was an exploit attempt against a different upload vulnerability.

The consequences: sites offline, attacks under way

In its warning (opens in a new tab) (in German), the BSI recommended that anyone unable to patch should consider temporarily disconnecting from the internet, since none of the workarounds offers complete protection. Many did exactly that. According to heise (opens in a new tab) (in German), Berlin's Charité hospital took its websites completely offline, parts of berlin.de were switched off, the Berlin police's online reporting service was unreachable, as were authority and police sites in Bavaria and Lower Saxony; Freie Universität Berlin restricted its IT services. According to heise, Reuters names Denmark, Finland, France and the Netherlands, where government sites went offline or into maintenance mode.

On exploitation, the statements diverge, which, frankly, is normal on the day after. Amazon now speaks of broad exploitation attempts "in the wild", Cloudflare, according to heise citing Reuters, has seen millions of attempts, and according to WinFuture (opens in a new tab) (in German, without naming its own source), exploit examples appeared in relevant forums hours before the official announcement. The PHP Foundation, on the other hand, writes that it did not identify any functional exploits at the time of release, and according to Amazon it is unclear whether the observed attempts succeeded against real systems. For your decision, none of this changes anything: public proofs of concept exist — although many only work together with a second vulnerability, see the update —, the patches can be analysed, and the BSI expects exploitation in the short term.

A word on sources: in the first hours, analyses circulated that contradicted themselves or simply listed wrong version numbers as "fixed". For versions, rely on the php.net advisory (opens in a new tab) and your distribution's security notices, not on forum posts.

Step by step: closing PHP2Shell

This guide is for everyone who runs PHP themselves: your own server, a VPS, containers, Docker hosts. If you are on shared or managed hosting, you will find the shorter version further down.

1. Inventory: where does PHP run?

This is the step most often cut short. Don't just think of the website:

  • Web servers and VPSs, including old ones "that only do a redirect".
  • Several PHP versions on one server: many setups with deb.sury.org or control panels run 8.1, 8.2, 8.3 and 8.4 side by side, each with its own FPM service.
  • Containers: every image with PHP is a patch case of its own.
  • Admin tools: phpMyAdmin, Roundcube, control panels such as ISPConfig or Plesk, monitoring interfaces.
  • Devices: router and firewall interfaces (pfSense, OPNsense), NAS systems, a Raspberry Pi as a mini server — heise explicitly advises checking there too. Here you need an update from the vendor, sometimes firmware, sometimes a package.
  • Vendor notices for devices (as of 23 October): in its advisory (opens in a new tab), Palo Alto lists the PAN-OS management interface, including the XML API, as affected, plus Panorama and the WildFire WF-500 and WF-500-B appliances in all versions. Hotfixes for PAN-OS are announced for 26 to 30 October, with no date yet for Panorama and WF-500; until then, Palo Alto advises, among other things, making management reachable only from trusted internal IP addresses. Netgate has published updated PHP packages for pfSense, and Synology has issued package updates with SA-26-14 (opens in a new tab). QNAP lists its devices as affected in QSA-26-18 (opens in a new tab) but has no fixed versions yet. In its updated warning (opens in a new tab) (in German), the BSI also lists F5 products and Citrix NetScaler as at least partly affected.
  • Control panels with their own PHP: in Plesk, the panel itself runs on its own PHP engine (sw-engine), which updating the websites' PHP versions does not cover. According to Plesk (opens in a new tab), it is only fixed from Plesk Obsidian 18.0.81 Update 4 or 18.0.80 Update 11 onwards; plesk version shows which version you have. The BSI also lists cPanel as affected.
  • Development environments that are publicly reachable (more on that below).

On a Debian or Ubuntu server, these commands give you a quick overview:

Terminal
# Installed PHP packages (all versions, incl. mod_php)
dpkg -l 'php*' 'libapache2-mod-php*' | awk '/^ii/ {print $2, $3}'

# FPM services (active and inactive) and Apache with mod_php
systemctl list-units --type=service --all 'php*-fpm*'
ls /etc/apache2/mods-enabled/ | grep -i php

# Containers and the images they run on
docker ps --format '{{.Names}}\t{{.Image}}'

2. Check the version properly — php -v is not always enough

This is the biggest trap. Distributions often patch security fixes into their own version without changing the PHP version number. Ubuntu 24.04 LTS, for example, ships PHP 8.3.6, and according to the Ubuntu security page (opens in a new tab) the package 8.3.6-0ubuntu0.24.04.13 is fixed. php -v keeps showing "8.3.6" there — patched, even though the number is below 8.3.36. Conversely, a number above the threshold proves nothing if the service has not been restarted.

So check the package version against your distribution's notice:

Distribution Package Fixed version
Debian 13 "trixie" php8.4 8.4.27-1~deb13u1 (DSA-6574-1 (opens in a new tab))
Debian 12 "bookworm" php8.2 8.2.35-1~deb12u1 (DLA-4848-1)
Ubuntu 26.04 LTS php8.5 8.5.4-0ubuntu1.5
Ubuntu 24.04 LTS php8.3 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS php8.1 8.1.2-1ubuntu2.28
Ubuntu 20.04 / 18.04 php7.4 / php7.2 only with Ubuntu Pro

For other systems (Red Hat, SUSE, Alpine, Gentoo) the same principle applies: find the vendor's security notice for CVE-2026-109618 and compare the package version. If you compile PHP yourself, the upstream versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35 apply.

3. Update — and restart everything

The update itself is usually unspectacular:

sudo apt update && sudo apt upgrade

If you want to update only PHP, name mod_php explicitly, because its package is not called php…: sudo apt install --only-upgrade 'php*' 'libapache2-mod-php*'. You can ignore the "not installed" messages for packages you don't have.

The important part comes afterwards. A running process keeps using the old code in memory until it restarts. So restart every service that loads PHP:

# Every installed FPM version, not just the default one
sudo systemctl restart 'php*-fpm.service'

# Apache with mod_php
sudo systemctl restart apache2

Add any long-running workers you run yourself: FrankenPHP, queue workers, Supervisor processes. Then check that every running process really has restarted. php -v or php-fpm8.3 -v don't help here, because they start the binary from disk — and that is already new after the update, even while the service is still running the old code in memory. What tells you something is the start time of the service, or a tool like needrestart:

# "Active: ... since" must be later than the update
systemctl status 'php*-fpm.service' apache2 | grep -E '●|Active:'

# Lists processes still running old library or binary code
sudo needrestart -r l

4. Rebuild containers, don't just restart them

A docker restart starts the old image. You need to pull the base image again and rebuild, without cache, so that the package layer really is fresh:

docker build --pull --no-cache -t my-app .
docker run --rm my-app php -v

Check which version is actually inside the finished image before you roll it out. For images that install PHP from distribution packages, the package version from step 2 applies again.

DDEV users: according to the DDEV blog (opens in a new tab), local development is not affected, because the project only listens on your own machine. It becomes dangerous with ddev share or if you use DDEV as public hosting. For PHP 8.2 to 8.5, the post describes a small Dockerfile under .ddev/web-build/ that updates the packages; for 5.6 to 8.1, ddev restart --no-cache is enough — afterwards ddev php -v must show a build date of 21 October or later. On ARM64, including Apple Silicon Macs, you have to wait for the packages from deb.sury.org or for DDEV 1.25.5. As of 23 October, neither is available yet.

5. Old PHP versions: backport or switch off

PHP 8.1 and older no longer get releases from the PHP project. You have three options:

  • Your distribution's packages: Debian LTS, Ubuntu (22.04 directly, 20.04 and older via Ubuntu Pro) and commercial LTS vendors ship backports. The TYPO3 team (opens in a new tab) generally recommends PHP 8.2 or newer and points only ELTS customers on TYPO3 v10 with PHP 7.4 to commercial PHP updates, for example via Ubuntu Pro.
  • Source backports: the PHP security team provides minimal patches for 7.4, 8.0 and 8.1, linked in the advisory. In addition, the GitHub account shivammathur, which is also behind the well-known GitHub Action setup-php, maintains php-src-backports (opens in a new tab), a fork that carries security fixes back into unmaintained branches (in its README's words, "Security backports for EOL versions of PHP"). Check for yourself before using it whether the PHP2Shell fixes for your branch are included.
  • Switch off: if neither works, the recommendation is clear. The BSI advises considering a temporary disconnection from the internet.

Building from patches yourself only makes sense if you compile PHP anyway. And either way: a backport is a bridge, not a home. Plan the upgrade to a supported version.

6. If you cannot patch right away: emergency brakes

The PHP team lists measures that block individual known attack paths and stresses that they do not make an installation safe. They are meant for the hours until the update is in place:

ini
; php.ini or FPM pool — must apply before request parsing, then restart every worker
memory_limit = -1                    ; CVE-2026-109618: no limit to push against
post_max_size = 8M                   ; 32M if you need uploads
upload_max_filesize = 6M             ; must stay below post_max_size
session.upload_progress.enabled = 0  ; CVE-2026-109619: confirmed session path
fatal_error_backtraces = 0           ; PHP 8.5 only

A few details decide whether this takes effect at all:

  • memory_limit = -1 must be set so that no application can override it, i.e. via php_admin_value[memory_limit] = -1 in the FPM pool or via php_admin_value memory_limit -1 in the Apache configuration with mod_php. Check that no pool, directory or application setting puts a finite limit back. Even then, a genuine allocation failure can still reach the vulnerable path. Do not replace the limit with RLIMIT_AS or RLIMIT_DATA — according to PHP, they can trigger exactly the vulnerable failure. A container or cgroup limit works as protection for availability.
  • session.upload_progress.enabled = 0 only helps via php.ini, the FPM pool or the server configuration, not via ini_set() or a .user.ini.
  • Hiding diagnostics (display_errors, error_reporting, @) does not help. Invalid or deprecated INI settings that emit warnings before initialisation have to go.
  • If your application needs no uploads at all, you can block multipart/form-data at the web server or proxy. ISPConfig offers its own helper script (opens in a new tab) that rejects multipart requests before they reach PHP — with the note that all uploads on the affected sites then stop working.
  • A WAF such as Cloudflare's blocks known attack patterns, but only for traffic that actually passes through it. Anyone who reaches your server directly via its IP address bypasses it.

When you roll back after patching: set memory_limit to a finite value again. According to the advisory, you may leave session.upload_progress.enabled = 0 in place if you don't need the feature — most applications don't.

7. Check whether someone already got in

Because the first attempts predate the patch, Amazon recommends starting investigations from 17 October. Concrete traces named by Wiz, Amazon and PHP:

  • POST requests with multipart/form-data containing PHP_SESSION_UPLOAD_PROGRESS.
  • Multipart requests followed by memory-limit errors or bursts of HTTP 500 responses.
  • Unusually large or malformed PHPSESSID cookies in responses — according to Wiz, one of the bugs leaks memory contents through exactly this cookie.
  • PHP workers crashing or restarting for no apparent reason.
  • Unexpected processes started by the web server user, and new or modified files in the web root.
Terminal
# Request bodies are not in the access log, but a burst of 500s
# after POSTs is a start (zgrep also reads rotated .gz logs)
sudo zgrep -E '"POST [^"]*" 500 ' /var/log/apache2/*access.log* | tail -50

# With FPM, PHP fatals end up in the web server's error log
sudo zgrep -i 'Allowed memory size' /var/log/apache2/*error.log* /var/log/nginx/error.log*

# Crashed FPM workers
sudo zgrep -E 'exited on signal|SIGSEGV' /var/log/php*-fpm.log*

# Files whose inode changed since 17 October (ctime, unlike mtime,
# cannot be reset with touch)
sudo find /var/www -newerct '2026-10-17' -type f \
  \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' \
     -o -name '.htaccess' -o -name '.user.ini' \) | head -50

Paths and log files depend on your setup; sites with their own per-vhost logs need to be checked one by one. Core and plugin updates since 17 October also show up in the file list — compare it against your own changes.

The traces play out below the application, so your application logs may show little. If you find nothing, that is no proof — the advisory for CVE-2026-109619 says explicitly that a missing log entry does not prove safety, because the failure can happen before PHP writes the message. And one more point from Amazon's list: the published IP addresses belong to shared infrastructure such as anonymisation services. Investigate traffic from them, but don't block them wholesale.

If there was a successful break-in, patching is not enough. Then it's the full programme: rebuild the system and rotate credentials — database password, API keys, everything that was in the PHP process's memory, because that is exactly what one of the bugs can read.

If you are hosted

If your website runs at a hosting provider and you have no root access, the list is shorter:

  1. Look up the PHP version. In your hosting provider's control panel, in WordPress under Tools → Site Health → Info → Server, in TYPO3 in the toolbar under System Information.
  2. Ask your hosting provider whether the fixed version is installed — especially if you are on an older version such as 8.1 or 7.4. Ask specifically instead of assuming it has already happened.
  3. Switch to a supported version yourself if your plan lets you choose and you are still on 8.1 or older. Test first: old themes and plugins don't cope with every version.
  4. No false sense of security from application updates. WordPress, TYPO3 or plugin updates do not close PHP2Shell. TYPO3 says so explicitly in its security announcement (opens in a new tab): "TYPO3 itself is not affected and does not need to be updated." You should still keep your application updated, just for other reasons.

Checklist to tick off

The checklist is in the Markdown block below. Copy it with the button in the bar, for example into a ticket, a wiki or a GitHub issue — the boxes turn into real checkboxes there.

php2shell-checklist.md
# PHP2Shell checklist (CVE-2026-109618, CVE-2026-109619)

Source: https://www.jpkc.com/db/en/blog/php2shell/ — as of 23 October 2026

## Right away

- [ ] All systems with PHP listed — servers, containers, admin tools, devices, publicly reachable development environments
- [ ] Package version checked per system against the distribution's notice, not just `php -v`
- [ ] Update installed
- [ ] Every PHP service restarted: all FPM versions, Apache with mod_php, long-running workers
- [ ] Start time of every service checked after the update (`systemctl status`, `needrestart`)
- [ ] Container images rebuilt with `--pull --no-cache` and rolled out
- [ ] Systems without a patch (PHP ≤ 8.1 without a backport, old device firmware) taken offline or put behind access protection

## Until the patch is in place

- [ ] Emergency brakes from `php.ini` set, via pool or server configuration, workers restarted
- [ ] Multipart blocked at the proxy where no uploads are needed
- [ ] WAF rules active (on Cloudflare's paid plans: Managed Ruleset in "Block" mode)

## Afterwards

- [ ] On 8.4.27 or 8.5.12: watched for SimpleXML crashes (`zend_mm_heap corrupted`), no downgrade, next release scheduled
- [ ] Logs reviewed from 17 October onwards
- [ ] Web root checked for new or modified files
- [ ] `memory_limit` back to a finite value
- [ ] Upgrade scheduled for everything below PHP 8.3 (8.2 ends on 31 December 2026)

Why static sites stay calm

Now for the second part, and back to this knowledge base. For its files there was nothing to do on 21 October. The reason is mundane: whoever requests https://www.jpkc.com/db/en/blog/ gets a finished HTML file. Apache reads it and sends it off. There is no PHP process that would parse a multipart/form-data request, no worker in which state lingers from one request to the next, no memory limit an attacker could deliberately blow. The entire attack class of PHP2Shell simply has nothing to grab onto.

It is the same property I described in Why 11ty: what isn't running can't be attacked. It works not only against flaws in plugins or in the CMS, but also against flaws one layer deeper, in the runtime itself — where no hardening plugin can reach.

I have to stay honest on two points:

  • Static means: this URL starts no PHP. If PHP still runs anywhere on the same server — a contact form, phpMyAdmin, a control panel, a webmail client — the server is vulnerable anyway, and a break-in there can also modify your static files. So the question is not "Is my website static?" but "Where on this server is PHP publicly reachable?".
  • The work doesn't disappear, it moves. My build tools with Node.js and npm have their own risks, above all in the supply chain; how I secure them is in Working securely with Node.js and npm. The difference is that these tools only run for a few minutes during a build and don't sit publicly waiting for requests around the clock.

Keep WordPress, but no longer public

You don't have to give up your CMS to benefit. In Sustainable websites I presented static export: plugins such as Simply Static (opens in a new tab) or Staatic (opens in a new tab) turn WordPress into a finished copy that visitors get. The editors keep working in the familiar back end, but the back end itself is no longer public.

PHP2Shell shows what matters here: where the access protection sits. The WordPress login doesn't protect you here, because it is itself a PHP script, and the attack happens before it. What protects you is a barrier the web server checks before it hands the request to PHP:

  • Basic Auth at web server level (Apache AuthType Basic, nginx auth_basic). Apache and nginx check it in a phase before handing over to PHP; a request without valid credentials gets a 401 and never reaches PHP. Simply Static describes (opens in a new tab) exactly this as protection.
  • IP allowlisting for the editors (Require ip in Apache, allow/deny in nginx). Behind a proxy or CDN, however, the web server sees the proxy's address unless you restore the real client IP (in Apache with mod_remoteip).
  • VPN or internal network: the WordPress behind it is not reachable from the internet at all.
  • Local or on demand: the CMS only runs while editing, for example in DDEV, and then uploads the static copy.

Whether the barrier really applies before PHP depends on details. If the 401 error page is itself a PHP script (ErrorDocument 401 or error_page 401), PHP runs for requests without credentials too. Satisfy Any can override the barrier, and a <Limit GET> only protects reads — the attack comes via POST. Apache silently ignores an .htaccess under AllowOverride None. And in nginx, an auth_basic in location /wp-admin/ does not apply to .php files if a regex location such as location ~ \.php$ wins. The safest approach is to protect the whole host, not individual paths, and to test it from outside: a curl -X POST without credentials must get a 401.

That doesn't make patching unnecessary — credentials can get lost, and editors' machines are targets too. But it takes the time pressure off. With PHP2Shell, 23 hours passed between the CVE IDs first circulating and the patch. A publicly reachable WordPress was exposed during that time; one behind Basic Auth or in a VPN was not exposed in the same way.

The static copy as an emergency plan

One thought I can't shake this week: many sites that followed the BSI's "take it offline" scenario were then completely gone. For a hospital, an authority or a police website, that means opening hours, emergency numbers, contact options — unreachable until patched.

Yet most of this content is pure information that doesn't change with every request. A static copy, generated regularly and kept ready as an emergency edition, could have kept serving exactly this content while PHP was switched off. Forms, search and logins would have been gone for a few hours, the information would not. That is no substitute for being able to patch, but it is the difference between "limited" and "offline". If you run a large CMS, ask yourself whether such a plan B exists — and who switches over to it when it counts.

Trade-offs for the future

PHP2Shell won't be the last flaw of this kind. A few things I take from it, and which you should check for your projects:

Patch time in hours, not days. Amazon recommends measuring patch and mitigation timelines for critical vulnerabilities in internet-facing systems "in hours"; a 7-day or 30-day SLA assumes a delay that no longer exists. In practice that means: who on your side can update PHP on all systems within two hours on a Wednesday afternoon? Is there a list of all systems? Is cover arranged?

AI speeds up both sides. AI-assisted teams found these flaws, and the WordPress vulnerability chain "wp2shell" in July 2026 was, according to Wiz (opens in a new tab), also discovered with an AI model. Since 1 July, the PHP Foundation has published 15 CVEs and weeded out more than 100 reports as invalid, duplicate or not reproducible. In its warning, the BSI writes that attackers with AI-assisted tools can analyse patches and generate exploits within a short time. So expect more security releases and shorter gaps between patch and attack.

Supported versions are not optional. On PHP 8.1 or older, you depended on third-party backports this week. PHP 8.2 (opens in a new tab) only gets security updates until 31 December 2026. If you are still on 8.2, plan the upgrade for November, not January.

Where your PHP comes from decides how fast you get fixes. Distributions received the patches in advance, and most shipped on release day. Third-party repositories that offer newer PHP versions than your distribution are convenient — but Remi only shipped one day after the release, and two days later sury.org's Debian repository still lacked most of the fixed versions for ARM64. The PHP team explicitly recommends Remi users switch to distribution packages or build from source. That is a genuine trade-off between the newest version and the fastest security fix.

Run hosting yourself or hand it over. If you don't run PHP yourself, you don't have to patch it yourself at 16:00 German time (14:00 UTC) when a flaw like this hits. Sites behind a WAF such as Cloudflare's had protection rules hours before the release, and distributions and LTS vendors received the patch in advance. Running it yourself pays off if you can organise the on-call readiness — not if nobody takes it on.

Keep the attack surface small on purpose. Does phpMyAdmin really belong on the public internet? The control panel? The test installation from 2019? PHP2Shell only needs some reachable PHP script. Every one that doesn't need to be public belongs behind Basic Auth, an IP allowlist or a VPN.

Separate delivery from editing. Where the content fits — company sites, blogs, documentation, club and authority sites that are mostly information — static delivery is the most robust answer: either directly with a generator like 11ty or as an export from the existing CMS. Where it doesn't fit, such as shops, customer portals or communities, defence in depth remains: patch quickly, limit workers (pm.max_requests), memory limits via cgroups, AppArmor or SELinux, a WAF in front. And often a split there too: the pages everyone sees static, only the personalised part dynamic.

FAQ

Is my static website affected by PHP2Shell?

The static pages themselves are not, because no PHP runs when they are delivered. The server is affected, though, as soon as PHP is publicly reachable anywhere else on it — a form script, phpMyAdmin, a control panel. So check the whole server, not just the website.

Do I need to update WordPress, TYPO3 or my plugins?

Not because of PHP2Shell. The flaw is in PHP, and only a PHP update closes it. TYPO3 and Cloudflare say so explicitly. You should still keep your application up to date, but that's a different topic.

Is a web application firewall enough?

No. It blocks known attack patterns and buys you time, but only for traffic that passes through it, and attackers deliberately modify their requests to get around rules — Cloudflare describes this itself. A WAF complements the update; it doesn't replace it.

Is my local development environment at risk?

Not as long as it only listens on your own machine. As soon as you share it publicly, for example with ddev share or a tunnel, it counts as an exposed server and needs the update first.

Are there still fixes for PHP 7.4 or 8.1?

From the PHP project there are only source patches for 7.4, 8.0 and 8.1. Distributions and LTS vendors ship ready-made packages, for example Ubuntu for 22.04 and, via Ubuntu Pro, for older releases. If you have none of these sources, take the system offline and plan the upgrade.

Does session.upload_progress.enabled = 0 help on its own?

No. It blocks the confirmed paths via upload progress, not other variants, and it does nothing against CVE-2026-109618. ISPConfig also writes that the setting alone is not sufficient. It is one emergency brake among several, not the solution.

My PHP workers have been crashing since the update. Should I go back to the old version?

No. On 8.4.27 and 8.5.12, a bug in the interplay of SimpleXML and the garbage collector can lead to zend_mm_heap corrupted. With the old version, however, PHP2Shell would be open again. Until a corrected release comes out, zend.enable_gc = 0 works as a stopgap; details are in the update.

Conclusion

PHP2Shell is one of those flaws that define a whole week: two bugs deep in the runtime, an attack without login and without a form, a disclosure that fell out of step, and websites of hospitals and authorities taken offline as a precaution. The urgent task is clear and can be ticked off: find all PHP systems, check the package version properly, update, restart every service, rebuild containers, review logs from 17 October.

The longer-term lesson is a question of architecture. Every publicly reachable PHP installation is a place where you have to act within hours when the next flaw of this kind comes. The fewer of them there are — because pages are delivered statically, because the CMS works behind a barrier, because admin tools aren't on the open internet — the calmer the next week like this one will be. For the files of this knowledge base there was nothing to patch. That wasn't luck, it was the architecture.

Further reading

Why I switched from WordPress to a static generator in 2020 and what that saves in maintenance is in Why 11ty. Static export for WordPress and lean web servers for finished files are covered in Sustainable websites. How I secure the build side against supply-chain attacks is shown in Working securely with Node.js and npm. And what hardening WordPress can take independently of PHP is in the guide to JPKCom Harden & Trim.

Primary sources: PHP advisory (opens in a new tab) · GHSA-w8hw-w9fx-j79w (opens in a new tab) · GHSA-6wmf-4rv5-p3rp (opens in a new tab) · BSI security notice (opens in a new tab) (in German) · PHP Foundation (opens in a new tab) · CISA alert (opens in a new tab) · NCSC-NL: affected products (opens in a new tab)