← All articles

WP2Shell: the WordPress flaw that needs no login

22 July 20263 min read

On 17 July WordPress closed WP2Shell, a two-CVE chain giving unauthenticated code execution on a clean install, and the first attempts landed within hours. What we checked across the sites we maintain, and why patched does not mean clean.

On 17 July WordPress shipped 7.0.2, with backports 6.9.5, 6.8.6 and 7.1 Beta 2, to close a two-bug core chain the security industry named WP2Shell: unauthenticated remote code execution on a default install, no plugins, no account. The first attempts arrived the same evening. The patch was the easy part.

The chain in two lines

CVE-2026-60137 is a SQL injection in the author__not_in parameter of WP_Query. CVE-2026-63030 is a route-confusion logic flaw in the REST API batch processor at /wp-json/batch/v1: validation and execution run in two separate loops, and when wp_parse_url() fails on a sub-request path the error is pushed to the validation array but not to the matches array. The two arrays desynchronise and every following request is dispatched under the wrong handler. The result: an anonymous request reaches somewhere it never should and the SQL injection becomes exploitable without authentication. From there an administrator account is forged, the attacker logs in and uploads a malicious plugin. Affected versions are 6.9.0 to 6.9.4 and 7.0.0 to 7.0.1 for the full chain, 6.8.0 to 6.8.5 for the SQL injection alone; anything before 6.8 is not exposed. Credit for the batch bug goes to Adam Kues (Assetnote, Searchlight Cyber), for the SQL injection to TF1T, dtro and haongo.

The severity labels do not agree

The official advisory describes one critical and one high severity issue. Rapid7 calls the RCE critical but scores it CVSS 7.5, Tenable rates it 9.8. Do not spend time on it: the severity here belongs to the chain, not to the individual links.

The real window was hours

Patchstack reported attempts shortly before 19:00 ET on 17 July. Cloudflare had already published two WAF rules at 17:03 UTC, with a default action of block, active on free plans too. By 19 July VulnCheck had verified more than two dozen distinct public PoCs, on 20 July it observed exploitation against production systems, and on 21 July both CVEs were in CISA's KEV catalogue. WordPress.org did something rare: it forced the automatic update on vulnerable sites.

What we checked, in this order

  • Actual version, not assumed version: the forced update covers a lot, not everything. Managed hosts, plugins that pin core and auto-updates switched off years ago by someone else all sit behind until a human intervenes.
  • REST surface: check whether /wp-json/batch/v1 answers anonymous requests. On a brochure site it serves nobody, and closing anonymous REST access costs nothing.
  • HTTP logs: look for batch endpoint requests in the days around 17 July. It is the only way to tell "not attacked" apart from "not looked at".
  • Post-patch diff: admin users, plugin directory, uploads folder, PHP files that appeared out of nowhere. Patched and clean are two different states.

Verdict

Automatic core updates are no longer a preference, they are a minimum requirement: whoever turns them off accepts a risk they cannot quantify. But the structural problem is different, and no patch fixes it: too many SME sites have nobody whose job it is to apply the patch. If yours was not updated within 48 hours of 17 July, do not ask whether it was hit: assume it was and run the diff. And close the REST API to anonymous requests, because that is the surface that keeps producing incidents of this size.