WordPress 7.0: the wp2shell bug that got every hosting company emailing you
What actually happened
On 17 July 2026 the WordPress security team shipped 7.0.2. It fixed two bugs, rated one critical and one high, and it was serious enough that WordPress.org switched on forced automatic updates for every affected version. I have worked with WordPress for a long time and I had never seen core do that before.
Then the advisories arrived. GoDaddy published one and force-patched managed hosting on every supported branch. InMotion posted one. cPanel put up a support article for its customers. Hosts that never talk about core security were suddenly emailing their whole customer list about it, which tells you more about the severity than any CVSS number does.
The two bugs, in plain language
Adam Kues at Searchlight Cyber found the chain and named it wp2shell. Neither half is a one-shot takeover on its own. Chained together they are about as bad as a web vulnerability gets: an anonymous attacker, one HTTP request, no login, no plugin, nothing unusual in your configuration.
CVE-2026-63030 is a route confusion bug in the REST API batch endpoint at /wp-json/batch/v1. The batch feature exists so a client can bundle several API calls into a single request. It validates those calls in one pass and dispatches them in another, holding matched routes and validation results in two parallel lists. Send a deliberately malformed item and the lists slip out of alignment. WordPress then checks one item against the rules written for a different one and hands it to the wrong handler. It was rated 9.8 out of 10.
CVE-2026-60137 is an SQL injection. Core does not properly sanitise the author__not_in parameter of WP_Query when untrusted input reaches it. On its own that is a read-only SELECT injection sitting behind a permission check that no anonymous caller can pass. The route confusion is what carries it in front of the check.
Together you get unauthenticated SQL injection on a stock WordPress install. From there the public exploit is a genuinely neat piece of work: it forges data that WordPress itself then acts on, planting a customizer changeset naming user 1, which ends in a brand new administrator account. Once the attacker has an admin, uploading a webshell is routine.
It needed no plugin, no account and no special configuration. One anonymous HTTP request against a default install was enough.
Who was affected
- 6.9.0 to 6.9.4 and 7.0.0 to 7.0.1: the full remote code execution chain.
- 6.8.0 to 6.8.5: the SQL injection half only, which is not the full chain.
- Anything below 6.8: not affected. The vulnerable code arrived in 6.8 and 6.9, and Patchstack confirmed by reading the source across releases that older branches simply do not contain it. If a pre-6.8 site was compromised that week, it went through a different door.
- 7.1 beta 1: affected, fixed in beta 2.
The fixes shipped in 7.0.2, 6.9.5 and 6.8.6.
How quickly it went from patch to weapon
Patchstack watched the whole thing live. The fix landed in the WordPress trunk roughly 90 minutes before 7.0.2 was tagged for release. That commit is the disclosure whether anyone calls it that or not, because anyone watching core development can diff it. The first real exploitation attempts hit Patchstack's sensors about 90 minutes after the release, three hours after the commit.
The numbers after that are absurd. Patchstack blocked more than 65,000 attempts from over 1,500 unique IPs, and those rules only activate on sites running a genuinely vulnerable version, so every one of them was aimed at an unpatched target. Wordfence blocked more than 11 million for its premium customers alone. Campaign trackers counted over 45 million attempts in the first week from nearly 150,000 unique sources, roughly twenty times the scale of Drupalgeddon. CrowdSec logged 62,802 distinct attacking IPs in a single 15 day window, the highest count of any CVE it tracks.
Most of that traffic was scanners knocking on doors with AND (1=1) and OR SLEEP(5). Three IP addresses went straight for the full chain and started creating administrator accounts.
CISA added both CVEs to its Known Exploited Vulnerabilities catalogue on 21 July, four days after the patch, with a remediation deadline that landed on the 24th.
The part almost nobody mentions
A firewall was not the safety net it looked like, at least not in the first days. WordPress reads the rest_route query variable from the POST body before it looks at the query string. So an attacker can send a plain POST / with the entire payload in the body and the path /batch/v1 never appears in the URL at all. Patchstack found that almost every rule shipped in the first hours after disclosure, including its own early versions, inspected only the URL and waved that straight through. They confirmed live attacks using exactly that shape from around 17:00 UTC on 21 July.
The practical rule from that week: if your block list did not cover both /wp-json/batch/v1 and rest_route=/batch/v1, you were not blocking the attack, you were blocking the easy version of it.
Why this is still worth reading in September
Because the summer never settled down. 7.0.3 on 6 August carried twelve more security fixes, including a pre-authentication reflected XSS on the login screen that could lead to PHP code execution. 7.0.4 on 12 August fixed an authenticated author-level remote code execution through a malicious file upload on sites using Imagick and Ghostscript. On 17 September, 7.1.1 shipped eleven security fixes alongside 36 bug fixes. On 22 September, 7.1.2 fixed a critical bug where page template resolution could pull in a readable local PHP file from outside the active theme and, if the preconditions lined up, execute it.
Core has had a rough year. wp2shell was the loud one, but it was not the last one, and it will not be the last one.
What to do on your own site
- Confirm the version. Do not assume. A forced update never reaches a site whose host disables it, a site with a read-only filesystem, or the site somebody handed to a contractor two years ago and forgot about. Automatic updates also skip major version jumps and can fail silently. As I write this the current release is 7.1.2. If you are pinned to an older branch, the up to date releases there are 7.0.6, 6.9.9 and 6.8.10.
- If you genuinely cannot update today, block both
/wp-json/batch/v1andrest_route=/batch/v1, or stop unauthenticated REST API access entirely. Every vendor that published those mitigations called them an emergency measure, not a fix. Treat them that way and remove them once you are patched. - Look for leftovers. The administrator accounts created in this campaign used usernames beginning with
w2s_orwp2_followed by twelve hexadecimal characters, often with email addresses at webmaster.com or hackerone.ltd. Check for administrator accounts you did not create, plugins you did not install, and stray PHP files underwp-content/uploads/. - Leave automatic updates on. The uncomfortable truth is that the sites which came through July fine were the ones already patched by the time the scanners arrived. Ninety minutes is not enough time to schedule a maintenance window.
The takeaway I keep coming back to
Kues found this chain with GPT-5.6 Sol Ultra, in about ten hours of work and roughly 25 dollars of compute. Exploit brokers reportedly pay up to 500,000 dollars for a WordPress core remote code execution. That gap is the whole story of security in 2026: finding serious bugs is cheap now, and the distance between a fix and a working exploit is measured in hours.
You cannot outrun that with good intentions. You can keep the version current, watch your administrator list, and confirm that the update you believe happened actually happened. I check mine every Monday. It takes ninety seconds, and it is the best value I get out of any ninety seconds all week.
Need a hand with a WordPress site that keeps breaking? See what I do and how I work.