Daniele Montecchi
EN IT
Subscribe
All articles PHP

php2shell: The PHP Flaw AI Found and How to Patch It

php2shell is a pair of critical PHP flaws found partly with GPT-5.6 Sol. What it is, how to check if you're exposed, and how to patch PHP on any server.
php2shell: The PHP Flaw AI Found and How to Patch It

On Tuesday, October 20, someone posted on Hacker News that two critical PHP flaws would go public on Thursday. Their evidence was an honest "source: trust me, bro." By the next day Finland, the Netherlands and France had pulled some of their government websites offline, and the flaw had a name: php2shell.

An AI did a good part of the finding. According to OpenAI, the first report came out of research done with GPT-5.6 Sol, the model some headlines call "ChatGPT Sol."

PHP has had plenty of serious engine bugs over the years. This one stands apart because it works with the default configuration, however you run PHP, and because the first attacks showed up before the patch did. Below: what php2shell is, how to tell whether you're exposed, and how to patch PHP without forgetting the processes that keep running.

What php2shell Is (and Why It Affects Anyone Running PHP)

php2shell is the name for two vulnerabilities in the PHP engine, CVE-2026-109618 and CVE-2026-109619. The PHP team scores both 9.2 on CVSS 4.0, and the US National Vulnerability Database gives them 9.8 on CVSS 3.1. Either one lets an attacker with no credentials run code on your server.

Government security teams moved fast. CERT-EU told organizations that couldn't patch to consider isolating their internet-facing applications, and Italy's CSIRT rated both flaws critical in advisory AL07/261021/CSIRT-ITA.

This isn't the first time the problem has lived in the engine rather than in an application. In 2019, CVE-2019-11043 allowed code execution on certain nginx and PHP-FPM setups. In 2024, CVE-2024-4577 hit php-cgi on Windows with certain system language settings. Both needed a specific configuration.

php2shell doesn't. Calif, the security firm that found the first piece, demonstrated unauthenticated code execution on WordPress, Drupal, Joomla, Magento, Nextcloud, phpMyAdmin, Symfony 8.1 and Laravel 13.11, with default settings. And according to W3Techs, PHP runs about 70% of websites whose server-side language is known.

So updating your CMS or framework does nothing here: the bug sits underneath, in the runtime. The official descriptions are in PHP's security advisory and in the two GitHub advisories, GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp:

CVE What happens Impact
CVE-2026-109618 An out-of-memory error interrupts PHP while some internal request data is only half initialized; cleanup then treats it as valid Remote code execution
CVE-2026-109619 Request-scoped internal state survives after its memory has been freed; the next request on the same worker reuses it Remote code execution

How It Works, Without Diving Into C

It starts with an error we've all seen a thousand times in our logs: Allowed memory size of 134217728 bytes exhausted. When a request goes over memory_limit, PHP kills it with a fatal error.

The trouble is where exactly it gets killed. If the fatal hits halfway through an internal operation, some structures are left in an inconsistent state. Calif describes a clear example in PHP's hash tables. Simplified (this is not the real code), it looks like this:

// Inserting an element into a hash table, simplified
ht->nNumOfElements++;              // 1. the counter says "one more element"
bucket->key = allocate_key(len);   // 2. the memory_limit fatal fires here
// Step 2 never completes, but during cleanup
// the counter says the element exists, so PHP treats it as valid.

An attacker knows how to inflate a request's memory, for example with crafted multipart uploads full of nested field names, and uses that to trigger the fatal at the right spot. The next request that lands on the same worker (the PHP process that serves requests one after another) finds memory that has already been freed and uses it anyway. That's a use-after-free, code working on memory it no longer owns, and with enough effort it turns into code execution.

Wiz also showed a sneakier variant: a chunk of process memory leaking into a PHPSESSID cookie in the response, even when the application never calls session_start().

How php2shell works: request A exhausts memory, the PHP worker survives with half-initialized state, request B reuses the freed memory

Who Found php2shell: GPT-5.6 Sol, Calif and Twenty Years of Clues

An AI finding a bug in 2026 isn't news anymore. This bug has a history, though: plenty of people brushed up against it, and nobody had ever pinned it down completely.

When What
2004 Stefan Esser finds the first remote PHP vulnerability tied to memory_limit
2012 PHP 5.4 introduces the first code path that is vulnerable today
2020 Bug #78876: a related out-of-memory issue involving temporary files
2024 The security audit by Quarkslab, OSTIF and the PHP Foundation finds a similar issue; nobody proves it's exploitable, so it stays
August 1, 2026 Calif finds the first way to trigger the fatal at the right spot
August 11-15 Calif reports the bug to PHP and hands over a proof of concept (a working demo attack), which PHP confirms
August 19-24 Wiz independently finds the same flaws, confirms the attack on Drupal and WordPress, and reports them
September OpenAI's team finds a more complete version of the problem
October 21 Patched PHP releases ship

The 2024 row is the one that matters. Quarkslab's external audit had spotted the problem. It wasn't fixed because nobody managed to turn it into a real attack, and without an exploit an out-of-memory bug looks like a nuisance, not an emergency.

What the models changed is the cost of that proof. Wiz writes that a single researcher, with Gemini 4 Argon, GPT-5.6 Sol and their Wiz Atlas platform, did in about a week what used to take a team months.

How Calif Worked

Calif's write-up is the part I'd recommend to anyone who uses AI to write code. They never name the model (they talk about an "LLM companion"; the GPT-5.6 Sol link comes from OpenAI), and there's no magic prompt in it. What they describe is a researcher who knows PHP's vulnerability history and treats the model as a working partner: to find ways past memory_limit, and to have an agent review the allocator and request-shutdown code.

One detail stuck with me. After the first bug, they asked the model to generalize the pattern into a SKILL.md file, and that skill is what surfaced the second flaw, the one in session upload progress.

Their main lesson, in short:

Know your target. That still holds in the era of LLM-driven vulnerability research, and no model finds all the bugs from a simple prompt, at least not yet.

It's the flip side of what I wrote about AI hallucinations and trusting without checking. Here the result came because an expert steered the model and checked every step, all the way to an attack PHP could confirm. One practical tip from them too: models start from the newest, best-known bugs, so you have to ask explicitly for the old attack techniques.

Found Before the Attackers? Not Quite

OpenAI frames php2shell as a case where, "to our knowledge," frontier AI got there before criminals did. The facts tell a messier story.

Amazon logged a first exploit attempt against its honeypots (decoy servers put online specifically to attract attacks) on October 17, four days before disclosure. On Tuesday the 20th came the Hacker News post, watchTowr confirmed the bugs were real and under embargo, and PHP moved its release up by a day.

On Wednesday morning Cloudflare CTO Dane Knecht wrote that they were seeing "early attacks" about six hours before the patch, as Decrypt reported. Cloudflare had deployed protection rules at 03:02 UTC and watched attackers tweak their payloads to slip past them. By evening it was counting millions of attempts.

The most honest line in the whole story came from Amazon:

"An embargo protects defenders only until someone else finds the same bug."

Calif points out that the initial discovery used no special harness and no models unavailable to the public. That's why the window keeps shrinking: if a model anyone can pay for finds a bug like this, the next person to find it could be anybody.

Are You Vulnerable to php2shell? How to Check

Short answer: yes, until you patch. Every PHP version from 5.4 onward contains at least one of the vulnerable paths, and the way you run it won't save you. The PHP team lists PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed, and names mod_php as the riskiest. On PHP-FPM, Calif had to work harder to get the attack going, but they got there.

These are the first fixed releases (full details in the PHP 8 ChangeLog):

Branch Fixed version
PHP 8.5 8.5.12
PHP 8.4 8.4.27
PHP 8.3 8.3.36
PHP 8.2 8.2.35
PHP 8.1 and older No official patch

Watch Out: php -v Can Mislead You

There are two traps here.

The CLI isn't the PHP serving your site. php -v gives you the command-line version. The one answering web requests shows up with php-fpm8.4 -v (adjust the number for your version), and the two don't have to match.

Distributions fix things without changing the version number. That's a backport: Ubuntu and Debian take the fix and apply it to the version they ship. On Ubuntu 24.04, PHP stays at "8.3.6", but the fixed package is 8.3.6-0ubuntu0.24.04.13. If you only look at the PHP number, you'll think you're vulnerable when you're not, or worse, the other way around.

System Package Fixed version
Ubuntu 24.04 LTS php8.3 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS php8.1 8.1.2-1ubuntu2.28
Debian 13 (trixie) php8.4 8.4.27-1~deb13u1
Debian 12 (bookworm) php8.2 8.2.35-1~deb12u1

The data comes from Ubuntu's blog and Debian's security tracker. Ubuntu 20.04 and older only get the fix through Ubuntu Pro (free for up to 5 machines).

# CLI version and the PHP-FPM version that actually serves the site
php -v
php-fpm8.4 -v

# On Ubuntu and Debian: exact version of the installed packages
dpkg -l | grep -E '^ii +php[0-9.]+-(fpm|cli|common) '

If you install PHP from Ondřej Surý's packages instead (the repository a lot of people use, Laravel Forge included), version numbers follow upstream: there you want to see exactly 8.4.27, 8.3.36 and so on.

OpenAI's Checker and Its Limits

OpenAI published php2shell-checker, a Node.js script that checks a website or your local PHP:

node phpcheck.js "https://yoursite.com"   # reads the X-Powered-By and Server headers
node phpcheck.js --local                  # runs php -v on the machine

It returns OK, VULNERABLE or UNKNOWN. The README itself flags two caveats: UNKNOWN doesn't mean you're safe, and OK is only a version comparison, so it can't see backports. I find it handy for inventorying a lot of sites at once. But if the checker can read your PHP version from the headers, so can attackers: set expose_php = Off in php.ini.

If You're on Shared Hosting

You can't patch this yourself; your provider has to. Cloudflare makes the point in its post on the protections: updating your site's code or plugins doesn't fix it, the PHP runtime itself has to be updated.

You'll find the version in your hosting panel or, on WordPress, under Tools → Site Health → Info → Server. Because of backports the number alone isn't enough, so ask your provider in writing whether the installed build includes the fix for CVE-2026-109618 and CVE-2026-109619.

How to Patch PHP Against php2shell

There's no complete mitigation, and the PHP team says so plainly: updating is the only safe option. Fixed versions shipped on October 21 around 13:30 UTC.

On Ubuntu and Debian with Distribution Packages

If you use your distribution's PHP, a regular upgrade followed by a service restart is enough:

sudo apt update && sudo apt upgrade

# Then restart whatever runs PHP
sudo systemctl restart php8.3-fpm   # or apache2, if you use mod_php

Ubuntu's automatic security updates apply the fix within about 24 hours, but they don't restart your applications. Until you restart, the old PHP is still running.

Restart Everything That Runs PHP

This is the step people forget. Restarting PHP-FPM or Apache is the bare minimum, but any long-running PHP process keeps the PHP it had in memory when it started: queue workers, Supervisor-managed daemons, application servers like Swoole, RoadRunner or FrankenPHP. Until you restart them, the hole stays open.

# List running PHP processes and when they started
ps -eo pid,lstart,cmd | grep -E '[p]hp'

# Restart Supervisor-managed daemons
sudo supervisorctl status
sudo supervisorctl restart <program-name>:*

# Check that CLI and FPM really run the fixed version
php -v
php-fpm8.4 -v

With application servers, do a full restart rather than just reloading the workers. The reason: with Swoole, for instance, workers are forked from the master process, which still has the old PHP in memory.

If you're on Laravel, these are the commands to remember:

php artisan queue:restart       # workers finish the current job and restart
php artisan horizon:terminate   # Supervisor brings Horizon back up on the new PHP
# Octane and Reverb: full daemon restart via supervisorctl, not octane:reload

Docker and FrankenPHP

With Docker, restarting the container isn't enough: the old PHP is still inside. The official images already have the 8.5.12, 8.4.27, 8.3.36 and 8.2.35 tags. If you use a floating tag like php:8.4-fpm, rebuild and force a fresh pull of the base image:

docker compose build --pull
docker compose up -d

FrankenPHP deserves its own note: PHP is compiled into its binary. Updating the system PHP changes nothing, you have to replace the binary or the image with a build that ships a fixed PHP. That applies if you run it through Laravel Octane too. The first fixed release is 1.13.2, out on October 21: its Docker images use PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and its static binaries use PHP 8.5.12.

If You Use a Server Panel: Laravel Forge as an Example

Server panels often play by their own rules, and Forge is a good example of the trap. Its automatic security updates don't touch PHP: Forge installs it from Ondřej Surý's packages, while Ubuntu's unattended upgrades only pull from Ubuntu's own repository.

From the panel: open the server, go to the PHP tab, open the version's menu and click Update PHP X.X. Repeat for every installed version, including the ones no site uses. If you have a lot of servers, use the Forge API or a Recipe (a script Forge runs as root across several servers).

The same goes for any server with PHP from Ondřej Surý's repository: first check that the fixed package has landed, then upgrade.

# Which PHP versions are on the server
ls /etc/php

# The "Candidate" version must be the fixed one (e.g. 8.4.27)
sudo apt-get update
apt-cache policy php8.4-fpm

# Upgrade every package for that version, keeping php.ini and FPM pools
sudo apt-get install --only-upgrade -y \
  -o Dpkg::Options::="--force-confold" \
  $(dpkg -l 'php8.4*' | awk '/^ii/ {print $2}')

Still on PHP 8.1 or Older?

This is the number that struck me most. According to Mallory, as cited by Decrypt, on October 20 about 884,000 internet-facing hosts advertised PHP 8.1 or older: 59% of the ones that expose a version.

PHP won't release patches for these. The security team published minimal backports for 7.4, 8.0 and 8.1 for people building from source, and some distributions and LTS vendors offer their own fixes. The real way out is moving to a supported branch. Keep in mind that PHP 8.2 reaches end of life on December 31, 2026, a little over two months from now.

Can't Patch Right Away? Temporary Mitigations

There are partial mitigations, meant to buy you a few hours while you schedule the update. They close some attack paths, not all of them.

; TEMPORARY mitigations: they don't replace the update.
; Set them in php.ini or in the FPM pool (php_admin_value), not with ini_set() or .user.ini,
; then restart every worker.

memory_limit = -1                    ; no out-of-memory fatals (see note below)
post_max_size = 8M                   ; 32M if you need uploads
upload_max_filesize = 6M             ; always below post_max_size
session.upload_progress.enabled = 0
fatal_error_backtraces = 0           ; PHP 8.5 only

Removing the memory limit has an obvious cost: a single request can eat the server's RAM. The PHP team suggests a container or cgroup limit that kills only the affected worker.

Three more things you can do in the meantime:

Block multipart uploads where you don't need them. That's Amazon's suggestion: if part of your application never receives files, close it to uploads. Test that nothing breaks first.

Keep your WAF on, but don't rely on it. A WAF (Web Application Firewall, the filter that blocks suspicious requests before they reach your server) helps. Cloudflare's rules are on automatically even on the free plan; on paid plans, check that the managed ruleset is in block mode. But Knecht himself called them "a stopgap only," and attackers are already tweaking payloads to get around them.

If the service isn't essential, take it offline. That's what CERT-EU and Italy's CSIRT recommend for sites that can't be patched quickly, and it's what Finland, the Netherlands and France did.

How to Tell if php2shell Already Hit You

Amazon's advice is to start from October 17, not from the disclosure date. Signals to look for: multipart POST requests followed by out-of-memory errors, bursts of 500 errors, odd or oversized PHPSESSID cookies in responses, PHP workers dying and respawning for no reason. Italy's CSIRT adds unexpected process execution, modified application files and unusual network connections.

# Out-of-memory fatals in nginx error logs (rotated .gz files included)
sudo zgrep -h "Allowed memory size" /var/log/nginx/*error.log* | tail -n 50

# PHP files modified since October 17, excluding vendor and storage (adjust the path)
# Compare them with your deploys: the ones you didn't touch are the problem
find /var/www -name "*.php" -newermt "2026-10-17" \
  -not -path "*/vendor/*" -not -path "*/storage/*"

Wiz warns that this plays out below the application layer, so your application logs may show nothing at all. If you have a WAF or request body logging, look for requests containing PHP_SESSION_UPLOAD_PROGRESS in upload forms.

If you find suspicious signs, treat anything that was in worker memory as exposed: database passwords, API keys, secrets in your .env. Rotate them. On Laravel 11 and later you can move the old key into APP_PREVIOUS_KEYS, so encrypted data stays readable while you switch to a new APP_KEY. And report the incident to your national CERT.

php2shell FAQ

Do attackers need an account to exploit php2shell?

No. The attack is unauthenticated: all it takes is sending crafted HTTP requests to any page served by PHP.

Is updating WordPress or Laravel enough?

No. The flaws live in the PHP engine, beneath any CMS or framework. You need to update PHP and restart the processes that run it.

Will Cloudflare or a WAF protect me?

Not on their own. They lower the risk while you patch, but Cloudflare itself tells customers to update PHP even with its protections on.

My server says PHP 8.3.6. Am I vulnerable?

It depends on who built that PHP. If it comes from Ubuntu 24.04's packages, version 8.3.6-0ubuntu0.24.04.13 is fixed thanks to the backport. If it comes from anywhere else, 8.3.6 is vulnerable and you need at least 8.3.36.

Is php2shell already being exploited?

Yes. Amazon says it is "now being broadly exploited in the wild," and it saw attempts four days before disclosure.

Is disabling session.upload_progress enough?

No. It closes only one of the attack paths. It's a temporary mitigation, not a fix.

I'm on PHP 7.4, 8.0 or 8.1. What should I do?

You won't get official patches. You can lean on a distribution or LTS vendor that backports the fix (Ubuntu Pro, for example), but the right move is upgrading to a supported branch.

Glasswing vs. Patch the Planet: Two Approaches to AI Security

In May I wrote about Project Glasswing, Anthropic's decision to give Claude Mythos to only about forty organizations because it was too good at finding vulnerabilities. The idea: lock the weapon away and let only defenders use it.

php2shell shows the opposite approach. The first piece of the bug was found with a public model, used by a security firm inside Patch the Planet, the OpenAI initiative that spent two months coordinating PHP, Wiz, Calif, Cloudflare, Quarkslab and US agencies. The result was a patch ready for everyone, along with an exploit in circulation before the patch was out.

I don't think either approach "won." I do think php2shell shows where Glasswing falls short: if a model anyone can use finds a flaw this serious in PHP, locking up the strongest model buys very little time. The bottleneck has moved. Finding bugs keeps getting cheaper, while fixing them and getting the fixes onto servers is still slow. And more are coming: OpenAI president Greg Brockman said they "expect to find many more such issues over upcoming months."

What Changes for Anyone Running PHP in Production

The clues to php2shell were there in 2004, in 2020, in 2024. What was missing was the time to follow them all the way, and now that time costs one researcher with an AI model about a week. The same goes for attackers: Amazon saw the first attempts four days before disclosure, and proof-of-concept exploits circulating within hours of it.

Anyone who runs servers knows exactly which PHP version is on them. The weak spot php2shell exposed is a different one: how long it takes for a released patch to actually run everywhere. The package that's slow to reach the repository, the panel that doesn't update PHP on its own, the Docker image that needs a rebuild, the workers that need a restart, the applications stuck on PHP 8.1 because migrating them costs money. On October 21 each of those steps meant a few more hours of exposure, and Amazon says it plainly: for critical internet-facing flaws, patch timelines should be measured in hours, not days.

If Brockman is right and more of these are on the way, I think the upshot will be treating PHP updates like operating system security updates: automatic, process restarts included, with version upgrades planned before end of support. PHP 8.2 goes out of support on December 31. Whoever is still running it when the next php2shell comes along won't get an official patch.

Did you like the article? If it helped you, consider buying me a coffee for support.

Buy me a coffee