CVE-2026-63030: 168 Swiss WordPress Sites Unpatched

An unauthenticated code execution chain in WordPress core is being exploited. We read the version of every WordPress site on Swiss networks. 168 are behind.

cvethreat-intelvulnerability-scanning

WordPress shipped an emergency fix on 17 July for a pair of flaws that let a complete stranger take over a site. Attacks showed up in research sensors the next day. On 21 July the US authorities added both to their exploited list and gave federal agencies until 24 July to fix them, which is tomorrow. We read the core version of every WordPress site we could find on Swiss networks this morning. At least 168 are still running a build that carries the chain.

That is the number we can prove. The more interesting one is underneath it: 836 Swiss sites took the fix within six days, and almost none of their owners decided to.

What the bug actually does

The chain is nicknamed wp2shell and it is two flaws that are harmless apart.

CVE-2026-63030 is a route confusion in the REST batch endpoint, the thing that lets a client bundle several API calls into one request. The bundle gets validated against one route and executed against another, so a parameter that would normally be thrown out arrives at the query builder intact.

CVE-2026-60137 is what it arrives at. WP_Query drops the author__not_in parameter straight into raw SQL when it turns up as a plain string instead of an array. On its own that is hard to reach, which is why the advisory says it needs a plugin or theme to pass untrusted input. Behind the broken batch route, it needs nothing.

Put together, someone with no account and no password reads and writes your database, forges the records that create an administrator, signs in as that administrator, uploads a plugin, and runs code. Full control of the site and everything in it. No plugin required at either end, a default installation is enough.

The version boundary is the part most summaries get wrong, and it decides everything below. The batch endpoint only arrived in WordPress 6.9. Sites on 6.8 have the SQL flaw with no way in through core. So the full unauthenticated chain lives in exactly two narrow windows:

BranchAffectedFixedCarries
7.0.x7.0.0 to 7.0.17.0.2the full chain
6.9.x6.9.0 to 6.9.46.9.5the full chain
6.8.x6.8.0 to 6.8.56.8.6the SQL flaw only
below 6.8not affectednone neededneither

How we counted

We searched an internet exposure index for WordPress markers in Swiss address space, saved the raw records before touching them, and merged them into 2’937 distinct sites. Then we asked every one of them, directly, and read what came back.

One thing worth saying about the unit. We count sites, not addresses. Swiss WordPress lives overwhelmingly on shared hosting, so a single address at Infomaniak or Hostpoint serves a lot of independent installations, each with its own version and its own patch state. Collapsing them to the address would have averaged unrelated sites together.

Three groups came out of the count. 150 sit on networks owned by a hyperscaler, so they are cloud tenants that happen to land in Switzerland on a map rather than Swiss infrastructure. That exclusion mattered more than we expected, because several leased ranges inside those networks carry a Swiss country code on the address record while the network belongs to the cloud provider. Attributing on the address record alone would have counted all 150 as Swiss. Another 48 come off for registration reasons: 14 are registered to a country outside Switzerland, and 34 sit on networks we could not resolve to a Swiss holder.

The last 11 are the interesting ones. The index tagged zero honeypots, which we treated as a reason to look harder rather than a result. Eleven hosts answered only on port 8080, every one of them advertising the identical string “WordPress 2.8”, scattered across seven unrelated networks including three different cloud providers and an academic CERT range. A genuinely neglected site sits on 80 or 443 and is not replicated byte for byte across seven autonomous systems. Those are research sensors and they are out.

That leaves 2’728 WordPress sites genuinely on Swiss networks. 2’630 still served WordPress when we asked. We could read the core version on 1’927 of them, which is 73% of the population, and the other 27% are counted in neither direction.

What the versions say

Of the 2’728, we can confirm 168 running a build inside the two vulnerable windows: 76 on 7.0.0, 43 on 6.9.4, 20 on 7.0.1, 16 on 6.9.0 and 13 on 6.9.1. Another 60 sit on the 6.8 branch below its fix, which carries the SQL flaw but not the route into it. 1’248 are confirmed on a fixed build.

Two honest caveats before anyone quotes the 168.

It is a floor. 776 sites serve WordPress and advertise no version at all, and we say nothing about them either way. The real figure is at least 168 and we cannot tell you the ceiling.

It also means the build has not received the fix. It does not prove the site is exploitable. Three preconditions sit behind the version and none of them are visible from outside. A persistent object cache blocks the vulnerable path completely, and Redis or Memcached is common on managed Swiss hosting. A security plugin or firewall may block the REST surface. A virtual patch may sit in front. We did check whether the REST discovery endpoint answers at all, which is a normal request and not the vulnerable route, and 129 of the 168 do.

The number we did not expect

The exposure index stores a dated copy of each page it has seen. Where that snapshot predates the 17 July release and we also hold a live read, the pair tells you whether the fix arrived.

946 Swiss sites could answer that question. 836 of them moved onto a build that is no longer affected. 110 did not.

That is 88% in six days, on a national estate, without anyone scheduling a maintenance window. WordPress enabled forced updates through the auto update system for every affected version, and this is what that looks like when you measure it from outside instead of taking the vendor’s word.

The denominator matters here and it is the thing we nearly got wrong. 998 sites were on an affected build when the index last saw them, but 52 of those give no version today, so they cannot answer either way. Counting them as failures would have produced a true numerator sitting on a false denominator. The honest base is 946.

The internal check on all of this: not one of the 1’927 readable sites reports a lower version than its cached page. A sloppy parser produces drift in both directions. Zero backward movement across nearly 2’000 sites is what a clean read looks like.

Compare that to the 24 of 32 Swiss SharePoint servers still behind eight days after their fix, which we measured on 22 July, where most of the exposed population was running software that cannot be patched at all. Same country, same month, opposite outcome. The difference is whether the update installs itself.

The 417 that are too old to care

417 Swiss sites are not affected by this chain, and the reason is that they are running WordPress older than 6.8. The oldest genuine reads we got were 2.8.6, 2.9.2, 3.3.1 and two on 3.5.1.

Nobody should take comfort from this. Being far enough behind made this particular flaw inapplicable, and that is all it did. A WordPress 2.8 install is missing 17 years of every other fix. The correct reading is that “not affected by wp2shell” and “safe” are unrelated statements.

Reading it yourself

WordPress volunteers its version to anyone who asks. It sits in the page source of the homepage:

<meta name="generator" content="WordPress 6.9.4" />

If the generator tag has been stripped, the version usually survives on the query string of a core stylesheet, and failing that in the <generator> tag of /feed/. Below 6.9.5 or 7.0.2 means the fix has not landed. One request, no authentication, nothing near the vulnerable endpoint.

Two traps, both of which produced wrong readings for us before we caught them. Never read the version from /wp-includes/js/jquery/, because that query string carries jQuery’s version, which is 3.7.1 in current WordPress. An early pass had us reporting five Swiss sites on “WordPress 3.7.1” for exactly that reason. And block-library stylesheets only exist from WordPress 5.0 onward, so a lower number on that path is a rewritten query string from a caching plugin rather than the core version.

The detection rule behind this post is a version matcher with its validation record: schema check, then a mock server serving 14 known builds including both exact fixed versions and the x.y.0 form WordPress writes without a third octet, then a live run against all 2’937 sites. It flagged 186 with zero false positives, and every site our measurement caught that the rule did not has a named reason, mostly sites that only revealed the version through the feed.

If you want to know what your own infrastructure looks like from outside, Sentinel scans your public IP and delivers an AI written report in 30 minutes. Free, no account required.

What to do before tomorrow

Check that the version actually reads 7.0.2, 6.9.5 or 6.8.6, and read the page rather than trusting the dashboard. If forced updates did their job you are already there, and if you are not, that mechanism did not reach you and it is worth finding out why.

If the site ran an affected build while reachable, assume someone tried. The decisive requests in this chain travel inside batch POST bodies, which rarely show up in access logs at all, so the database is the better evidence. Look for administrator accounts nobody created, then check the plugin directory for anything you do not recognise.

And if you are one of the 417 sitting on a version from the last decade, this particular chain is not your problem. Roughly everything else is.