wp2shell: Unauthenticated WordPress RCE and How to Block It
wp2shell is an unauthenticated remote code execution chain in WordPress core. It combines a SQL injection (CVE-2026-60137) with a REST batch route-confusion bug (CVE-2026-63030) and needs no login and no third-party plugin. Sites running ReportedIP Hive are already protected: the rules are live in the bundled baseline and published to the ruleset API, so free and synced installs block the request pattern today.
Patch WordPress core to the fixed release first. That removes the root cause. A firewall covers the window before every site in your fleet is updated, and it catches variants, but it is not a substitute for the core patch. If you run Hive, update it to 2.1.25 as well, since the two new rules ship in the free baseline. The plugin guide has the install and update steps, and the latest build is on the GitHub releases page.
The two bugs, and why the chain matters
Neither bug is fatal on its own. Chained, they take an anonymous request all the way to code execution.
- CVE-2026-60137 (SQL injection, CVSS 9.1). The REST
author_excludeparameter is mapped to theauthor__not_inquery var inWP_Queryand interpolated as a string into apost_author NOT IN (…)clause. A value like0) UNION SELECT …-- -closes theIN()list and appends arbitrary SQL. On its own this is reachable only from an authenticated collection request. - CVE-2026-63030 (REST batch route confusion). In
/wp-json/batch/v1, a sub-request whose path failswp_parse_url()is appended to the internal$validationlist but not to$matches. The two arrays fall out of step, and a validated sub-request is dispatched under the next sub-request’s handler. That lets aGET /wp/v2/posts/999999carryingauthor_excluderun under the posts collection’sget_items(), where the injectable query var lives, with no authentication.
The batch escalation lands on WordPress 6.9 and newer, which is where the route-confusion handling exists.
From a blind SQLi to a shell
Once the injection reaches a non-split query (the exploit uses orderby=none and per_page=500 so the row survives as a fake post), it forges a full 23-column wp_posts row with UNION SELECT and carries the leaked value in the reflected post_title. From there the chain seeds three oembed_cache posts, recovers their IDs through the same injection, recasts those IDs into a customizer changeset and a nav-menu-item graph, and writes a fresh administrator through the REST user route. With an admin in hand, plugin or theme upload gives code execution. No credentials at any step.
How ReportedIP Hive blocks it
The Hive firewall hooks init at priority 1, and the optional drop-in runs earlier still through an auto_prepend_file guard, before WordPress boots. For an unauthenticated request it inspects the URI, the user agent and the request body up to 64 KB. Since 2.1.25 it inspects the body both raw and url-decoded, so a percent-encoded payload inside a JSON batch (SLEEP%283%29 instead of SLEEP() is visible to the same signatures. The firewall write-up covers the engine in more depth.
The admin-creation path depends on UNION SELECT, which the free waf_sqli_union rule already blocked before this release. Version 2.1.25 adds two structural rules that do not rely on the SQL keywords surviving in the body.
waf_rest_batch_desyncmatches the class of malformed sub-request paths a batch parser rejects, such as two or more leading slashes or a scheme with an empty host. It targets the desync primer itself, so changing the token does not evade it.waf_rest_batch_nestedmatches the one invariant the attack cannot drop, a sub-request whose body is itself a batch ("body":{…"requests":[).
Both rules are Paranoia-Level-1 and both are bounded against catastrophic backtracking. They ship in the offline baseline that every install carries, so protection is active the moment the plugin updates, with no rule sync required. For Professional sites the same rules are published to the signed ruleset API and arrive on the next sync. Against the public proof of concept, all six stages were rejected on the free and professional tiers, and a false-positive corpus of normal batch calls, protocol-relative URLs and absolute URLs stayed clean.
What to do now
Update WordPress core across your sites. Update ReportedIP Hive to 2.1.25 from Plugins, or click Check for updates to pull it right away. Free and unsynced sites get both rules with the plugin update. Synced Professional sites get them on the next ruleset sync, or instantly with Sync now. If you manage many sites, roll the core patch first and let the firewall hold the line while it propagates.
Frequently asked questions
Does ReportedIP Hive replace the WordPress core update?
No. The core patch is the fix. Hive blocks the request pattern in front of the vulnerable code, which protects the patch window and catches variants, but it does not remove the underlying bug.
Are free sites protected against wp2shell?
Yes. Both new rules are Paranoia-Level-1 and ship in the bundled baseline, so they apply on every plan as soon as the plugin updates to 2.1.25.
Will the new rules block legitimate REST batch requests?
No. They target malformed sub-request paths and a batch nested inside a sub-request body. Normal batch calls do not produce those shapes, and the false-positive corpus confirmed it.
The days around disclosure show up in our telemetry: REST batch-endpoint probing ticked up in mid-July. The WordPress Attack Report for May–July 2026 puts wp2shell in context alongside 1.69 million other attacks, and the Threat Reports hub tracks the data each quarter.