Updating WordPress does not close PHP2Shell

2026-10-22

On 21 October 2026 the PHP project shipped four security releases, 8.5.12, 8.4.27, 8.3.36 and 8.2.35, and said they fix two bugs that can let a stranger run a program on the server. The nickname in the headlines is PHP2Shell. The thing those releases replace is PHP itself (the program on the server that runs the site), not WordPress, not a plugin, and not a theme. The useful part is which sentence in the headline is a survey, which one is the bug, and which button does nothing.

If you only do one thing, find the line that says which PHP you are running, and move that line to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to the package your Linux vendor built from those fixes. On a machine you control, that line is the output of php -v. In WordPress it is under Tools, then Site Health, then Info, then Server. Updating WordPress does not change it.

Two programs, and people keep updating the wrong one

Mind map of PHP2Shell: a bug in PHP not WordPress, two CVEs, fixed in 8.5.12, 8.4.27, 8.3.36 and 8.2.35, no package for 8.1 and older, settings only partial, restart workers

A website is a reply to a request. The browser (the program on the phone or laptop that shows the page) sends the request. A web server (the program that accepts the connection, often Apache, nginx, LiteSpeed, or Caddy) receives it. If the page is not a finished file, the web server hands the work to another program. For a huge share of the public web, that other program is PHP. Your blog, shop, or form is a set of instructions that PHP carries out. Updating the instructions does not update the program that carries them out.

PHP2Shell request path: the browser asks for a page, the web server accepts the connection, a PHP worker runs the site, and your app sits on top

A worker (one long-lived copy of PHP that handles many requests, one after another, instead of starting blank every time) is why leftover state matters. A SAPI (Server API: the bolt between the web server and PHP) is the name of that arrangement. mod_php means PHP lives inside Apache. PHP-FPM means PHP is a separate pool of workers that Apache or nginx talks to. php-cgi, FrankenPHP and LiteSpeed are other bolts. PHP’s own advisory, posted 21 October 2026, lists every one of those as in scope. It says the risk is highest for mod_php, because a demonstrated attack can fire before any of your application code runs.

A static site (finished HTML files, with no PHP process answering requests) is not this bug. A Node, Python, Go or Ruby app is not this bug, unless some corner of the same domain still runs PHP. A billing page, an old blog, or a /wp folder is enough of a corner.

The headline’s 70 percent is a survey, not a map of every packet

W3Techs, the survey people quote, wrote this on 22 October 2026: “PHP is used by 69.3% of all the websites whose server-side programming language we know.” Both halves of that sentence are the fact. The percentage is taken over sites where the survey could tell the language at all, from headers, cookies, file names and similar traces. A site that leaves no trace is not in the denominator. Each site counts once, so a personal blog weighs the same as a site with millions of readers. A site can use more than one language, which is why the language columns do not add up to 100.

The 69.3 percent PHP figure behind PHP2Shell headlines counts only sites whose server language the survey can see

So “70 percent of the world’s websites” is a rounding of a survey of detectable sites. It is not “70 percent of internet traffic,” and it is not “your bank, your phone, and your email.” It is still the right size of warning for anyone whose page is produced by PHP. WordPress, Drupal, Joomla, Magento, Laravel, MediaWiki (the software behind Wikipedia): those are instructions. The bug Wiz published under the name PHP2Shell is in the program underneath them. Wiz says a short script that only prints hello is enough.

A second count is easy to mix in, and it is a different object. OX Security, writing on 21 October 2026 from a Shodan search (a search engine for machines that answer on the internet, not a survey of websites), said about 2 million internet-facing servers were showing a PHP version affected by these two bugs, that 245 of the servers it could see were already showing a patched version, and that 1.37 million, 69 percent, were on branches that will never get an official package. OX also says this counts only servers that advertise their PHP version, so the real total is likely higher. The “99.99 percent unpatched” line is that same-day snapshot. A patch that landed the afternoon of the measurement will look almost unused. Do not quote it next month as if it were still a measurement.

The PHP2Shell bug is a leftover address, not a stolen password

Memory (the numbered slots a program uses while it runs) is not the same object as a disk. A pointer (a number that says which slot to use) is how the program gets back to something it stored. Free (telling the memory manager that a slot may now be given to someone else) is a normal step when a request ends. Use-after-free (keeping the number, and then reading or writing that slot, after you have said it may be reused) is the class of mistake both of these bugs belong to. The next request, or the cleanup of this one, can find different bytes in that slot and treat them as PHP’s own bookkeeping.

Out of memory (the program asked for another slot and was refused) is one way to interrupt PHP in the middle of setting up a request. PHP’s old reaction to that kind of fatal failure is a bailout (a jump out of the work in progress, skipping the rest of the normal path, rather than returning neatly from each function). If the jump happens while request data is only half built, cleanup can treat the half-built data as finished. That is PHP’s own one-sentence description of the first bug.

How the PHP2Shell use-after-free works: a request runs out of memory, PHP bails out, and a later request on the same worker uses a freed pointer

The second bug is the same shape with a different leftover. Request-owned state (notes that were only supposed to exist for one request) can still be sitting there after the memory behind those notes was freed. The next request on that same worker may use the stale notes before normal startup wipes them. A worker that exits after every single request would throw those notes away by dying. A worker that stays up, which is the normal way to serve a site, keeps the process and therefore can keep the mistake.

Unauthenticated (no password, no account, no admin cookie) is the part that makes the class serious. Remote code execution (the attacker runs a program of their choosing on your machine) is the outcome PHP says both bugs can reach. A CVE (a public number so two people can be sure they mean the same bug) is how you look them up: CVE-2026-109618 and CVE-2026-109619. CVSS (a severity score from 0 to 10, not a prediction that you will be hit today) is 9.2 on version 4 of that scale, according to HackerOne’s write-up and the PHP Foundation. The Foundation’s 21 October post says the actual score is 9.2, after headlines printed 10.0.

Two bugs, one nickname

PHP’s advisory splits them like this.

CVE-2026-109618, which PHP calls “RCE via OOM corruption,” and which GitHub tracks as GHSA-w8hw-w9fx-j79w: out-of-memory handling can interrupt PHP while internal request data is only partially initialized. Request cleanup may then treat the invalid data as valid.

CVE-2026-109619, “RCE via stale global state,” GHSA-6wmf-4rv5-p3rp: request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it.

Wiz’s write-up, which is where the name PHP2Shell comes from, is more specific about paths it demonstrated, and it is Wiz’s account, not the PHP project’s wording. Wiz describes one path through the upload-progress feature (a built-in that watches a file upload and stores how far along it is, before your script runs) and a separate path through fatal-error bookkeeping and the OPcache (a shared cache of already-compiled PHP, so the next request does not compile the same file again). Wiz says it got unauthenticated code execution against current WordPress, Drupal and Joomla container images, and that the same bugs apply to a site that is none of those. OX, separately, says the failing allocation does not have to be the piece of memory that ends up corrupted, and that stock configuration is enough for the upload-progress path. This post does not walk through the request that does it. The patch is public, and the version check is the part that protects you.

One confusion is worth killing on its own. Turning off “file uploads” inside a WordPress screen is not the same switch as PHP’s upload-progress feature. The progress feature runs in the engine, before your application starts. An admin checkbox in a plugin does not reach back into that.

The four PHP versions that fix it, and the branches that will not get one

These are the releases PHP announced on 21 October 2026. The support dates next to them are from PHP’s supported-versions page as it stood on 22 October 2026.

BranchInstall thisWhat that branch is, on 22 Oct 2026
8.58.5.12Active support until 31 Dec 2027
8.48.4.27Active support until 31 Dec 2026
8.38.3.36Security fixes only, until 31 Dec 2027
8.28.2.35Security fixes only, until 31 Dec 2026
8.1 and olderNo official packageSource backports exist for 7.4, 8.0 and 8.1 only

Active support (bugs and security issues both get regular releases) is a different window from security-only support (critical fixes, when the project decides to ship them). Being on 8.2 does not mean you are skipped. 8.2.35 is the security release for that branch. Being on 8.1 does mean the PHP project will not ship you a package. The advisory says some Linux distributions and long-term-support vendors got the patches early, so the package name on Ubuntu or Debian or a paid LTS build may not be the number in the table. Read the vendor’s changelog for these two CVE numbers. “Latest WordPress” is the wrong sentence to put in a hosting ticket. The right sentence is the PHP package that contains the fix.

The PHP project also published minimal source backports, not full releases, for people building 7.4, 8.0 or 8.1 themselves: branches named PHP-7.4-CVE-2026-109618-109619, PHP-8.0-CVE-2026-109618-109619 and PHP-8.1-CVE-2026-109618-109619. A backport (a fix copied onto an old line of the code, without bringing the rest of the modern release along) is a source tree, not a promise that apt upgrade will do it. The same note says the volunteer repository rpms.remirepo.net would not have the updates the day of the release, and asks people who depend on it to switch to distribution packages or build from source.

W3Techs, same 22 October 2026 page, splits the sites it already counted as PHP: version 8 is 65.3 percent of them, version 7 is 26.9 percent, version 5 is 7.7 percent, version 4 is 0.1 percent. Those four add to 100, so they are shares of PHP sites, not a second copy of the 69.3. Version 7, 5 and 4 together are 34.7 percent of detected PHP sites, and none of those majors get an official package. Version 8 is not a clean bill either. That bucket includes 8.0 and 8.1, which are outside the four releases above. W3Techs does not print a split of version 8 into 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5. Anyone who writes “two thirds of PHP sites can just install today’s package” is adding a number the survey did not print.

PHP version shares on 22 Oct 2026: 65.3 percent on PHP 8 (8.2 to 8.5 have releases, 8.0 and 8.1 do not) and 34.7 percent on PHP 7, 5 or 4 with no official package

The settings people will paste are not a fix

The release announcements say there are no complete mitigations, and that updating is the only safe choice. The longer advisory, on the same day, is the careful version of that sentence: an update is the only complete solution, and the settings below reduce exposure to specific paths without making an installation safe. Both sentences are PHP’s. A blog that quotes only the settings is dropping the half that matters.

For CVE-2026-109618, PHP says you can set memory_limit to -1 in a place the application cannot override, such as php_admin_value in the FPM pool or the Apache config, and then check that no pool, directory or app puts a finite limit back. Its own next sentence: this removes the controllable PHP memory-limit failure, but an underlying allocation failure can still reach the vulnerable path. It also says not to use RLIMIT_AS or RLIMIT_DATA (operating-system caps on how much memory a process may ask for) as a substitute, because those caps can cause the allocation failure you were trying to avoid. A low post_max_size, and limits at the web server on body size, field count, multipart parts, headers and nesting, are the other reductions it lists. They are speed bumps in front of one path.

For CVE-2026-109619, PHP says set session.upload_progress.enabled=0 and restart every worker. The setting has to apply before request parsing, in php.ini or the pool or the server config. ini_set() (a call inside a PHP script that changes a setting for that request) is too late, and so is an FPM .user.ini. PHP says this blocks the confirmed session paths, not other variants. On 8.5 only, fatal_error_backtraces=0 blocks the confirmed fatal-backtrace path and nothing else. Hiding warnings with error_reporting, display_errors or @ does not help.

PHP’s partial settings in one place. They reduce specific paths and do not make an installation safe:

; php.ini, the FPM pool or the server config (not .user.ini, not ini_set())
session.upload_progress.enabled = 0   ; CVE-2026-109619: confirmed session paths only
fatal_error_backtraces = 0            ; PHP 8.5 only: confirmed fatal-backtrace path only

; FPM pool, for CVE-2026-109618 (the app cannot override php_admin_value)
php_admin_value[memory_limit] = -1

; then restart every PHP worker

Upwind’s note that disabling upload progress “doesn’t fully mitigate it” matches PHP. Upwind also mentions .user.ini and .htaccess as places to set the switch. PHP’s advisory says not to rely on FPM .user.ini. Where those two disagree, follow the project that ships the interpreter.

The find took months, and the public part took a day

The PHP Foundation’s account, posted 21 October 2026, is the best place to start, because it is the maintainer talking about its own delay. It says a possible exploit of the out-of-memory problem was theorized in 2024. It says Calif reported an unconditional remote code execution with that same root cause on 11 August 2026, and that OpenAI independently reported the same root cause the same day, then further attack paths on 18 September. It says Wiz reported additional paths on 24 August. It then says the quiet part in its own words: “We won’t pretend otherwise: the delay was a structural failure, caused by both the security team’s decision process and its lack of resources.”

Wiz dates its own first find to 19 August 2026, using Wiz Atlas (its vulnerability-research system), Gemini 4 Argon and GPT-5.6 Sol, with unauthenticated code execution against Drupal the next day and WordPress the day after, and the report to PHP on 24 August. The Verge, citing OpenAI, says a researcher at Calif used GPT-5.6 Sol to identify the flaw. Those accounts can all be true. They are not one person, and they are not one model.

PHP2Shell disclosure timeline: 2024 theory, August reports from Calif, OpenAI and Wiz, the 20 October leak and the 21 October coordinated release

The Foundation says the leak was 20 October at 15:00 UTC, that a pre-announcement followed at 19:30 UTC, and that the coordinated release was 21 October at 14:00 UTC. It also says that at 07:41 UTC on 21 October, more than six hours before that release, Chainguard published built images containing the fix and broke the embargo. A fix you can download is also a fix an attacker can compare against the old code. That is the actual AI story, more than the brand of the model. The same class of tool that helped find the bug can help turn a patch into an attack, which is why a two-month quiet period and then a leaked day is a bad shape even when the find itself was the right thing to do.

On whether anyone had already used it: the Foundation says the PHP security team inspected proofs of concept after the leak and “did not identify any functional exploits at the time of release.” Beazley’s advisory, dated around the same release, describes the flaws as under active exploitation. Treat “already being exploited” and “no working exploit confirmed at release” as two claims from two parties, not as a settled fact.

Ami Luttwak of Wiz, quoted by The Verge from the Wall Street Journal, called it “the most severe AI vulnerability that was ever found.” That ranking is not needed. A score of 9.2 on a program this widely installed is severe without a superlative. “AI vulnerability” is also the wrong noun. The vulnerability is in PHP. The models were a way of looking.

What to check before lunch

  1. php -v on every machine that still answers HTTP, including the old staging box and the box that only serves the blog. Read the first line. 8.5.11 is not 8.5.12. 8.4.26 is not 8.4.27. 8.3.35 is not 8.3.36. 8.2.34 is not 8.2.35.
  2. If a host runs PHP for you, ask which package contains CVE-2026-109618 and CVE-2026-109619. Do not ask them to “update WordPress” and stop there.
  3. If the version is 8.1 or older, there is no official release to wait for. Either move to a supported branch, or confirm that the vendor’s backport names these two CVEs. “We are on PHP 8” is not that confirmation.
  4. Restart the workers after the package is on disk. A replaced binary does nothing for a process that is still the old one.
  5. If you cannot patch today, apply PHP’s partial settings and believe the sentence printed above them. Then patch anyway.
  6. Do not put a public phpinfo() page up to check the version (phpinfo prints the configuration, including paths and modules, to anyone who can load the URL). Check it from a shell, or from a page you delete, or from WordPress Site Health while you are logged in.

The same checks from a shell:

php -v                              # first line: 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your vendor's patched build
php --ini                           # which php.ini files this PHP actually loads
sudo systemctl restart php8.3-fpm   # example service name: restart every PHP worker after the package lands

Common questions about PHP2Shell

Does updating WordPress fix PHP2Shell?

No. PHP2Shell is in PHP, the program that runs WordPress, not in WordPress, a plugin or a theme. The fix is PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or a vendor package that names CVE-2026-109618 and CVE-2026-109619.

How do I check if my PHP version is patched?

Run php -v on the server and read the first line, or open Tools, then Site Health, then Info, then Server in WordPress while logged in. Do not put a public phpinfo() page up to check it.

Is turning off session.upload_progress enough to stop PHP2Shell?

No. PHP says session.upload_progress.enabled=0 blocks the confirmed session paths for CVE-2026-109619 and not other variants. Updating PHP is the only complete fix.

What if my server runs PHP 8.1 or older?

There is no official package. PHP published source backports for 7.4, 8.0 and 8.1, so either move to a supported branch or confirm that your vendor’s backport names both CVEs.

JOIN OUR NEWSLETTER
Be the first to know. Get fresh AI/Tech updates instantly, no spam, unsubscribe anytime

Leave a comment