Skip to content
coderband

PHP2Shell (CVE-2026-109618/109619): What to Patch and How to Check

PHP2Shell lets attackers run code on almost any PHP web server without logging in. Upgrade to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, restart PHP, then verify.

coderband engineering13 min read

PHP2Shell is a pair of critical bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, that can allow unauthenticated remote code execution on any server where PHP handles web requests, whatever runs on top of it: WordPress, Laravel, Symfony, Magento or a 15-year-old custom app. The fix is to upgrade to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, all released on October 21, 2026, or to your distribution’s patched package, and then restart PHP. If you’re on PHP 8.1 or older, the PHP project won’t publish a release for your branch, so you need a vendor backport or an upgrade.

Key takeaways

  • Patch the runtime, not the app. Updating plugins, themes or Composer packages doesn’t fix this. Cloudflare says so directly.
  • Apache mod_php is the most exposed setup. PHP says it has a demonstrated exploit against mod_php that fires before any application code runs. PHP-FPM, php-cgi, FrankenPHP and LiteSpeed are affected too.
  • Check the PHP that serves web traffic. php -v reports the command-line binary, which can be a different package or a different version from the one serving requests.
  • Distro version numbers won’t match upstream. Ubuntu 24.04’s fixed package still reports PHP 8.3.6 (USN-8948-1). Compare package versions against your distro’s advisory.
  • Restart after upgrading. Running PHP-FPM workers, Apache, queue workers and Octane keep the old code in memory until they restart.
  • The config workarounds are partial. PHP lists several, and says plainly that they do not make an installation safe.
  • On PHP 8.2, you have ten weeks left anyway. Security support for 8.2 ends December 31, 2026.

What we know as of October 22, 2026

This is fast-moving. What follows reflects sources we checked up to about 21:30 UTC on October 21, and we’ll update this post as things change.

Confirmed by PHP. The fixed versions, the two CVEs, and the affected setups are in PHP’s own security advisory and the GitHub advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. PHP rates both bugs 9.2 under CVSS v4.0. The confirmed impact is code execution as the web worker account, and for CVE-2026-109619 also memory disclosure.

How it went public. Wiz says it reported the first bug to PHP on August 24. Disclosure was planned for October 22 at 14:00 UTC, but a public leak moved it forward a day, according to Canonical. PHP posted an advance notice on October 20, and Huntress has a timeline of the leak.

Exploitation: sources disagree.

Severity scores differ. PHP scores both bugs 9.2 (CVSS v4.0). NVD scores them 9.8 under CVSS v3.1. Red Hat rates them Important, 8.1, because it judges reliable exploitation to need precise heap manipulation.

Our read: treat this as actively exploited and patch today. A wrong guess costs you an evening. The other way round, it costs you a server.

Fixed versions by PHP branch

Your branch Fixed release PHP support status What to do
8.5 8.5.12 Active support until Dec 31, 2027 Upgrade to 8.5.12
8.4 8.4.27 Active support until Dec 31, 2026 Upgrade to 8.4.27
8.3 8.3.36 Security fixes only, until Dec 31, 2027 Upgrade to 8.3.36
8.2 8.2.35 Security fixes only, ends Dec 31, 2026 Upgrade to 8.2.35 now, and plan a move to 8.4 or 8.5
8.1, 8.0, 7.4 No official release End of life Use a distro or vendor backport (see below), PHP’s minimal source backports if you build from source, or upgrade
7.3 and older No fix from PHP End of life Upgrade, buy extended support, or take it off the internet

Support dates come from php.net’s supported versions page. How far back the bugs go isn’t settled: Wiz and Google Cloud say PHP 5.4 and later are affected, and cPanel lists PHP 5.0 and later. Either way, if your PHP is older than the versions in this table, assume it’s vulnerable.

How to check whether you’re affected

On a server you manage

Start by finding out which PHP binary actually answers web requests. The command-line php and the PHP-FPM or Apache module are separate packages. On Debian and Ubuntu you can easily have several versions installed side by side, and Laravel Forge’s guidance is to update every installed version, including ones no site uses.

# Command-line PHP. Not necessarily what serves your site.
php -v

# Which PHP processes are actually running?
ps aux | grep -E 'php-fpm|php-cgi|lsphp|frankenphp|apache2|httpd' | grep -v grep

# PHP-FPM version (the binary name includes the version on Debian/Ubuntu)
php-fpm8.3 -v      # Debian/Ubuntu, adjust to your version
php-fpm -v         # RHEL, Alma, Rocky, official Docker images

# Is Apache running PHP in-process (mod_php)? Any php*_module line means yes.
apache2ctl -M 2>/dev/null | grep -i php    # Debian/Ubuntu
httpd -M 2>/dev/null | grep -i php         # RHEL family

# All installed PHP versions on Debian/Ubuntu
ls /etc/php

If you’re not sure which binary serves a particular site, ask PHP itself through the web server. Use a random file name, and delete the file as soon as you’ve read it. Don’t leave a full phpinfo() page up, because it shows paths, modules and configuration to anyone who finds it.

cd /var/www/your-site/public
f="v-$RANDOM$RANDOM.php"
echo '<?php echo PHP_VERSION, " ", PHP_SAPI;' > "$f"
curl -s "https://your-site.example/$f"   # prints e.g. "8.3.36 fpm-fcgi"
rm "$f"

php -i gives you the full configuration, but only for the command-line binary, so it can’t tell you what the web server is running.

Distro packages: compare package versions, not “PHP 8.x.y”

Distributions backport security fixes into their own builds and keep the original version number. So php -v can show an “old” version that’s actually patched, and the only reliable check is the package version against the distro’s advisory.

  • Ubuntu (USN-8948-1): the fixed packages are php8.3 8.3.6-0ubuntu0.24.04.13 on 24.04, php8.1 8.1.2-1ubuntu2.28 on 22.04 and php8.5 8.5.4-0ubuntu1.5 on 26.04. Ubuntu 20.04 and older are fixed through Ubuntu Pro (ESM). That also means Ubuntu’s 22.04 PHP 8.1 is patched, even though PHP 8.1 is end of life upstream.
  • Debian (security tracker): php8.4 8.4.27-1~deb13u1 on trixie (DSA-6574-1) and php8.2 8.2.35-1~deb12u1 on bookworm (DLA-4848-1). When we checked, sid and forky were still listed as open.
  • RHEL 8, 9 and 10 (RHSB-2026-013): Red Hat lists them as affected, with “some updates already available, with additional updates in progress.” It says RHEL 7 and earlier ship PHP versions that aren’t affected.
  • Alpine: the security database lists fixed php83, php84 and php85 packages for edge. Stable branches weren’t listed yet when we checked.
  • Remi’s repository (rpms.remirepo.net) had no updates on release day. PHP urges users to switch to distribution packages or build from source.
# Ubuntu (command from Canonical's advisory)
apt list '?installed ?name(^php[0-9.]+$)'
sudo apt update && sudo apt install --only-upgrade '?name(^php[0-9.]+$)'

# RHEL family: check the package changelog for the CVE
rpm -q --changelog php-fpm | grep -m1 CVE-2026-109618

A note on automatic updates: Ubuntu’s unattended upgrades apply security fixes within 24 hours, but don’t restart your services. If you install PHP from a third-party repository, as Forge does with Ondřej Surý’s packages, unattended upgrades don’t patch PHP at all.

After upgrading: restart, then verify

sudo systemctl restart php8.3-fpm   # Debian/Ubuntu, per installed version
sudo systemctl restart php-fpm      # RHEL family
sudo systemctl restart apache2      # mod_php on Debian/Ubuntu (httpd on RHEL)

# Laravel: long-running workers keep the old PHP until restarted
php artisan queue:restart
php artisan horizon:terminate
php artisan octane:reload

The Laravel commands come from Forge’s advisory, which also points out that CLI-started processes like queue workers, Horizon, Octane and Reverb keep running the old binary. Then repeat the version checks above, against the web SAPI and not just the CLI.

Docker images

Pulling a new image doesn’t change containers that are already running, and a cached build layer can quietly keep the old PHP. Rebuild from a fresh base, push, redeploy, and then check inside the running container.

docker build --pull --no-cache -t your-app:php-patched .
# after deploying
docker exec your-container php -v
docker exec your-container php-fpm -v

The official php images for 8.5.12, 8.4.27, 8.3.36 and 8.2.35 were published on October 21. That build list no longer includes 8.1, so php:8.1 tags won’t get this fix. If you pin images by digest, update the digest. If your Dockerfile installs PHP from distro packages, the rebuild only helps once that distro has shipped its fix. FrankenPHP compiles PHP into its binary, so you need FrankenPHP 1.13.2, whose Docker images and static binaries include the fixed PHP.

cPanel and Plesk servers

cPanel has shipped EasyApache 4 packages for 8.2 through 8.5, plus backports for its EOL builds: ea-php74 7.4.33-19, ea-php80 8.0.30-12 and ea-php81 8.1.34-5. For anything older than 7.4, it points to paid TuxCare extended support. cPanel’s commands:

yum clean all && yum makecache
/scripts/rpmup
rpm -q --changelog ea-php81-php-cli | grep -m1 CVE-2026-109618

Plesk has fixes for Linux and Windows in 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Its EOL builds keep their old version numbers (7.4.33, 8.0.30 and 8.1.34), and only builds dated October 21, 2026 include the fix, so check the “built” date in /opt/plesk/php/8.1/bin/php -v. Plesk’s own panel runs on an embedded PHP called sw-engine, and its fix is planned as hotfix 18.0.80#11 and 18.0.81#4.

Shared hosting and managed WordPress

If your host manages PHP, you can’t patch it yourself, and updating WordPress won’t help. GoDaddy, for example, says it has patched every PHP version on its Managed WordPress hosting, is waiting on vendor packages for its regular Web Hosting (with mitigations in place in the meantime), and expects VPS customers to update PHP themselves. We haven’t found published statements from most other large hosts yet. Open a ticket and ask a specific question: “Which PHP version and build serves my site, and when was it patched for CVE-2026-109618 and CVE-2026-109619?”

Platforms

  • Laravel Forge: Forge’s own security updates don’t patch PHP. Use Update PHP X.X on the server’s PHP tab for every installed version, check that apt-cache policy php8.4-fpm shows the fixed version as the candidate, and restart your workers.
  • Google Cloud (Google’s guidance): App Engine standard patches supported runtimes automatically. App Engine flexible needs a redeploy. Cloud Run and Cloud Run functions are patched automatically only if automatic base image updates are on, and GKE and custom images need a rebuild. Runtimes on PHP 8.1 or older stay vulnerable until you move to 8.2 or newer.

We haven’t been able to verify statements from other PaaS providers, so we’ve left them out. If you’re on one, check its status page.

If you can’t patch today

Patching is the fix. Everything below shrinks specific attack paths, and PHP says none of it makes an installation safe. These are PHP’s own interim measures:

  • For CVE-2026-109618: set memory_limit=-1 so applications can’t override it, for example with php_admin_value in the FPM pool or Apache config, and keep a container or cgroup memory limit as a backstop. Set post_max_size to 8M if you don’t take uploads, or 32M if you do. If the app doesn’t use multipart form data, block it at the proxy. Don’t use RLIMIT_AS or RLIMIT_DATA, which can trigger the vulnerable path. Removing the memory limit means one request can eat the server’s memory, as Sansec warns, so watch memory while it’s in place.
  • For CVE-2026-109619: set session.upload_progress.enabled=0 in php.ini or the FPM pool, not with ini_set() or .user.ini, and restart every worker. On PHP 8.5, also set fatal_error_backtraces=0. Hiding errors with display_errors or @ doesn’t help. PHP suggests leaving upload progress off even after patching if you don’t use it.
  • Cloudflare: WAF rules have been on by default for Free and paid plans since 03:02 UTC on October 21. On Pro, Business and Enterprise, confirm that the Cloudflare Managed Ruleset is in block mode. A WAF only filters traffic that passes through it, so if your origin server is reachable directly, it isn’t protected.
  • End-of-life servers you can’t upgrade this week: Huntress recommends taking them off the internet or behind a VPN or WAF. Community patches for old PHP versions are circulating, but Huntress says it can’t verify them, and neither can we.

What we haven’t found is PHP or a major vendor recommending a switch from mod_php to PHP-FPM as an emergency fix. PHP says FPM is affected too, and a session-path exploit was validated against FPM. Red Hat notes that FPM’s handling of startup failures blocks the most direct path, so FPM is the better long-term setup. But changing SAPIs in the middle of an incident is a bigger change than applying the patch.

How to tell whether you were hit

No confirmed indicators of compromise had been published when we checked. Huntress said it would publish Sigma and YARA rules once technical details settled. What responders have published so far:

  • Process ancestry (Huntress, Datadog): a PHP worker (php-fpm, php-cgi, apache2, httpd) starting a shell, python, perl, curl, wget or nc is the most direct sign.
  • Logs (Datadog, Wiz): memory-exhaustion fatal errors during request startup alongside unusually large or malformed multipart POSTs, bursts of HTTP 500s, oversized PHPSESSID cookies, and unexplained worker crashes or churn.
  • Caveats: Datadog says a successful exploit may return HTTP 200 and leave the worker running, and PHP says a clean log is not proof of safety.

Beyond that, standard incident hygiene applies. None of the following is specific to PHP2Shell:

# PHP files created or changed in the web root recently (ctime is harder to fake than mtime)
find /var/www -name '*.php' -ctime -3 -ls

# Scheduled jobs for the web user, and system-wide
crontab -l -u www-data
ls -la /etc/cron.d /etc/cron.daily

# What is running as the web user, and what it's connected to
ps -u www-data -o pid,ppid,etime,cmd
ss -tnp | grep -E 'php|apache2|httpd'

Plugin and core updates also change files, so compare what you find against your deploy history. If you find something, Huntress’s advice is to isolate the server and preserve the web server and PHP logs before you clean anything. In practice, we’d rebuild from a known-good image rather than try to clean a compromised box. Wiz also showed that worker memory can leak passwords, tokens, API keys and certificates, so on a server with signs of compromise, rotate every secret that server’s PHP processes could see.

The bigger lesson

Someone has to own patching. PHP shipped a security release on September 24 too, and that’s a normal month. This time, “automatic updates are on” turned out to mean different things in different places: on Ubuntu it doesn’t restart PHP, and on a Forge server it doesn’t touch PHP at all. A person needs to know which updates land automatically, which don’t, and who checks.

Know what runs in production. Huntress’s first recommendation is to inventory every PHP install, including copies bundled inside control panels, Magento builds and vendor appliances. Plesk’s own panel runs its own PHP. So does every FrankenPHP binary. A software bill of materials per repository and a list of base images per service make the next one of these a one-hour job. Our technical due diligence post shows how to generate the SBOM, and our production-readiness checklist covers the rest of knowing what’s deployed and who can fix it.

End-of-life PHP is a liability you can put a date on. PHP 8.1 and older got no release this week. PHP 8.2 stops getting fixes on December 31, 2026. Unpatched end-of-life software is also one of the first things a due-diligence reviewer flags, because it tells them nobody owns the platform.

If nobody owns patching for you

If reading this felt like finding out you don’t know which PHP your site runs, or who would upgrade it, that’s the gap our Senior Team Retainer fills. Senior engineers take on the unglamorous parts of running production: patching, upgrades, dependency and runtime inventory, and moving you off end-of-life versions before they become an incident. It costs $6,000 a month for 30 senior hours or $9,500 for 50, and you can cancel anytime. If the first week doesn’t feel worth it, we refund the first month.

To be clear about the limits: it isn’t an on-call or 24/7 service. We work business hours on CET, which overlaps US mornings. If you need emergency cover, tell us and we’ll quote it separately. If you’d rather start with a conversation about where your PHP estate stands, book a call.