PHP2Shell: Who's Exposed and What to Patch
PHP2Shell is under attack: two critical PHP flaws let attackers run code on web servers without a login. Who's exposed, how to patch, and what to check for.
On Wednesday 21 October the PHP project shipped emergency releases, PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, to fix two critical flaws that can let an attacker run their own code on a web server without logging in. They are tracked as CVE-2026-109618 and CVE-2026-109619, both rated 9.2 out of 10, and researchers have nicknamed the pair "PHP2Shell": send the right requests to a vulnerable site, get a shell on the server behind it.
We first published this post the day the fixes shipped. A day later the picture is worse: someone tried to exploit the flaws four days before they were disclosed, attack attempts now run into the millions, and several European governments took their own websites offline rather than risk it.
The bugs are in PHP itself, not in a plugin or an app, so they sit underneath WordPress, Drupal, Magento, Laravel and every other PHP application at once. Below: what has changed, who is exposed and whose job the fix is, how to update properly, what to do if you can't update today, and how to check whether someone got in before you patched.
What has changed since 21 October#
- Attacks began before the disclosure and are now widespread. Amazon logged an attempt on 17 October and says PHP2Shell is now "broadly exploited in the wild". Details below.
- Governments took their own websites offline. Germany, France, Belgium, the Netherlands, Finland and the Czech Republic pulled government sites while they patched, and the NSA advised taking sites offline if they can't be updated immediately (Al Jazeera). The UK's NCSC urged organisations to consider taking offline "any system that can't be fixed right away" (Help Net Security).
- Researchers have shown attacks on everyday applications, including WordPress, Drupal and Joomla.
- Fixed packages are out almost everywhere, including from Remi's repository, which PHP had warned would lag. A few platforms were still catching up on Thursday morning; see step 2.
- We now know how the leak happened, and that PHP had known about part of the problem since 2024. See the timeline.
Node.js shipped its own security release the same day, including a Brotli decoder bug that Node says permits remote code execution. If you run both, see our Node.js post.
What happened#
- 2024: PHP's first external security audit in years, carried out by Quarkslab, led a core developer to the first signs of the out-of-memory problem behind CVE-2026-109618. An attack was theorised but none found, and the fix stalled. The PHP Foundation now calls the delay "a structural failure, caused by both the security team's decision process and its lack of resources" (PHP Foundation).
- August 2026: researchers at Calif, using AI models as part of OpenAI's "Patch the Planet" programme, reported the first bug to PHP on 11 August, with a second report later that month (Calif). Calif, OpenAI and Wiz are credited with the discoveries.
- 17 October: Amazon's honeypots recorded the first known attempt to exploit the flaws, from an address on a commercial anonymising network. Amazon says it is unclear how the attacker got the details before disclosure (The Hacker News).
- 20 October: around 15:00 UTC, the two CVE numbers leaked on forums and social media, with a wrong release time and a wrong severity score but no technical details. At 19:30 UTC PHP published an advance notice and brought the release forward a day. Around 20:00 UTC, Calif mistakenly published a video of its exploit working against several targets. It was deleted, but copies spread.
- 21 October, 07:41 UTC: Chainguard published container images containing the fix, more than six hours before the coordinated release. The PHP Foundation says that broke the embargo and let anyone study the fix early; Chainguard's chief executive said he made the call because exploits were already being used (Socket).
- 21 October, 14:00 UTC: PHP published the fixed versions and a security advisory, with a technical write-up of each flaw on GitHub: CVE-2026-109618 and CVE-2026-109619. Linux distributions and long-term-support vendors had received the patches in advance.
Is it being exploited?#
Yes. Attackers are trying it at scale:
- Amazon saw further attempts from about 02:00 UTC on 21 October, likely from several different actors, and says PHP2Shell "is now being broadly exploited in the wild" (Tenable).
- Cloudflare's chief executive, Matthew Prince, said attempts went from thousands before the announcement to millions by the time he posted (Security Online), and Cloudflare says attackers keep reworking their payloads to get past its rules.
- watchTowr says it has reproduced both bugs and is seeing exploitation attempts against both (watchTowr).
- Hadrian says most of the exploitation it has seen goes through PHP's session upload-progress feature (SecurityWeek), the path the first stopgap below closes.
- VulnCheck caught a scanner calling itself
php2shell-scan/109618, and one host trying to exploit both flaws (The Hacker News).
Whether the attacks are succeeding is less clear. The PHP Foundation says none of the public exploit code it examined worked at release time. watchTowr's head of threat intelligence told SecurityWeek on Wednesday: "Whilst nobody has been successful to date, we don't expect that will last long." Huntress had seen no exploitation on its customers' systems as of 4:30 pm US Eastern time that day (Huntress), and none of the reports we have read describes a confirmed break-in yet.
Don't take much comfort from that. Wiz has shown unauthenticated code execution against WordPress, Drupal and Joomla, and Calif says it confirmed attacks without a login against recent versions of WordPress, Drupal, Joomla, Magento, Nextcloud, Laravel and Symfony, among others. Public proof-of-concept code is circulating too. If you test any of it, do it in a throwaway lab: fake exploit repositories that infect whoever runs them are a well-worn trick after big disclosures.
As we put it in our guide to securing AI-built apps, a published vulnerability is a published map for attackers. This one was published early.
Who is exposed#
PHP's advisory is blunt: every PHP installation that serves web requests is at risk. What differs is how directly, depending on how PHP is wired into the web server.
| How PHP runs | Risk, per PHP's advisories |
|---|---|
| Apache with mod_php | Highest. Unauthenticated code execution was demonstrated for both flaws, and the memory flaw fires while PHP is still reading the request, before any application code runs. |
| PHP-FPM (behind nginx, Apache or another proxy) | Exposed. PHP validated code execution through the session-upload path, though it needed a suitable operation in the application, and Calif has shown an attack on PHP-FPM behind nginx. For the memory flaw, PHP's analysis says FPM is susceptible, but no complete exploit was shown. |
| FrankenPHP | Likely susceptible to the memory flaw with no help from the application, per PHP's source analysis. |
| LiteSpeed (LSAPI) | Exploits that depend on the application are considered likely, but were not validated. |
| php-cgi | Named by PHP as at risk. |
| Command line only (cron jobs and scripts, no web traffic) | Outside PHP's at-risk list, which covers installations serving web requests. Update it with everything else anyway. |
Whose job the fix is depends on who runs PHP for you:
- You only visit websites. Nothing to do. This is a server-side bug; your browser and phone are not affected.
- Your site is on shared or managed hosting, which is where many small-business WordPress sites live. Your host runs PHP, so installing the fix is their job. Yours is to make sure it happened: check your host's status page or announcements, or ask support directly whether the PHP build your site runs includes the fixes for CVE-2026-109618 and CVE-2026-109619. In WordPress, Tools > Site Health > Info > Server shows the PHP version your site is using. Don't assume: on Thursday morning CloudLinux, whose PHP builds many shared hosts use, was still preparing fixed packages for current PHP versions (Mattias Geniar's tracker).
- You run your own server or VPS. The fix is yours. The steps are below.
- You run PHP in Docker. A running container never updates itself. The official
php:<version>-apacheimages run PHP as mod_php under Apache's prefork model, and the official WordPress image's default variant is built on top of them (its Dockerfile startsFROM php:8.3-apache). A WordPress container built from the official image therefore sits in PHP's highest-risk configuration. Fixed images are available; see step 2. - You use software that bundles its own PHP. Control panels, vendor appliances and some Windows software ship their own copy of PHP, and FrankenPHP's standalone binaries embed one too. Those copies only change when the vendor ships an update, and Huntress lists them among the less obvious places PHP hides. FrankenPHP 1.13.2 carries the fix, but Laravel Octane keeps its own copy of the
frankenphpbinary in your project and will happily keep running an older one: delete it and restart Octane so it downloads a current build. - You run PHP 8.1 or older. No official release is coming. See step 4.
What to do now#
1) Find every PHP you run#
Start with internet-facing servers, then hunt for the less obvious ones: staging sites, old campaign microsites, internal admin tools, and anything a contractor set up and nobody has touched since.
From the outside, a site that sends an X-Powered-By header announces its PHP version to anyone who asks:
curl -sI https://example.com | grep -i x-powered-by
A missing header proves nothing, since many servers hide it. An old-looking number is worth checking, though distribution builds can carry the fix under an older number (more on that in step 2).
On the server itself:
php -v # the command-line PHP
php-fpm8.3 -v # PHP-FPM on Debian/Ubuntu (match your version); php-fpm -v on RHEL-family systems
The command-line PHP and the one your web server uses are not always the same build, especially with several versions installed side by side. Check the one that actually serves your site.
2) Update to a fixed build#
- Upstream builds: PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Source and Windows downloads are linked from PHP's advisory.
- Distribution packages: install your vendor's security update. Debian (DSA-6574-1 for Debian 13, DLA-4848-1 for Debian 12), Ubuntu (USN-8948-1), Red Hat and its rebuilds, SUSE, Alpine 3.24 and others have shipped fixes, and Mattias Geniar's per-platform tracker is more current than any article. Distributions often backport fixes into the version they already ship, so a patched package can still report an older version number. Compare the installed package version with your vendor's advisory, not with PHP's numbers. Ubuntu's changelogs name the two CVEs; Debian's, and some Red Hat streams, may only say "new upstream version".
# Debian / Ubuntu: what is installed, to compare with your vendor's advisory
dpkg -l 'php*' 'libapache2-mod-php*' | grep '^ii'
# Ubuntu: the changelog names the CVEs (use the package name for your PHP version)
apt changelog php8.3-fpm | grep -E 'CVE-2026-10961[89]'
# RHEL / AlmaLinux / Rocky Linux
rpm -q php-fpm
- Third-party repositories: on release day PHP warned that the volunteer-run remi repository (
rpms.remirepo.net) would not have updates that day. It has since shipped fixed packages for all current branches. Ondřej Surý's widely used packages for Debian and Ubuntu are fixed on amd64, but some arm64 builds were still catching up on Thursday morning, so check what your servers actually installed. - Containers: the official
phpimages on Docker Hub now carry the fix for every current branch. Pull the new tag, rebuild your own images withdocker build --pull, recreate the containers, then confirm what is actually running withdocker exec <container> php -v.
3) Restart every PHP process#
Updating a package does not replace code that is already running in memory. Restart PHP-FPM (every pool), Apache if it runs mod_php, and any long-running PHP workers, then confirm the new build is the one answering requests. Package managers often restart services for you; check rather than assume.
4) If you're on PHP 8.1 or older#
The PHP project no longer supports these versions and has not released fixed builds. Calif traced the earliest vulnerable code path back to PHP 5.4, released in 2012, so assume any older version is affected. Your options:
- Your vendor's backport. Some Linux distributions and long-term-support vendors still patch versions PHP itself has retired: Ubuntu has fixed its older PHP packages through Ubuntu Pro, for example, and Freexian's paid extended support covers older Debian releases. Ask yours.
- PHP's own source patches. Given the severity, PHP's security team published minimal backports for 7.4, 8.0 and 8.1, linked from its advisory, for anyone who builds PHP from source.
- Upgrade to a supported version. That is the real fix, and the window is shrinking: PHP 8.2 itself stops receiving security fixes on 31 December 2026.
Until one of those is done, take the site off the open internet, or put it behind a VPN or web-server-level authentication that stops requests before they reach PHP. An application login page doesn't count: the attack doesn't need to get past it. Governments did exactly this with their own sites this week.
If you can't update today#
PHP lists stopgaps that close the specific attack paths it has confirmed, and it is explicit that none of them makes a server safe. watchTowr goes further: it says mitigations that rely on .ini changes "should be understood to be ineffective". Use them to buy hours while the update goes in, nothing more.
Web application firewalls are the same. Cloudflare has rules for known attack patterns on every plan, including the free one (Help Net Security), but attackers are already reworking their payloads to get around them. A firewall rule is a useful extra layer, never the fix. Organisations running Oracle PeopleSoft learned that this year, when attackers walked around the firewall rule many had used in place of a patch (we covered it here).
For the leftover-state flaw (CVE-2026-109619):
- Turn off session upload progress. Set
session.upload_progress.enabled=0inphp.ini, the FPM pool or the server configuration, then restart every worker. Setting it withini_set()or a.user.inifile does not work, because those apply too late. This is the path Hadrian says most attacks are using. Few sites use the feature, and PHP's advisory suggests leaving it off even after patching if you don't need it. - On PHP 8.5, set
fatal_error_backtraces=0and restart every worker. This closes the confirmed fatal-error backtrace path, and nothing else. - Remove deprecated or invalid PHP settings applied per request, such as
php_valuelines in.htaccessfiles orPHP_VALUEpassed from nginx, that make PHP emit a warning before it has set up the request. Hiding the warning withdisplay_errors,error_reportingor@does not help. - Fix fatal errors and timeouts an attacker can trigger in your application, including in upload, import and clean-up code.
- If performance allows,
pm.max_requests=1in PHP-FPM retires each worker after a single request, so leftovers from one request have no next request to poison. It is expensive, and PHP lists it as a way to reduce risk, not remove it.
For the memory flaw (CVE-2026-109618):
- Remove the per-request memory cap, carefully. PHP's advice is to set
memory_limit=-1in a way applications can't override (php_admin_valuein the FPM pool or Apache configuration) and to check that nothing sets a finite limit back. That removes the predictable out-of-memory failure the confirmed exploit relies on, but it also removes PHP's own guard against runaway scripts, so pair it with a container or cgroup memory limit that kills only the affected worker. PHP specifically warns against usingRLIMIT_ASorRLIMIT_DATAfor this, because they cause exactly the failure the attack needs. - Keep request bodies small:
post_max_size = 8Mif the site takes no file uploads,32Mif it does, withupload_max_filesizebelow it. - Enforce limits in front of PHP: total request size, number of form fields, multipart parts and headers, including on requests that don't declare their length. If an application never accepts multipart uploads (a JSON-only API, say), block them at the web server or proxy.
How to tell if you were already hit#
Patching closes the door; it doesn't evict anyone who already came in. Amazon recommends starting investigations from 17 October, the date of the earliest attempt it saw, so check any server that ran unpatched PHP since then, especially Apache with mod_php:
- PHP starting things it shouldn't. The clearest sign, per Huntress, is a PHP or web server process (
php-fpm,php-cgi,httpd,apache2) launching shells, interpreters or download tools:sh,bash,python,perl,curl,wgetornc, or on Windowscmd.exe,powershell.exeandcertutil.exe. Reverse-shell patterns such as/dev/tcp/,mkfifoorfsockopenin a command line, and quick reconnaissance likewhoami,idoruname -arun by the web server, deserve an immediate look. The FBI's advice is the same: make sure something is watching for PHP processes that launch shells or download files. - Files you didn't put there: unfamiliar
.phpfiles in the web root or upload folders, and new cron jobs, scheduled tasks or user accounts. - Crashes and memory errors in the logs. Both flaws are memory-corruption bugs, and attempts that miss tend to crash the worker. Look for bursts of
exit signal Segmentation faultin Apache's error log,exited on signal 11in PHP-FPM's, orAllowed memory size offatal errors, especially on multipart POST requests to pages that don't normally take uploads. CrowdStrike says a single memory-limit fatal error in the web server's log may be the only trace an attack leaves (The Hacker News), and PHP notes that a failure can happen before anything is written at all. Clean logs are not proof of safety. - Scanner traffic. A user-agent of
php2shell-scan/109618in your access logs means you were probed, not that you were breached, but it tells you when to start looking.
If you find something, take the server off the network, keep the logs, and assume everything the web server could read is now in someone else's hands: database passwords, API keys, cloud credentials and session secrets. Rotate them. Those are the machine identities nobody watches, and a stolen one keeps working long after the server is cleaned.
How the two flaws work#
You don't need this section to fix the problem, but it explains why the advice above looks the way it does.
The memory flaw (CVE-2026-109618)#
When PHP runs out of memory in the middle of a request, it abandons what it was doing with a hard jump straight out of the current code. At most points that is harmless. At a few, PHP is halfway through setting up internal request data, and the jump leaves it half-built. The clean-up that runs at the end of every request then treats the half-built data as real, and with the right preparation an attacker can steer that into running their own code.
The attacker's job is to make PHP run out of memory at exactly that moment, and PHP's advisory lists several ways to do it with a single upload request while staying under PHP's own size limits: a form part with an enormous number of headers, a request that doesn't declare its length, or empty file fields, which don't count towards the upload limit but still cost memory. With Apache's mod_php, all of this happens while PHP is reading the request, before the site's own code runs at all. That is why it doesn't matter which application you run, and why a security plugin can't help: plugins are application code, and the attack is over before they load.
The fix stops PHP from making that jump on out-of-memory errors. A script that blows its limit is now stopped at the next safe point instead, with an immediate hard exit if memory keeps climbing past a second, higher threshold.
The leftovers flaw (CVE-2026-109619)#
A PHP worker process handles many requests, one after another. Some of its internal bookkeeping, such as error details, session state and extension state, points into memory that belongs to the current request, and it has to be reset before that memory is thrown away. When a request dies in the wrong place, from a fatal error, a timeout or running out of memory, the reset can be skipped. The memory is freed, but the pointer to it survives. If the next request on the same worker touches that stale pointer before PHP has finished setting up, it is working with freed memory, which an attacker can arrange to fill with something of their choosing.
So an attack takes two requests: one to leave poisoned leftovers behind, one to trip over them. PHP confirmed two ways of doing the tripping. One is a warning raised by a deprecated or invalid PHP setting applied per request, which fires before setup is complete. The other is session upload progress, a little-used feature that is on by default and runs before the session system initialises, without your application ever calling session_start(). Running many workers lowers the odds that both requests land on the same one, but that only means an attacker needs more attempts.
Why 9.2 and not 10#
Both flaws score 9.2 under CVSS 4.0: reachable over the network, no login, no user interaction, and the attacker's code runs with the web server's own privileges. What keeps them off 10 is that the attack is not a single magic request. It depends on the PHP build, the extensions loaded and how memory happens to be laid out, and it can take many attempts.
That is a speed bump, not a wall. Wiz says proving the flaws exploitable took a chain of steps "at the edge of what human researchers could practically achieve", and that frontier AI models "substantially reduced that barrier". PHP's own team demonstrated full code execution over HTTP against Apache with mod_php using PHP's default memory and input limits.
What to watch next#
- The first confirmed break-ins. The reports so far describe attempts. At this volume that will change, and the first incident reports will show what attackers do once they are in.
- The long tail. Security researcher Hal Pomeranz warned of "a long tail" of unpatched embedded and forgotten installations (Help Net Security). Shared hosting builds, arm64 packages, appliances and desktop development tools that bundle PHP are where unpatched copies will linger.
- How PHP changes after this. The PHP Foundation says its security team will make decisions by majority and bring in project leadership earlier when a fix stalls. It is also asking the companies, hosts and governments that run PHP to help fund the work.
Summary#
- Update now to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your vendor's patched package, then restart every PHP-FPM pool and Apache. Attacks are already widespread.
- Verify, don't assume. Compare the installed package version with your vendor's advisory, because distribution builds can keep an old version number.
- Know whose job it is. On hosting, confirm with your host; in containers, pull the fixed image and rebuild; for bundled PHP, ask the vendor.
- On PHP 8.1 or older, get a vendor backport, apply PHP's source patches or upgrade, and keep the site off the open internet until then.
- If you can't patch today, use PHP's stopgaps and a web application firewall to buy hours, not as a fix. Turning off session upload progress closes the path Hadrian says most attacks are using.
- If you ran unpatched PHP since 17 October, look for PHP starting shells, unfamiliar files, memory-limit errors and worker crashes. If you find any, isolate the server and rotate every secret it could read.
Running PHP on servers you manage, or not sure where it runs in your business? Get in touch - bluwarden can help you find your internet-facing PHP, confirm the fix is actually in place, and check for signs of compromise. See what our services cover.
First published on 21 October 2026, the day the fixes shipped, and updated on 22 October 2026 with exploitation reports, government responses and platform patch status as of that morning. The situation is moving quickly, and details may change.