CVE Tools
PHP2Shell (CVE-2026-109618, CVE-2026-109619): how to protect your PHP servers. Full guide

PHP2Shell (CVE-2026-109618, CVE-2026-109619): how to protect your PHP servers. Full guide

Two critical remote code execution flaws in PHP itself. Fixes ship October 21 at 14:00 UTC. Attack chains, what's confirmed, and a step-by-step defence guide collected from CERT-EU, PHP core developers and hands-on admin write-ups.

The PHP project has pre-announced an emergency security release for two critical vulnerabilities, both rated CVSS v4.0 9.2. Security researchers and CERT-EU tie them to CVE-2026-109618 and CVE-2026-109619 and describe both as remote code execution in PHP itself — not in a framework or a plugin. An exploit tool nicknamed PHP2Shell leaked as a screenshot alongside the news. If you run PHP on a public web server, plan to patch within hours of the release, not days. Below: what is known, how the attack is expected to work, and a defence checklist for every common setup.

What is confirmed

  • PHP's official notice (Oct 20): a security release on Oct 21, 14:00 UTC, fixing two critical vulnerabilities, both CVSS v4.0 9.2. Affected and fixed versions will be published with the release.
  • CERT-EU advisory 2026-018 lists both CVEs and says the score indicates potential unauthenticated remote code execution.
  • watchTowr says both are RCE, that it reproduced CVE-2026-109618 in its lab, and that it has not seen exploitation in the wild. Neither CVE is in CISA KEV.
  • Officially supported PHP branches are 8.2, 8.3, 8.4 and 8.5 (supported versions). Older branches should not expect an official fix.

What is reported but not confirmed by PHP

  • Where the bug is. Independent research and several write-ups point to a use-after-free in session.upload_progress handling, triggered by a multipart/form-data POST. One researcher claims a second path in the multipart parser that does not depend on that setting. At least one audit disputes the claim. PHP has not confirmed any of it.
  • Exact affected versions. Claims range from "PHP 5.4.0 onward" (Sansec) to "PHP 5 onward" (CERT-EU, citing a social media post).
  • Score. Some reports quote CVSS 10.0. The official PHP rating is 9.2 (v4.0). NVD and distro trackers have no records yet.
  • Public PoC. Exploit bundles claiming to be PHP2Shell are already circulating. They are unverified — do not download or run them; they can just as easily be malware aimed at defenders.

Why this one is urgent

PHP runs a large share of the web: WordPress, WooCommerce, Magento, Shopware, Drupal, Nextcloud, Laravel apps and countless custom sites. The reported bug sits in PHP's request parsing — it fires before your application code runs, so a plugin update, a login check or an app-level firewall will not cover it. PHP is open source, so once the fix ships attackers can diff it; Sansec expects mass attacks within about 15 minutes of the release.

Attack chains

1. From a request to a compromised server

PHP2Shell: expected attack chain on an unpatched server

  1. Internet-wide scan for PHP — Attackers fingerprint hosts that run PHP (`X-Powered-By` headers, `.php` URLs, known CMS paths).
  2. Crafted multipart POST — Reportedly a `multipart/form-data` request to **any** PHP script — no login, no upload form needed. Not yet confirmed by PHP.
  3. Hits the PHP engine before app code — PHP parses the request body before your code runs, so plugins, middleware and app rules never see it.
  4. Reported root cause — Use-after-free in `session.upload_progress`; possibly a second path in the multipart parser. Unconfirmed.
  5. Fixed PHP build installed? — Fixed versions arrive Oct 21, 14:00 UTC (8.2–8.5 only).
  6. Blocked — The patched engine handles the request safely.
  7. Code execution as the PHP worker — Commands run as the FPM / Apache user (e.g. `www-data`) — with access to everything the website can read.
  8. Webshell / persistence — Typical next step: a backdoor `.php` file, `.user.ini` / `auto_prepend_file`, a cron job or an SSH key, so access survives the patch.
  9. What runs on the box?
  10. Store: payment skimmer — Sansec flags Magento, Shopware and WooCommerce as exposed; on shops the classic payload is a card skimmer in checkout.
  11. Secrets and database stolen — `wp-config.php`, `env.php`, `.env` hold database and API keys → data theft and access to other services.
  12. Pivot, spam or mining — The server becomes a foothold into the internal network, or is reused for spam and cryptomining.

2. From the patch to mass exploitation

Patch release → exploit → mass scanning

  1. Fixed PHP released — Oct 21, 14:00 UTC (or earlier if details leak or attacks start).
  2. Attackers diff the source — Comparing old and new code points straight at the vulnerable function.
  3. Working exploit — watchTowr already reproduced CVE-2026-109618; exploit bundles are circulating.
  4. Mass scanning — Sansec expects mass attacks within ~15 minutes of the release.
  5. Your servers updated?
  6. Safe — Fixed build installed, every FPM pool and worker restarted.
  7. Compromised — Late patching, a forgotten host, or an end-of-life branch with no fix at all.

Am I exposed?

A quick decision path

  1. Do you run PHP anywhere? — Include the hidden ones: CMS sites, phpMyAdmin, admin panels, staging, vendor appliances and containers.
  2. Reachable from the internet?
  3. Lower urgency — still patch — Internal-only hosts are reachable from a compromised neighbour.
  4. Which PHP branch?
  5. Install the fix at 14:00 UTC — Fixed releases for 8.2, 8.3, 8.4, 8.5.
  6. Backport, upgrade or isolate — 8.1 and older get no official fix. Ask your distro / host, move to 8.2+, or take the site off the internet.
  7. Meanwhile: interim layers — Do your sites accept file uploads?
  8. Upload progress off + block multipart — No uploads needed → block `multipart` at the web server for all PHP routes.
  9. Upload progress off + allowlist — Uploads needed → allow multipart only on known upload routes, restrict those to trusted users or IPs.
  10. Patch, restart, verify via the web server — Check the PHP build in a request served by FPM / Apache, not on the CLI.

Step 1. Find every PHP install

The host you forget is the one that gets owned. CERT-EU's first recommendation is to identify all PHP-based applications. Look beyond the main site: staging, old marketing sites, phpMyAdmin and admin tools on subdomains, vendor appliances, containers.

# Debian/Ubuntu: installed PHP branches
ls /etc/php

# PHP binaries and Apache modules outside the usual paths
find / -xdev \( -name 'php' -o -name 'php-fpm*' -o -name 'php-cgi*' -o -name 'libphp*.so' \) -type f 2>/dev/null

# Running containers built on PHP images
docker ps -q | xargs -r -I{} docker exec {} sh -c 'echo "{} $PHP_VERSION"' 2>/dev/null | grep -v ' $'

# What a site reveals from the outside
curl -sI https://example.com | grep -i x-powered-by

For each host write down the branch, where PHP came from (distro, PPA, Docker image, panel), the SAPI (FPM, mod_php, CGI) and whether it faces the internet. php -v on the command line can differ from what the web server actually loads — check the Server API row of a private phpinfo() page and delete it right after.

Step 2. Before the patch: turn off session upload progress

This is the stopgap most researchers converge on (Sansec, opennet, independent admin write-ups). It disables only upload progress bars — normal uploads keep working. It is not confirmed to block both bugs, so treat it as one layer, not a fix. It cannot be changed with ini_set(); it must go into a config file.

SetupWhere to set itApply
Own server, any SAPIphp.ini or a file in conf.d/: session.upload_progress.enabled = 0Restart PHP-FPM / Apache
PHP-FPM (recommended)Each pool, e.g. /etc/php/8.3/fpm/pool.d/www.conf: php_admin_flag[session.upload_progress.enabled] = offsystemctl reload php8.3-fpm for every branch
Apache mod_php, no php.ini access.htaccess: php_flag session.upload_progress.enabled offImmediate (needs AllowOverride Options or All)
Shared hosting / FPM / CGI, no php.ini access.user.ini in the web root: session.upload_progress.enabled = 0Up to 5 min (user_ini.cache_ttl)
cPanel / Plesk / DirectAdminMultiPHP INI Editor or the PHP settings page, per domain and versionPanel applies it
DockerCopy an ini into /usr/local/etc/php/conf.d/ in your imageRebuild and redeploy
  • Wrong file = 500 error. php_flag in .htaccess breaks PHP-FPM/CGI sites, and .user.ini is ignored under mod_php. Load the site after each change.
  • Lock it. A .user.ini can switch the setting back on; php_admin_flag in the FPM pool cannot be overridden. Look for stray overrides: find /home /var/www -maxdepth 6 -name .user.ini.
  • Verify through the web server, not the CLI: a private phpinfo() must show session.upload_progress.enabled = Off.
  • Strict session mode (session.use_strict_mode = 1) is also recommended by Sansec; it has no effect when sessions are stored in Redis or a database.

Step 3. Before the patch: block or limit multipart requests

Because the reported trigger is a multipart/form-data request, the strongest interim layer is to stop such requests from reaching PHP. The trade-off: file uploads and some forms stop working. If a site needs uploads, allow multipart only on the known upload routes and restrict those to logged-in staff or trusted IPs. Rules published by opennet and admin write-ups:

# nginx — inside every server {} block, including default/catch-all ones
if ($http_content_type ~* "multipart") {
    return 403;   # use 444 if error_page routes errors to PHP
}
# Apache httpd (mod_rewrite)
RewriteEngine On
RewriteCond %{HTTP:Content-Type} multipart [NC]
RewriteRule ^ - [F,L]
# Use %{HTTP:Content-Type}, NOT %{CONTENT_TYPE} — the latter silently never matches
# Caddy / FrankenPHP — put it in the same route/handle block as php_fastcgi / php_server
@multipart header_regexp Content-Type (?i)multipart
respond @multipart 403
  • Test it with a multipart request to a .php URL and confirm the block in the access log: curl -sS -o /dev/null -w '%{http_code}\n' -F x=1 https://example.com/index.php should return 403.
  • Cloudflare / CDN WAF: a custom rule blocking multipart works only if attackers cannot reach the origin directly — restrict ports 80/443 to the CDN's IP ranges.
  • ModSecurity: a narrower rule denies requests whose body contains the PHP_SESSION_UPLOAD_PROGRESS field (needs SecRequestBodyAccess On). WAFs parse multipart differently from PHP, so treat it as an extra layer only.
  • Admin tools such as phpMyAdmin or Adminer: restrict them to your own IPs now, whatever else you do.
  • Can't patch at all? CERT-EU recommends isolating internet-facing PHP applications from public networks until a fix is applied.

Step 4. After 14:00 UTC: patch and restart everything

  1. Get the fixed versions from php.net (or your vendor) — not from social media or blog posts.
  2. Debian / Ubuntu: run sudo apt update, then apt-cache policy php8.3-fpm (repeat for every branch in /etc/php) to compare installed and candidate versions. Check apt-mark showhold — a held package silently skips the update.
  3. Backports keep the old version number. Distros often patch without bumping the upstream version, so check the package changelog for the CVE: zcat /usr/share/doc/php8.3-fpm/changelog.Debian.gz | head -20. If the CVE is not there, the package is not fixed yet.
  4. RHEL / Alma / Rocky: dnf updateinfo list --cve CVE-2026-109618 once the advisory lands, then dnf update 'php*'.
  5. Restart every PHP process: each FPM branch (systemctl restart php8.x-fpm), Apache for mod_php, and long-running workers — queue workers, Laravel Horizon, Octane, schedulers, Swoole/RoadRunner. Find processes still running the old binary: ls -l /proc//exe 2>/dev/null | grep -i 'php.deleted'.
  6. Docker: the official php images are rebuilt from php.net sources. Pull the new base, rebuild your image, redeploy, and confirm with docker run --rm your-image php -v. Old images stay vulnerable forever.
  7. Shared / managed hosting: ask the host to confirm that the patched build serves each of your domains. Switching the PHP version in cPanel, Plesk or CloudLinux does not prove the build is fixed.
  8. End-of-life branches (8.1 and older): no official fix is expected. Keep the interim layers, buy extended support from your distro or vendor, or move to 8.2+. Ubuntu 20.04 users have no PPA builds — upgrade the OS.
  9. Re-check your config after the upgrade. Package updates can overwrite local ini files.

Step 5. Check whether you were already hit

Details leaked around 15:00 UTC on October 20, so treat that as the start of the exposure window. Preserve web server and PHP logs before patching or log rotation.

# PHP-FPM workers crashing (memory corruption attempts often crash first)
grep -E 'SIGSEGV|signal 11|SIGBUS' /var/log/php*-fpm.log
journalctl -u 'php*-fpm*' --since '2026-10-20 15:00' | grep -Ei 'segfault|signal'

# PHP files created or changed since the leak
find /var/www -type f \( -name '*.php' -o -name '.user.ini' -o -name '.htaccess' \) -newermt '2026-10-20 15:00'

# Shells or tools started by the web user (www-data on Debian/Ubuntu)
ps -u www-data -o pid,ppid,lstart,cmd | grep -Ev 'php-fpm|apache2|nginx'
  • Look for multipart POSTs to URLs that never take uploads, and request bodies with a PHP_SESSION_UPLOAD_PROGRESS field when your app has no upload progress bar.
  • Check persistence spots: auto_prepend_file / auto_append_file, new .user.ini files, cron and systemd timers, Supervisor configs, ~/.ssh/authorized_keys, new admin users in the CMS, mu-plugins in WordPress.
  • Watch for outbound connections from PHP workers and for sh, curl, wget or nc spawned by php-fpm — SigmaHQ's webshell rules plus auditd on the web user's execve calls catch most of this.
  • On stores, compare checkout templates and JavaScript against a clean copy to rule out a card skimmer.
  • If you find anything: isolate the server, rebuild it from a clean image, and rotate every secret the PHP user could read — database passwords, API keys, payment and mail credentials.

Notes for popular platforms

PlatformWhat to do
WordPressCore does not need upload progress, so turning it off is safe. Security plugins and Wordfence do not stop this. If you block multipart, media uploads and some form plugins break — allowlist /wp-admin/ for your IPs.
WooCommerce, Magento, ShopwareHighest-value targets (Sansec). Patch first, then check checkout pages for skimmers.
LaravelMaintenance mode and middleware run too late. Livewire / Filament uploads break under a multipart block. Restart queue workers, Horizon and Octane after patching. Vapor: rebuild Docker runtimes; native runtimes need Vapor's fixed runtime.
Nextcloud, Drupal, other upload-heavy appsDon't block multipart globally — turn off upload progress, restrict the instance to VPN or known IPs, and patch at release.
Docker / KubernetesRebuild images from the patched php base and roll out every deployment; check sidecars and cron jobs built on PHP too.
Shared hostingUse .user.ini or the panel's PHP settings; ask the host for written confirmation of the patched build.

General hardening (worth doing anyway)

  • expose_php = Off — stop advertising the PHP version in X-Powered-By.
  • Disable PHP extensions you don't use.
  • Run each site under its own FPM pool and system user, so one compromised site doesn't expose the rest.
  • disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec raises the bar after compromise — memory-corruption exploits have bypassed it before, so it is not a fix.
  • Keep a monitored inventory of PHP versions across your estate — this will not be the last emergency release.

Timeline (UTC)

  1. Leak
    A Hacker News post reports two upcoming PHP RCE disclosures (15:20 UTC, per Sansec).
  2. PHP pre-announces a critical security release
    Two vulnerabilities, CVSS v4.0 9.2.
    source
  3. watchTowr confirms RCE, reproduces CVE-2026-109618
    source
  4. CERT-EU advisory 2026-018
    Identify PHP apps, prepare to patch, isolate if needed.
    source
  5. Scheduled PHP security release
    14:00 UTC. Fixed versions to be announced.

FAQ

What is PHP2Shell?
The nickname for two critical remote code execution flaws in PHP, CVE-2026-109618 and CVE-2026-109619, rated CVSS v4.0 9.2 by the PHP security team. Fixes ship on October 21, 2026 at 14:00 UTC.
Is PHP2Shell being exploited in the wild?
Not as of October 21 morning UTC. watchTowr reports no observed exploitation and neither CVE is in CISA KEV. Expect that to change quickly after the patch is published.
Which PHP versions are affected?
Not officially published yet. Researchers suggest PHP 5.4 and later. PHP will publish the affected and fixed versions with the release.
Does disabling session.upload_progress fix it?
No. It is a reasonable stopgap that may block one of the reported paths, but a PHP core developer says the only mitigation is upgrading PHP. Combine it with a multipart block and patch at release.
Does a WAF or Cloudflare protect me?
Partly. A rule blocking multipart requests helps only if the origin cannot be reached directly, and WAFs parse multipart differently from PHP. It buys time; it doesn't replace the patch.
Does Wordfence or another WordPress security plugin help?
No. The bug is in PHP's request parsing, which runs before any plugin code — including Wordfence's prepend-file firewall.
Will PHP 7.4 or 8.1 get a fix?
Probably not from the PHP project: only 8.2–8.5 are supported. Check with your distribution for a backport, upgrade, or isolate the site.
How do I check that my distro package is patched?
Look for the CVE ID in the package changelog (zcat /usr/share/doc/php8.3-fpm/changelog.Debian.gz | head). Distros often backport fixes without changing the upstream version number.
Is my Docker image patched automatically?
No. Pull the updated official php base image, rebuild your own image and redeploy. Images built before the release stay vulnerable.
How do I know if I was already hacked?
Check PHP-FPM logs for segfaults since October 20, 15:00 UTC, look for new or changed .php and .user.ini files, and for shells started by the web user. If anything looks off, rebuild the server and rotate its secrets.

Sources: PHP announcement, CERT-EU 2026-018, Sansec, watchTowr, BleepingComputer, OpenNET, Vincent Schmalbach, lilting.ch, pentestit, Gina Peter Banyard on phpc.social.

Filed under php, php2shell, cve-2026-109618, cve-2026-109619, rce, session.upload_progress, wordpress, vulnerability

More from the blog