PHP2Shell: how to check, contain, patch and verify PHP servers against a critical RCE
Two critical PHP flaws (CVE-2026-109618, CVE-2026-109619) let attackers run code without logging in, and exploitation began before the fix shipped. I built a check–contain–patch–verify response and a read-only checker script that tells you in seconds whether each PHP server is affected.
9.2
CVSS v4 severity (critical)
0
Logins needed to exploit
4
Steps: check, contain, patch, verify
1
Read-only script to answer "are we affected?"
Table of contents
The business problem
On 21 October 2026, PHP shipped an emergency security release for PHP2Shell: two critical flaws (CVE-2026-109618 and CVE-2026-109619, both CVSS v4 9.2) that can let an attacker run code on a server without logging in.
What makes it different from a typical vulnerability:
- It's in PHP itself, not in your code. Laravel, WordPress, Magento or custom apps are all in scope. Clean, well-written application code doesn't help.
- It triggers before your app runs. The bugs sit in how PHP handles
multipart/form-datarequests (file uploads), which PHP parses before any framework code, middleware or login check. - Attacks started before the fix existed. Details leaked on 20 October, exploitation attempts were reported from 17 October, and attack traffic was seen on the morning of 21 October, hours before the 14:00 UTC release.
For any business running PHP, from online stores and admin panels to booking systems and APIs, the questions were urgent: Are we affected? What do we do today? How do we know we're safe afterwards?
Why existing solutions failed
- "We use a framework, so we're fine" doesn't apply. The vulnerable code runs before Laravel, Symfony or WordPress ever sees the request.
- Web application firewalls and app-level security plugins may not see the attack. Some security vendors noted their in-app protections run after PHP has already parsed the request.
php -vcan lie to you. The command-line PHP is often a different build from the one serving the website (PHP-FPM, Apache mod_php, LiteSpeed or a hosting panel's selector).- Version numbers can mislead both ways. Ubuntu, Debian and RHEL may backport the fix into an old version number like 8.1.x, while a manually installed or Docker build with the same number stays vulnerable.
- Generic scanners flag "PHP detected" but don't tell you which installation the site uses, or whether the interim mitigations are actually active.
The solution
I put together a four-step response — Check, Contain, Patch, Verify — and a small read-only checker script that answers the key questions in seconds on any Linux server.
1. Check: am I affected?
You're affected if any PHP that serves web requests is older than the fixed release for its branch:
| PHP branch | Vulnerable | Safe from |
|---|---|---|
| 8.5 | below 8.5.12 | 8.5.12 |
| 8.4 | below 8.4.27 | 8.4.27 |
| 8.3 | below 8.3.36 | 8.3.36 |
| 8.2 | below 8.2.35 | 8.2.35 |
| 7.4 · 8.0 · 8.1 | all versions | only with a distro/vendor backport |
| 7.3 and older | all versions | no fix: upgrade the branch |
All web SAPIs are in scope: Apache mod_php (highest risk), PHP-FPM, php-cgi, LiteSpeed and FrankenPHP.
Quick manual checks:
- Version the website actually uses: create a temporary file containing
<?php echo PHP_VERSION, ' ', PHP_SAPI, ' upload_progress=', ini_get('session.upload_progress.enabled');, open it once in the browser, then delete it immediately. - Distro backports (8.1 and older):
- Debian/Ubuntu:
zgrep CVE-2026-109618 /usr/share/doc/php8.1-common/changelog.Debian.gz - RHEL/Alma/Rocky:
rpm -q --changelog php-common | grep CVE-2026-109618
- Debian/Ubuntu:
- Shared hosting (Hostinger, cPanel, Plesk): check the PHP version selected in the panel and the exact version the site reports, since the host controls the build.
The checker script (php2shell-check.sh) automates this. It finds every PHP binary on the server, lets PHP compare its own version against the fixed release for its branch, checks distro changelogs for backports, reports whether the interim mitigations are on, and, if pointed at your logs and web root, highlights anything worth reviewing since 17 October. It's read-only and changes nothing.
2. Contain: until you can update
Updating PHP is the only complete fix, but these reduce exposure in the meantime:
- Turn off session upload progress in the main configuration:
session.upload_progress.enabled = 0inphp.ini, orphp_admin_flag[session.upload_progress.enabled] = offin each PHP-FPM pool. Then restart PHP. Setting it withini_set()or.user.iniis not enough. - Block
multipart/form-datawhere uploads aren't needed. This is the known trigger. On nginx, return403for multipart requests on PHP locations; on Apache, use aRewriteCondon the Content-Type header. Scope it carefully: blanket blocking breaks every upload form. In a Laravel app, block it everywhere except the specific upload routes (for example, an admin media endpoint). - Out-of-memory hardening (temporary, with care): some advisories suggest
memory_limit = -1plus tighterpost_max_sizeand request-size limits at the proxy. This trades one risk for resource exhaustion, so monitor memory if you use it. - Restrict access to anything that can't be updated quickly: put admin panels behind an IP allow-list or VPN.
3. Patch
- Ubuntu/Debian:
sudo apt update && sudo apt install --only-upgrade 'php8.3*', then restartphp8.3-fpmorapache2. - RHEL/Alma/Rocky:
sudo dnf upgrade 'php*', then restart PHP-FPM. - Docker: move to a fixed tag and rebuild with
docker build --pull. The official image has no fix for 8.1 and older, so those need a branch upgrade. - Shared hosting: select the newest PHP version in the panel and confirm the host has rolled out the patched build.
- End-of-life branches (7.4–8.1): upgrade to a supported branch. For Laravel this is a good moment to plan the jump to 8.3 or 8.4.
4. Verify
- Re-run the checker, and re-check the version the website reports, not just the CLI.
- Restart workers. Old PHP-FPM or Apache processes keep running the vulnerable code until they're restarted.
- Review signals since 17 October:
- PHP worker crashes (
exited on signal 11, segmentation faults) and out-of-memory fatal errors, since both bugs involve memory corruption; - PHP files that shouldn't exist, especially inside upload or storage folders (the classic place for a web shell);
- PHP files changed recently that you didn't deploy.
- PHP worker crashes (
- If anything looks wrong: rotate secrets (database passwords, API keys,
APP_KEY, session secrets), since one of the bugs can leak server memory, then investigate before declaring the server clean.
Key features
- Clear exposure answer per PHP build: patched, vulnerable (with the exact version to install) or end-of-life
- Checks the right PHP: every binary on the server, not just the one on
PATH - Backport-aware for Debian, Ubuntu and RHEL packages
- Mitigation status for session upload progress, memory and post limits
- Signals worth reviewing: worker crashes, out-of-memory errors, new PHP files, PHP inside upload folders
- Read-only and safe to run on production
- Copy-paste fixes for php.ini, PHP-FPM, nginx, Apache, apt, dnf and Docker
Technical architecture
- Version logic runs inside PHP (
version_compareagainst a branch-to-fixed-version map), so pre-release tags and odd builds are handled correctly. - Backport detection reads Debian and RPM package changelogs for the CVE identifier, because backported builds keep their old version numbers.
- Scans skip
vendor/,node_modules/and cache folders entirely, so even large Laravel projects are scanned in a second or two. - No exploit code. The checker never sends attack traffic. Advisories also warn against running public proof-of-concept scripts on production: they can tie up workers, write data to disk, and some are fake.
Challenges
- Conflicting information on day one. Early reports disagreed on which CVE number maps to which bug, and on how easy exploitation is. I based the response on what all sources agreed on (affected versions, fixed releases, the multipart trigger, and patching as the only complete fix) rather than on one vendor's interpretation.
- "Which PHP is the site using?" On servers with several PHP versions, the CLI often differs from PHP-FPM or the hosting panel's build. The checker inspects every binary, and the guide adds a temporary web-served check to confirm what visitors actually hit.
- Mitigations that break things. Blocking multipart requests stops uploads: profile photos, product images, document uploads. Scoping the block to routes that never need uploads keeps the business running while reducing exposure.
- Not overreacting. A worker crash or an out-of-memory error isn't proof of a breach. The checker labels them as signals to review, not verdicts, so findings are investigated rather than panicked over.
The outcome
- A minutes-long answer to "are we affected?" for every PHP server, instead of guessing from
php -v. - A safe interim posture (upload progress off, scoped multipart blocking, restricted admin access) that can be applied the same day without taking sites offline.
- A repeatable patch and verify routine that covers apt, dnf, Docker and shared hosting, ending with proof that the website, not just the CLI, runs a fixed build.
- A reusable checker that can be run again after every PHP change or future advisory.
What this means for other businesses
Most businesses don't think about the language their website runs on until something like PHP2Shell happens. A few lessons apply far beyond this one bug:
- Know your stack. Keep a simple inventory of which PHP version each site and server runs, and who updates it.
- Stay on supported versions. PHP 8.1 and older didn't get an official fix; 8.2 reaches end of life on 31 December 2026.
- Have a response plan before you need it. Check, contain, patch, verify, with someone accountable for each step.
- Layer your defences. Restricting admin panels, limiting uploads to the routes that need them and monitoring for unexpected files all reduce the damage of the next critical bug.
Running PHP and not sure where you stand? I can check your servers, apply safe mitigations and get you patched with minimal downtime.
Sources: PHP.net, watchTowr, Sansec, Qualys, Akamai, Sophos.