PHP2Shell: two critical vulnerabilities in PHP, and what to do today
· Updated · 31 min read · 6,183 words · 73 sources
On Wednesday 21 October 2026 the PHP security team released PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. The announcements went online around 13:00 UTC, an hour earlier than scheduled. These versions fix two critical vulnerabilities, both rated CVSS v4.0 9.2, that let an attacker run code on your server without logging in. The news calls them PHP2Shell. If your website, web shop or application runs on PHP, update today, not next week.
Addendum 22 October: is the patch complete?
Security researchers are taking Wednesday's patches apart, and two of them claim new problems, Tweakers reports. My conclusion: still update to the versions below, because they fix the two CVEs. Just don't count on this being the last PHP update this month.
- A working attack is now public. Researcher Nicolas Krassas published an unauthenticated attack on PHP 8.5.11 on GitHub, tested in a lab setup with non-default settings, such as
display_errorson and a single Apache worker. He notes himself that this doesn't prove the attack works on every installation. If you haven't patched yet, your risk is higher now than it was on Wednesday. - On Wednesday evening Krassas wrote on X that the patch in 8.5.12 is "incomplete", adding that this "needs further validation". In his research both CVE attacks no longer work on 8.5.12, but three other routes remain. The first is a memory bug in
unserialize()that he already published in May. The second only works when an application reads sessions with two differentsession.serialize_handlersettings. In the third, attacker data ends up verbatim in session files through upload progress, and that is only usable if your application also has a flaw that lets an attacker include a file. Krassas did not coordinate this with the PHP security team. People are debating whether his claim holds up on Reddit and LinkedIn. - Researcher Elise Amber Katze found a third bug on Wednesday, a so-called double free, which she says also works on 8.5.12. She reported it to PHP. PHP core developer Ilija Tovilo calls it a different memory bug that is "in no way urgent", according to Tweakers. Canonical asked PHP about it and was told it is "unrelated and not a real issue", Aaron Rainbolt wrote on the oss-security mailing list. He had that secondhand and asked PHP to confirm it.
Krassas' research also weakens two of the temporary measures further down:
- With tens of thousands of short headers he makes an unpatched PHP use about 52 times as much memory as the request is large. A request that fits within an 8M
post_max_sizecan still exhaust memory that way. According to him the patch closes this route. - PHP accepts
Content-Type: multipart/form-data, boundary=...with a comma where the usual semicolon goes. WAF rules that look for the semicolon miss such a request.
What I recommend myself, on top of patching: set session.upload_progress.enabled=0 and keep it that way after the update, if your application doesn't show upload progress. The setting is on by default. That removes Krassas' third route, and most sites won't notice. Also use a single session.serialize_handler for the whole application, set in php.ini or the FPM pool, not per directory or per script.
How big is this?
PHP sits underneath WordPress, WooCommerce, Joomla, Drupal, TYPO3, Magento, Shopware and PrestaShop, and underneath frameworks such as Laravel, Symfony, CodeIgniter and CakePHP. The flaw is in PHP itself, so every one of those platforms is affected, however well the application on top is built. According to Tweakers, a proof-of-concept video that circulated showed an attacker bypassing the login of Joomla and WordPress. Security firm Calif reported the first flaw on 11 August, and OpenAI independently reported the same root cause that day. Wiz reported further attack vectors on 24 August, according to the PHP Foundation. Wiz, which used AI tools, told Reuters they went unnoticed "for more than ten years". The PHP Foundation admits the fix took months, and calls that delay "a structural failure".
In the Netherlands, dozens of well-known sites and services went dark as a precaution on Wednesday. According to AD, NOS, NU.nl and ANP, they included:
- DigiD, MijnOverheid, the Dutch House of Representatives, parts of UWV and even the site of the NCSC, the national cyber security centre
- at least 13 of the 25 safety regions, the Dutch Water Authorities and NotuBiz, which hosts council information for over 100 municipalities, provinces and water boards
- hospitals: the sites of Jeroen Bosch Ziekenhuis, Haaglanden MC and St. Antonius, among others, were unreachable, and Bravis's site said it had put its systems into maintenance mode as a precaution "because of the PHP2Shell vulnerability"
- Odido brands Ben and Simpel, Eneco subsidiary Agro Energy, the Port of Rotterdam, the Schiphol app, supermarket chain Hoogvliet and parts of the SURF education network
- Maastricht UMC+, Thuisarts.nl, Medimo, which home care and nursing homes use to dispense medication, and Stichting Ambulance Wens, a charity that fulfils last wishes, which lost access to its planning for the next day
- RTL Nieuws, DPG Media titles such as Tweakers, AutoWeek and vtwonen, Pathé Thuis and the upload system of NPO Start and NPO Luister. Tweakers itself said it had taken its systems offline as a precaution "because of critical PHP2Shell vulnerabilities".
- the Dutch motoring association ANWB, theme park Duinrell, TicketSwap and childcare organisations such as Humankind, KION and Basker
- the Dutch football association KNVB, charities such as KWF and Amnesty, and the sites of political parties including VVD, CDA, BBB and the Party for the Animals
- hosting provider Mijndomein, which took hundreds of thousands of websites offline at 13:35, including its Managed WordPress and Managed WooCommerce plans
By Wednesday evening many sites were back online. According to NOS, Tweakers and RTL returned around 15:00 UTC, MijnOverheid around 16:10 UTC and DigiD around 18:30 UTC.
Sites outside the Netherlands went down too. Flemish minister-president Matthias Diependaele had all Flemish government websites taken offline, and sites went dark in Finland, Japan and France as well. NU.nl and AD themselves stayed online: their news sites don't run on PHP.
Attack attempts are already happening. On Wednesday morning at 07:43 UTC, Cloudflare CTO Dane Knecht wrote: "We are seeing early attacks emerging and are updating mitigations as new vectors show up." Whether anyone has actually been breached is not confirmed. The PHP Foundation told ANP it has no indication of active exploitation, and on Wednesday the NCSC had not seen active exploitation either. According to NOS, the US agency CISA sees no active exploitation, and on 22 October the flaws were not in CISA's catalog of known exploited vulnerabilities. The NCSC did warn of a very high risk of a complete system takeover. NCSC director Corine Schipper-Derkse told AD it was "not an attack" but a design flaw whose potential danger is "very large".
What is going on
- CVE-2026-109618, RCE via OOM corruption. If PHP runs out of memory while internal request data is only partially initialised, request cleanup may treat that invalid data as valid.
- CVE-2026-109619, RCE via stale global state. Request-owned internal state can survive after its backing memory has been freed. A later request on the same worker may use that stale state before normal initialisation resets it.
Every PHP installation that serves web requests is affected: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The risk is highest for mod_php. There, a working exploit triggers before a single line of application code runs.
Which PHP version to move to
- PHP 8.5 to 8.5.12
- PHP 8.4 to 8.4.27
- PHP 8.3 to 8.3.36
- PHP 8.2 to 8.2.35
Keep in mind that another update may follow. According to NU.nl, the NCSC doesn't rule out a new update later if the first one doesn't solve every problem.
Check what runs on every server and in every container with php -v. Include CI runners, cron servers and managed hosting. Restart PHP-FPM or your web server after the update, if your package manager didn't do it for you. php -v already shows the new version at that point, while the running workers still use the old code. If you are picking a new version now, take 8.3 or later: PHP 8.2 reaches end of life on 31 December 2026.
PHP 8.1 and older get no release. The security team did publish minimal source backports for 7.4, 8.0 and 8.1, for people who build PHP themselves. Further down you'll find what else you can do if you are stuck on an old version.
Upgrade path per Linux distribution
Linux distributions received the patches in advance. Most now ship fixes for their supported PHP versions, but older Alpine releases, retired RHEL and Rocky streams and Surý's ARM builds still lagged. Below is the status as of Thursday 22 October around 08:45 UTC. Check your distribution's security tracker for the current status.
Ubuntu
- 26.04 ships PHP 8.5, 24.04 ships PHP 8.3 and 22.04 ships PHP 8.1. Ubuntu patches those versions itself. At 13:56 UTC it published USN-8948-1 with the fixed versions: 8.1.2-1ubuntu2.28 for 22.04, 8.3.6-0ubuntu0.24.04.13 for 24.04, 8.5.4-0ubuntu1.5 for 26.04 and 8.5.9-0ubuntu2.1 for 26.10. Ubuntu's security archive now serves those versions. Check with
apt-cache policy php8.3-fpm(adjust the version) that you have them, and withapt changelog php8.3-cli | grep 109618that the fix is in. - 20.04 (PHP 7.4) and 18.04 (PHP 7.2) only get updates through Ubuntu Pro. Personal use is free for up to five machines. The fix is in USN-8948-2, which also covers 16.04 and 14.04 through the paid Legacy add-on.
- If you use Ondřej Surý's packages,
packages.sury.organd theppa:ondrej/phpPPA (22.04 and 24.04 only) carry PHP 8.2.35 to 8.5.12 on amd64. On ARM only the PPA has them. Further down: why you shouldn't addpackages.sury.orgthere now. - Upgrade path: move to 24.04 (PHP 8.3) or 26.04 (PHP 8.5).
Debian
- 13 trixie ships PHP 8.4, 12 bookworm ships PHP 8.2 and 11 bullseye ships PHP 7.4.
- Debian 12 has been in LTS since June 2026. Debian 11 left free LTS on 31 August 2026. After that, only Freexian's paid ELTS remains, as it does for Debian 10.
- Debian 13: php8.4 8.4.27-1~deb13u1 in
trixie-security(DSA-6574-1). Debian 12: php8.2 8.2.35-1~deb12u1 inbookworm-security(DLA-4848-1). Debian 11: php7.4 7.4.33-1+deb11u14 through Freexian ELTS (ELA-1853-1).packages.sury.orgcarries 8.2.35 to 8.5.12 for Debian 11, 12 and 13, but not yet on ARM (arm64). - Upgrade path: move to Debian 13 (PHP 8.4). Debian 12 gives you PHP 8.2, which PHP itself stops supporting at the end of this year.
CentOS, RHEL, AlmaLinux and Rocky Linux
- CentOS 7 has been end of life since 30 June 2024, CentOS Stream 8 since 31 May 2024 and CentOS Linux 8 since the end of 2021. No update is coming for those.
- RHEL, AlmaLinux and Rocky 8 offer PHP 7.4 and 8.2, version 9 offers 8.0 by default with 8.2 and 8.3 as streams, and version 10 offers 8.3 and 8.4.
- Red Hat released RHSAs on 21 October between 15:53 and 20:36 UTC for RHEL 8 (php:7.4 and php:8.2), 9 (php, php:8.2 and php:8.3) and 10 (php and php8.4). Rocky Linux followed overnight. Only PHP 8.2 on Rocky 8 was still missing on Thursday morning. AlmaLinux shipped its own patched packages the same day for every PHP stream on 8, 9 and 10, including old streams Red Hat no longer supports. Some keep their old version number, so check with
rpm -q --changelog php-common | grep 109618(for PHP 8.4 on version 10:php8.4-common). Remi's repository (rpms.remirepo.net) has 8.2.35 to 8.5.12 for versions 8, 9 and 10. The advisory first said Remi would get no update that day, but PHP removed that sentence at 14:18 UTC. - Upgrade path from CentOS 7: in place to AlmaLinux 8 with ELevate and onward from there, or a clean install of AlmaLinux or Rocky 9 or 10 (PHP 8.3 or 8.4). For an application server, the clean install is usually the calmer route.
Docker, FrankenPHP and hosting panels
- Since the night of 21 to 22 October, the official Docker images have tags for 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Rebuild with
docker build --pulland restart your containers. - FrankenPHP ships PHP inside its image or binary, so there you need a new FrankenPHP build, and that exists: FrankenPHP 1.13.2 from 21 October.
- Plesk (PHP 8.2 to 8.5), cPanel (EasyApache 4 version 25.91) and CloudLinux (alt-php and ea-php) shipped patched packages on 21 and 22 October. Run your panel's update and restart PHP-FPM and Apache. On Wednesday Mijndomein told AD it was waiting for Plesk's packages.
How to patch today, with and without a package manager
Status as of Thursday 22 October around 08:45 UTC. The examples use PHP 8.4. Replace 8.4 with your version.
Debian and Ubuntu (apt): via the PPA or packages.sury.org
Ondřej Surý distributes PHP through two sources: the well-known PPA (ppa:ondrej/php, Ubuntu 22.04 and 24.04 only) and his own package source, packages.sury.org. According to the PPA itself, it is being merged into packages.sury.org. On regular servers (amd64) both sources have PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12. If you already use the PPA, sudo apt-get update && sudo apt-get upgrade is enough.
On ARM servers (arm64), only the PPA has the patched versions right now. packages.sury.org still serves 8.2.34, 8.3.35, 8.4.26 and 8.5.11 there. So don't add packages.sury.org on ARM now. If you run Ubuntu's own PHP there, it even makes you more vulnerable. Ubuntu has already patched its PHP, but the unpatched build on packages.sury.org has a higher version number, so apt swaps the safe version for the vulnerable one. On a test server with Ubuntu 24.04 (arm64), the patched 8.3.6-0ubuntu0.24.04.13 was replaced by 8.3.35 this way.
If you switch to packages.sury.org on amd64, use one of the two sources, not both. Otherwise apt picks, per package, the source with the highest version number, and that may well be the other one. If you have the PPA, remove it first:
sudo add-apt-repository --remove ppa:ondrej/php
Then add packages.sury.org the way the source's README prescribes, and upgrade only the PHP packages you already have:
sudo apt-get update
sudo apt-get -y install lsb-release ca-certificates curl
sudo curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
sudo dpkg -i /tmp/debsuryorg-archive-keyring.deb
echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list
sudo apt-get update
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev}${binary:Package}\n' 'php8.4*' | awk '$1=="ii"{print $2}')
sudo systemctl restart php8.4-fpm
The dpkg-query line picks exactly the PHP packages that are installed. A wildcard such as 'php8.4-*' passed straight to apt-get breaks on virtual package names. If you use mod_php, restart Apache instead of PHP-FPM. Then check with php -v and apt-cache policy php8.4-fpm which version runs and which source it came from. I tested these steps on a clean Ubuntu 24.04 (amd64): PHP 8.3.6 went to 8.3.36.
RHEL, AlmaLinux and Rocky Linux (dnf/yum)
Red Hat, AlmaLinux and Rocky have patched packages for their supported streams, see above. Remi has all four fixed versions, 8.2.35, 8.3.36, 8.4.27 and 8.5.12, for versions 8, 9 and 10. If you already use Remi, sudo dnf upgrade 'php*' is enough. To add Remi, follow Remi's configuration wizard for your version. If you only use your distribution's packages, run sudo dnf upgrade 'php*' as well, and then check with rpm -q --changelog php-common | grep 109618 that the fix is in. On version 10 with PHP 8.4, that package is called php8.4-common. Don't rely on php -v here. Red Hat and AlmaLinux sometimes put the fix into an existing version, so you may still see 8.0.30 or 8.2.33. CentOS 7 with yum gets no update at all.
Alpine (apk)
Alpine 3.24 and edge now have php83 8.3.36, php84 8.4.27 and php85 8.5.12. On 3.23 only php83 had been updated on 22 October, not php84 or php85. 3.22 and older got nothing. Run apk update && apk upgrade and check with php84 -v.
Docker
Since the night of 21 to 22 October, the official php images have tags for 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Tags such as 8.4-fpm point to them now too. Pull the new images, rebuild your own images on the new base and restart the containers:
docker compose pull --ignore-buildable
docker compose build --pull
docker compose up -d
docker compose exec app php -v
Replace app with the name of your PHP service. The first line is needed for services that use a php image directly, because build --pull skips them.
Building from source
The source of all four patched versions is on php.net, with checksums and signatures. This is the route if you have no updated package and can't wait. Reuse the configure options of your current build, or you'll lose extensions:
php -i | grep 'Configure Command'
curl -fsSLO https://www.php.net/distributions/php-8.4.27.tar.xz
echo "dcf93923b0e38e0d5252b25b796520b5a47761a08b3488b6d0b5d1115764d3f0 php-8.4.27.tar.xz" | sha256sum -c
tar xf php-8.4.27.tar.xz && cd php-8.4.27
./configure # with the options from the first line
make -j"$(nproc)"
sudo make install
For PHP 7.4, 8.0 and 8.1, build from the security team's backport branch, for example for 8.1:
git clone --depth 1 --branch PHP-8.1-CVE-2026-109618-109619 https://github.com/php/php-src.git
cd php-src && ./buildconf --force
Then continue as above with ./configure, make and make install. A build from git also needs autoconf, bison and re2c, and still reports itself as 8.1.34 afterwards. So php -v won't show the fix. A build of your own installs under /usr/local by default, next to your distribution's PHP. Check which binary your web server and PHP-FPM actually use afterwards.
FrankenPHP
FrankenPHP 1.13.2 came out on Wednesday at 18:19 UTC. Its Docker images are built with PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and its static binaries with PHP 8.5.12. The release also fixes four vulnerabilities in FrankenPHP itself. Update to 1.13.2, and read the upgrade notes in the release on GitHub if you use the static builds. There is no binary for Intel Macs any more, and the -gnu binary now needs glibc 2.28 or newer.
Don't forget your extensions (PECL and PIE)
The two flaws are in PHP itself, but extensions play a part. According to the advisory for CVE-2026-109619, a worker also retains "extension state" between requests, and whether an attack works depends on, among other things, "the callbacks registered by that application or extension". The advisory for CVE-2026-109618 says the exploit depends on "the PHP build, extensions, frontend behavior and allocator layout". Neither advisory names a flaw in a specific extension.
While you're at it, this is the moment to go through your extensions. They have their own releases and security fixes, separate from PHP. Think of redis, imagick, apcu, xdebug or swoole. Extensions that wrap a C library, such as imagick with ImageMagick, partly get their fixes through that library.
What to do depends on your situation:
- You stay on the same minor version, for example 8.4.26 to 8.4.27. Your extensions keep working, because a patch release doesn't change PHP's internal API.
- You move to another minor version, for example 8.1 to 8.3, or you build PHP yourself. Then you need to reinstall every extension for that version. Forget one and PHP runs without it, which you only notice when your application breaks on it. Compare
php -mbefore and after, and search the PHP-FPM log for "Unable to load dynamic library". - Extensions you installed through PECL or PIE are yours to update. PECL is officially deprecated. Its successor is PIE from the PHP Foundation.
php -m # which extensions load right now
pecl list # what was installed through PECL
pecl list-upgrades # which of those have a newer version
pie show # what was installed through PIE
If you use PIE to install an extension for another PHP version on the same machine, pass that version with --with-php-config, for example pie install --with-php-config=/usr/bin/php-config8.3 vendor/extension. Run pie install without arguments in your project directory and PIE checks whether every extension your composer.json requires is present. In Docker, extensions your Dockerfile adds with pecl install or docker-php-ext-install are rebuilt once you build with --pull on a new base image.
What this means for WordPress, Magento, Laravel and the rest
The question is not whether your platform is affected, because it is. The question is how easily you can move to a patched PHP version. That depends on your platform version.
Usually an afternoon or a day of work, if your platform is up to date:
- WordPress 6.9 and later run on PHP 8.2 to 8.5, and WooCommerce recommends PHP 8.3 or later. The risk lies in old plugins and themes that can't handle PHP 8.
- Laravel 12 (PHP 8.2 to 8.5) and Laravel 13 (8.3 to 8.5).
- Symfony 7.4 LTS (8.2 and later) and Symfony 8.1 (8.4 and later).
- Drupal 11 (8.3 and later, 8.5 from 11.3) and Drupal 10.4 to 10.6 (8.1 to 8.4). Drupal 10 reaches end of life on 9 December 2026.
- Joomla 5 (8.1 and later) and Joomla 6 (8.3 and later).
- TYPO3 13 and 14 (8.2 to 8.5).
- Magento 2.4.6 (8.1 and 8.2), 2.4.7 (8.2 and 8.3), 2.4.8 (8.3 and 8.4) and 2.4.9 (8.5), OpenMage (8.1 to 8.5), Shopware 6.6 and 6.7 (8.2 to 8.4) and PrestaShop 9.
- CodeIgniter 4.7, CakePHP 5, Craft CMS 5 and Nextcloud 33 to 35.
Often a project, because moving to PHP 8.2 or later first takes an upgrade or migration of the platform:
- Magento 1 has been end of life since June 2020. The way forward is OpenMage or a new platform.
- Magento 2.3 and 2.4.0 to 2.4.3 are end of life and run on PHP 7. Magento 2.4.4 and 2.4.5 only run on PHP 8.1. So Magento needs upgrading first.
- PrestaShop 1.7 goes no further than PHP 7.4, and PrestaShop 8 no further than 8.1.
- Drupal 7, Joomla 3 and 4, CakePHP 3, Yii 1.1, Zend Framework 1, Craft 4 and Laravel 11 and older are end of life. CodeIgniter 3 has no official support for PHP 8.2 or later either.
Let me be honest about one thing. You don't move a Magento shop from PHP 5.6 or 7.4 to PHP 8.4 in a week. Extensions, custom code and integrations move along with it, and that is a migration, not an update. Anyone who promises that in a few days is promising miracles.
Stuck on PHP 5.6, 7.x, 8.0 or 8.1? How to buy time
If you can't upgrade right away, there are ways to bridge the gap until the migration is done. Status as of Thursday 22 October, around 08:45 UTC:
- Build PHP yourself from the PHP security team's source backports for 7.4, 8.0 and 8.1. There are none for 5.6 or 7.0 to 7.3.
- Ondřej Surý also maintains old PHP versions for Debian and Ubuntu, and says the fix covers every version in his repository. On 22 October, Ubuntu 24.04 (amd64) had builds dated 21 October for every version from 5.6 to 8.1. Check with
apt-cache policy php7.4-fpm(adjust the version) that you have at least this package revision: 5.6.40-104, 7.0.33-92, 7.1.33-80, 7.2.34-66, 7.3.33-35, 7.4.33-31, 8.0.30-25 or 8.1.34-9. The package itself doesn't name the CVEs, so here you rely on his word. - Ubuntu Pro covers PHP 7.2 and 7.4 on Ubuntu 18.04 and 20.04, and Freexian ELTS covers Debian 10 and 11. Ubuntu Pro shipped the fix in USN-8948-2, for 16.04 and 14.04 too through the paid Legacy add-on. On 21 October Freexian shipped updates for php7.0 (Debian 9), php7.3 (Debian 10) and php7.4 (Debian 11).
- CloudLinux's HardenedPHP provides maintained builds of old PHP versions, from the 5.x line up to 8.5, through CloudLinux OS or a TuxCare licence, and often through your hosting provider. On 22 October CloudLinux reported fixed alt-php packages for every version from 5.2 to 8.5 in stable, and updated ea-php packages from CloudLinux for cPanel as well.
- TuxCare Extended Lifecycle Support for PHP covers PHP 5.2 and later on AlmaLinux, CentOS, Ubuntu and Debian, among others, and promises to patch critical flaws within 14 days. According to CloudLinux, fixed alt-php packages were in TuxCare's ELS repository even earlier. TuxCare's CVE tracker shows the status per package.
- Zend PHP LTS covers PHP 7.2, 7.3, 7.4 and 8.1. On 22 October I found no announcement there yet.
- Backport and build it yourself, when no vendor covers your version. Start from the security team's backports for 7.4 to 8.1, or from distribution patches that name the CVEs, such as Ubuntu's, Freexian's and AlmaLinux's. With help from AI you find the right places in old code much faster, but this changes memory management in the core of PHP. A backport that looks right can still leave the hole open. Have someone who knows the engine review it, run the fix's regression tests and
make test, and test your application before the build goes live. Every later fix for that build is then your job too.
As soon as one of these vendors ships a fix, it is a real way to bridge the gap. It buys time, nothing more. PHP itself is then patched, but the application stays old, with its own known flaws.
If you cannot update today
In its release announcements, PHP is clear about this: "There are no complete mitigations, updating is the only safe choice." A PHP core developer said as much in the night before the release:
"The only thing I can say is that the only mitigation is patching / upgrading PHP. I sadly know too much."
Gina Peter Banyard, PHP core developer, on Mastodon, 21 October 02:13 UTC
Many of the measures that went around on Wednesday morning, such as only disabling upload progress, cover just one of the two flaws. The measures in the next two blocks come from the official advisory. They reduce exposure to specific attack paths, but they do not make an installation safe.
Against CVE-2026-109618 (OOM corruption)
- Set
memory_limit=-1, preferably in a way applications can't override, such asphp_admin_valuein the FPM pool or Apache configuration. Check that no pool, directory or application setting puts a finite limit back. This removes the PHP memory limit failure an attacker can control, but an underlying allocation failure can still reach the vulnerable path. - Set a low
post_max_size: 8M for applications that don't need file uploads, 32M if they do. Keepupload_max_filesizebelowpost_max_size. Consider limits at the web server or proxy for decoded request size, input field count, multipart part count, header count and nesting depth. These limits must also cover requests without a usableContent-Length. Don't lean on this measure too hard: as the addendum at the top explains, a request that fits within 8M can still exhaust memory. - Use a container or cgroup limit as an availability boundary, preferably with a policy that kills only the affected worker. Do not substitute
RLIMIT_ASorRLIMIT_DATA, as these can cause the vulnerable allocation failure. - If your site doesn't use multipart forms, the format browsers use for file uploads among other things, block those requests at the web server or proxy before they reach PHP. If part of the site does need uploads, limit the block to the other paths.
This is how you block multipart in nginx, in your site's server block:
if ($http_content_type ~* "multipart/form-data") {
return 403;
}
And this is how in Apache, in your site's <Directory>:
<If "%{HTTP:Content-Type} =~ m#multipart/form-data#i">
Require all denied
</If>
I tested both rules. They match multipart/form-data without the semicolon, so they also block the comma variant from the addendum. Apache rules using %{CONTENT_TYPE} are going around as well. Those block nothing, because in Apache that is the type of the response, not of the request.
Against CVE-2026-109619 (stale global state)
- Set
session.upload_progress.enabled=0and restart every worker. Usephp.ini, an FPM pool or server configuration that applies before request parsing. Do not rely onini_set()or an FPM.user.ini. This blocks the confirmed session paths, but not other variants. - On PHP 8.5, set
fatal_error_backtraces=0and restart every worker. This blocks the confirmed fatal backtrace path only. - Remove invalid or deprecated settings applied per request that emit diagnostics before initialisation. Hiding those diagnostics with
error_reporting,display_errorsor@does not help. - Fix attacker-reachable fatal errors and execution timeouts, including failures in callbacks, upload or import processing and cleanup.
My own advice: a WAF, or taking the site offline
This is not in the advisory, but it is what I recommend. A WAF such as Cloudflare's stops known exploits, but not every variant still to come. A way around known WAF rules has been published since, see the addendum at the top. Cloudflare says so itself:
"Cloudflare has protections in place for known exploit payloads. There will be more variations to come and the only way to fully protect your site is to update php once released."
Dane Knecht, CTO of Cloudflare, on X, 21 October 07:06 UTC
If you'd rather take a site offline for now, stop the web server or have it return a 503. Don't just disable the PHP handler: your web server may then serve .php files as plain text, passwords in your configuration included.
Patched? Then check whether someone got in first
An update closes the hole, but it doesn't clean up what an attacker did with it before. A webshell, a PHP file that lets someone run commands remotely, stays in your web root. So does an extra cron job, a new SSH key or a database password that has already been read. Yorick R. published an extensive checklist on bufon.nl for checking this. These are the steps I would take first.
Search wide, from 20 October, a day before the release. Read logs in a central place if you have one, such as a syslog server or your hosting panel. Whoever has a shell on your server can edit the local logs. Look for:
- POST requests that ended in a server error, especially to pages without uploads, such as your home page or login page. An attack often makes PHP run out of memory or crash.
- PHP workers that crashed. Healthy PHP doesn't crash, so every segfault since 20 October needs an explanation.
- PHP files that changed or appeared since 20 October, especially in upload, cache and temporary folders. Also look for
auto_prepend_filein.user.iniand.htaccess. With it, an attacker loads code before every page, and it keeps working after you delete the webshell itself. - Processes of the web user, such as
www-data. That user runs PHP, but never starts a shell,curl,wgetornc. - Ways to come back, such as cron jobs, systemd timers, SSH keys, new users and sudo rights. In your application, also look for new administrators, new API tokens and a modified checkout. On a web shop, a skimmer, code that captures payment details, is often the real goal.
For Debian and Ubuntu with nginx:
# POST requests that ended in a server error
zgrep -hE '"POST [^"]*" 50[0-4] ' /var/log/nginx/access.log*
# Crashed PHP workers
zgrep -hE 'SIGSEGV|signal 11' /var/log/php*-fpm.log*
journalctl -k --since 2026-10-20 | grep -i segfault
# PHP files changed since 20 October
find /var/www /tmp /var/tmp /dev/shm -type f \
\( -name '*.php*' -o -name '*.phtml' -o -name '*.phar' -o -name '.htaccess' -o -name '.user.ini' \) \
-newermt 2026-10-20 -ls
grep -rnE 'auto_(prepend|append)_file' /var/www --include=.user.ini --include=.htaccess
# What the web user is running
ps -u www-data -o pid,ppid,lstart,cmd
# Ways to come back
crontab -l -u www-data
find /etc/cron* /etc/systemd /etc/sudoers.d -newermt 2026-10-20 -ls
find / -name authorized_keys -newermt 2026-10-20 -ls 2>/dev/null
On Apache, look in /var/log/apache2/access.log* and search error.log* for Segmentation fault. Your own deploys and the PHP update itself also show up in these lists. Anything you didn't change yourself needs an explanation. An attacker can reset a file's modification time, so also compare against a known good state: wp core verify-checksums and wp plugin verify-checksums --all for WordPress, debsums -c or rpm -Va for packages, git status in the web root if you deploy with git, and docker diff for containers.
If you find something, or can't rule it out, replace the secrets PHP could read, from a clean machine. Whoever runs code as the web user can read your configuration. Think of database passwords from wp-config.php or .env, API keys for payment, mail and storage services, and the secret key your application uses to sign or encrypt cookies, among other things. In WordPress those are the salts in wp-config.php, in Symfony it's APP_SECRET and in Laravel APP_KEY. Laravel also encrypts data with that key. From Laravel 11 on, put the old key in APP_PREVIOUS_KEYS, or you can no longer read that data.
If you find real traces, it's no longer a patching job but an incident:
- Take the server off the network, but don't clean anything up yet. If you only delete the webshell you found, you warn the attacker and leave the webshells you didn't find.
- Snapshot the disk first and copy the logs off the server.
- Build a clean server with patched PHP, deploy your code again from your repository and restore only your data. Check that data, such as uploads, for PHP files too.
- If personal data may have leaked, the GDPR generally requires you to notify your data protection authority within 72 hours of discovering it. In the Netherlands that is the Autoriteit Persoonsgegevens.
Don't forget the PHP outside your websites
The websites themselves will get patched. Hosting providers are on it and container images are being rebuilt. Yorick R. predicts that the hard part is PHP nobody thinks of as a PHP server, and I think he is right:
- phpMyAdmin, installed once for a quick fix and never removed, often on the same server as the database
- devices whose web interface runs on PHP, such as some NAS boxes, routers, firewalls, VPN portals, printers, camera systems and UPS management cards
- industrial systems, such as operator panels, SCADA portals and energy and building management systems
- the old time tracking tool, wiki or monitoring dashboard on a forgotten virtual machine
These systems don't get an apt upgrade. They wait for vendor firmware, if the vendor is still around, and sometimes run a PHP version that has no fix. A webshell on a NAS or on a portal next to your production machines also sits inside your network. That often makes them a more dangerous target than your website. If you can't patch them, control who can reach them:
- Take them off the internet. No port forwards, not even temporary ones.
- Put them behind a VPN, only for the people who need them.
- Firewall them off from the rest of the network. An industrial portal only needs to be reachable from the workstations that manage it.
- Ask your vendor in writing whether the product uses PHP and when a fix is coming.
- If nobody uses them anymore, switch them off. That old phpMyAdmin can go.
How I can help
I'm a freelance PHP developer, with Laravel and Symfony as my core and experience with Magento modules and upgrading older PHP applications, among other things. With flaws like these, I can help you in three ways.
- Today: map where PHP runs and which versions, patch within your current version, put the official measures in place properly and check that they are actually active.
- This week: pick and set up the right bridge for an old stack, from HardenedPHP, Ubuntu Pro or ELTS to, where none of those covers you, your own build in which I backport the fix with help from AI, check it against the upstream patch and test it.
- After that: an honest upgrade plan to a supported PHP and platform version, with what it costs and what it gets you. Sometimes migrating is cheaper than patching forever.
What I won't promise: that your Magento 2.3 shop on PHP 7.4 runs on PHP 8.4 by Friday. What I will promise: an honest answer about what can be done today and what becomes a project. Send me your PHP version, platform and hosting. Get in touch.
About the write-up that went around on Wednesday morning
Before the release, PHP-UP-2026 on jackalsyn.com was widely shared as the explanation of these CVEs. It described a use-after-free in session.upload_progress. The author later retracted that claim and kept a bug that supposedly runs no code and only affects Apache prefork with mod_php.
Whether the write-up describes the same bug is not confirmed. What is clear is that its reassuring conclusion does not hold for what PHP fixed today: every SAPI is affected and code execution is possible. The lesson for next time: during an embargo, treat a third party's "it's not that bad" as critically as an "it's terrible", and wait for the official advisory.
Written by AI, directed by Human
AI helped with the writing. The choices and the experience behind them are mine.
Sources
PHP and the PHP Foundation
- php.net: Security advisory, critical remote code execution vulnerabilities
- GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- PHP Foundation: Today's PHP security release and how we're strengthening PHP security
- PHP: supported versions
- php.net: downloads, checksums and signatures
- php/web-php: commits to the php.net website
- PIE: PHP Installer for Extensions, successor to PECL
News and agencies
- AD (Dutch): live blog on the PHP vulnerability
- AD (Dutch): hospitals, MijnOverheid and RTL, the sites offline over the PHP vulnerability
- NOS (Dutch): live blog, MijnOverheid and DigiD back online
- NU.nl (Dutch): live blog, important PHP security update is live
- Reformatorisch Dagblad / ANP (Dutch): websites offline over PHP warning
- Tweakers (Dutch): PHP warns of two serious bugs that look easy to exploit
- Tweakers: Security experts find third bug in PHP and claim the patch is incomplete (Dutch)
- NCSC-NL (Dutch, website notice): serious vulnerabilities in PHP: prepare to patch
- CISA: Known Exploited Vulnerabilities Catalog
- Dutch Data Protection Authority: this is how you report a data breach
Developers and researchers
- Gina Peter Banyard (PHP core developer) on Mastodon, 21 October 02:13 UTC
- Dane Knecht (CTO Cloudflare) on X, 21 October 07:43 UTC
- Dane Knecht (CTO Cloudflare) on X, 21 October 07:06 UTC
- Nicolas Krassas on X, 21 October 17:40 UTC
- Nicolas Krassas: php2shell lab on GitHub
- Elise Amber Katze on Mastodon, 21 October 14:32 UTC
- oss-security: Re: CVE-2026-109619 (Aaron Rainbolt, 21 October)
- Mattias Geniar: early mitigation steps
- Mattias Geniar: how to apply the patches
- Bufon (Yorick R.): PHP2Shell, patching is phase 1. Phase 2 and 3 are coming
- PHP-UP-2026 (corrected write-up)
Distributions, package sources and images
- Ubuntu: CVE-2026-109618
- Ubuntu: USN-8948-1, PHP vulnerabilities
- Ubuntu: USN-8948-2, PHP updates for Ubuntu Pro (14.04 to 20.04)
- Ubuntu: release cycle
- Debian security tracker: CVE-2026-109618
- Debian security tracker: php8.4
- Debian LTS
- Red Hat: CVE-2026-109618
- AlmaLinux: php2shell, patched PHP packages released
- AlmaLinux ELevate
- Remi's RPM repository: configuration wizard
- packages.sury.org: README for adding the PHP source
- deb.sury.org: issue 145 on CVE-2026-109618/109619
- Docker Hub: official php images
- Alpine Linux: php84 packages
- FrankenPHP: releases
- FrankenPHP 1.13.2
- Plesk Obsidian: change log
Extended support for old PHP versions
Platforms and frameworks
- WordPress: PHP compatibility and WordPress versions
- WooCommerce: updating PHP
- Adobe Commerce: system requirements
- Adobe Commerce: versions and support periods
- OpenMage: requirements
- Shopware 6.6: requirements
- PrestaShop 9: system requirements
- Drupal: PHP requirements
- Joomla: technical requirements
- TYPO3: versions and support
- Craft CMS: supported versions
- Nextcloud: maintenance and release schedule
- Laravel: release notes and support policy
- Symfony: releases
- CodeIgniter 4: server requirements
- CodeIgniter 3: server requirements
- CakePHP: versions and support
- Yii: release cycle
- Zend Framework 1: end of life