
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.
Member of PHP FartTime14 min read
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-109618in 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_progresshandling, triggered by amultipart/form-dataPOST. 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
- Internet-wide scan for PHP — Attackers fingerprint hosts that run PHP (`X-Powered-By` headers, `.php` URLs, known CMS paths).
- Crafted multipart POST — Reportedly a `multipart/form-data` request to **any** PHP script — no login, no upload form needed. Not yet confirmed by PHP.
- 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.
- Reported root cause — Use-after-free in `session.upload_progress`; possibly a second path in the multipart parser. Unconfirmed.
- Fixed PHP build installed? — Fixed versions arrive Oct 21, 14:00 UTC (8.2–8.5 only).
- Blocked — The patched engine handles the request safely.
- 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.
- 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.
- What runs on the box?
- Store: payment skimmer — Sansec flags Magento, Shopware and WooCommerce as exposed; on shops the classic payload is a card skimmer in checkout.
- Secrets and database stolen — `wp-config.php`, `env.php`, `.env` hold database and API keys → data theft and access to other services.
- 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
- Fixed PHP released — Oct 21, 14:00 UTC (or earlier if details leak or attacks start).
- Attackers diff the source — Comparing old and new code points straight at the vulnerable function.
- Working exploit — watchTowr already reproduced CVE-2026-109618; exploit bundles are circulating.
- Mass scanning — Sansec expects mass attacks within ~15 minutes of the release.
- Your servers updated?
- Safe — Fixed build installed, every FPM pool and worker restarted.
- Compromised — Late patching, a forgotten host, or an end-of-life branch with no fix at all.
Am I exposed?
A quick decision path
- Do you run PHP anywhere? — Include the hidden ones: CMS sites, phpMyAdmin, admin panels, staging, vendor appliances and containers.
- Reachable from the internet?
- Lower urgency — still patch — Internal-only hosts are reachable from a compromised neighbour.
- Which PHP branch?
- Install the fix at 14:00 UTC — Fixed releases for 8.2, 8.3, 8.4, 8.5.
- 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.
- Meanwhile: interim layers — Do your sites accept file uploads?
- Upload progress off + block multipart — No uploads needed → block `multipart` at the web server for all PHP routes.
- Upload progress off + allowlist — Uploads needed → allow multipart only on known upload routes, restrict those to trusted users or IPs.
- 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-byFor 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.
| Setup | Where to set it | Apply |
|---|---|---|
| Own server, any SAPI | php.ini or a file in conf.d/: session.upload_progress.enabled = 0 | Restart 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] = off | systemctl reload php8.3-fpm for every branch |
| Apache mod_php, no php.ini access | .htaccess: php_flag session.upload_progress.enabled off | Immediate (needs AllowOverride Options or All) |
| Shared hosting / FPM / CGI, no php.ini access | .user.ini in the web root: session.upload_progress.enabled = 0 | Up to 5 min (user_ini.cache_ttl) |
| cPanel / Plesk / DirectAdmin | MultiPHP INI Editor or the PHP settings page, per domain and version | Panel applies it |
| Docker | Copy an ini into /usr/local/etc/php/conf.d/ in your image | Rebuild and redeploy |
- Wrong file = 500 error.
php_flagin.htaccessbreaks PHP-FPM/CGI sites, and.user.iniis ignored under mod_php. Load the site after each change. - Lock it. A
.user.inican switch the setting back on;php_admin_flagin 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 showsession.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
.phpURL and confirm the block in the access log:curl -sS -o /dev/null -w '%{http_code}\n' -F x=1 https://example.com/index.phpshould return403. - 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_PROGRESSfield (needsSecRequestBodyAccess 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
- Get the fixed versions from php.net (or your vendor) — not from social media or blog posts.
- Debian / Ubuntu: run
sudo apt update, thenapt-cache policy php8.3-fpm(repeat for every branch in/etc/php) to compare installed and candidate versions. Checkapt-mark showhold— a held package silently skips the update. - 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. - RHEL / Alma / Rocky:
dnf updateinfo list --cve CVE-2026-109618once the advisory lands, thendnf update 'php*'. - 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'. - Docker: the official
phpimages are rebuilt from php.net sources. Pull the new base, rebuild your image, redeploy, and confirm withdocker run --rm your-image php -v. Old images stay vulnerable forever. - 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.
- 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.
- 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_PROGRESSfield when your app has no upload progress bar. - Check persistence spots:
auto_prepend_file/auto_append_file, new.user.inifiles, cron and systemd timers, Supervisor configs,~/.ssh/authorized_keys, new admin users in the CMS,mu-pluginsin WordPress. - Watch for outbound connections from PHP workers and for
sh,curl,wgetorncspawned byphp-fpm— SigmaHQ's webshell rules plusauditdon the web user'sexecvecalls 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
| Platform | What to do |
|---|---|
| WordPress | Core 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, Shopware | Highest-value targets (Sansec). Patch first, then check checkout pages for skimmers. |
| Laravel | Maintenance 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 apps | Don't block multipart globally — turn off upload progress, restrict the instance to VPN or known IPs, and patch at release. |
| Docker / Kubernetes | Rebuild images from the patched php base and roll out every deployment; check sidecars and cron jobs built on PHP too. |
| Shared hosting | Use .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 inX-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_execraises 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)
- LeakA Hacker News post reports two upcoming PHP RCE disclosures (15:20 UTC, per Sansec).
- watchTowr confirms RCE, reproduces CVE-2026-109618source
- Scheduled PHP security release14:00 UTC. Fixed versions to be announced.
FAQ
What is PHP2Shell?
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?
Which PHP versions are affected?
Does disabling session.upload_progress fix it?
Does a WAF or Cloudflare protect me?
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?
Will PHP 7.4 or 8.1 get a fix?
How do I check that my distro package is patched?
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?
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?
.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.