A WordPress security hole doesn’t wait for business hours. When a maximum-severity, no-login-required takeover bug landed in WordPress core, the race was on: the fix was already public, and attackers were reverse-engineering it to find sites that hadn’t patched yet. The chain now goes by the nickname “wp2shell.” Here’s how our team got ahead of it, and what our protection stopped once the attacks started.

The bug, in one paragraph

This is a two-stage attack. CVE-2026-60137 is a SQL injection in WordPress core, reached through the author_exclude / author__not_in query parameters. CVE-2026-63030 is a route-confusion flaw in the REST API batch endpoint (/batch/v1): a crafted batch request can be steered onto a handler, and permission check it was never meant to reach. Combined, they let an anonymous visitor run the injection where it should never have been allowed, create a new administrator account, and from there install a webshell for full remote code execution. Neither issue is catastrophic on its own; together they are a complete pre-authentication takeover. The full chain affects WordPress 6.9.0 to 6.9.4 and 7.0.0 to 7.0.1, and is fixed in 6.9.5 and 7.0.2 (the SQL injection is also fixed in 6.8.6).

How we responded, in about 12 hours

The escalation reached us late on Friday, July 17. The fix had already been committed to WordPress core earlier that afternoon, and for a vulnerability like this, the public code change is enough on its own: attackers can work backward from it to build a working exploit.

We treated it as the emergency it was. It was a weekend, outside business hours, but the team jumped on it immediately. Server-level WAF rules were written, tested, and live in the stable channel across the entire protected fleet by Saturday morning, roughly twelve hours after that Friday-night alert. A follow-up refinement that tightened the SQL-injection coverage and smoothed out a legitimate-traffic edge case shipped the same Saturday.

That speed is the whole point. The heaviest exploitation did not arrive on the first day; it built steadily over the following days. By the time it peaked, every site behind Imunify360 was already covered, with no action required from the site owner. The protection was in place before it was needed.

What we shipped

The rules block the public URL forms of the vulnerable batch endpoint and lock down the injectable parameters directly:

  • A rule that blocks requests to the REST batch endpoint in its /wp-json/batch/v1 form.
  • A rule that blocks the alternative ?rest_route=/batch/v1 form of the same endpoint.
  • A rule that blocks SQL-injection payloads in the author_exclude and author__not_in parameters, allowing only the integer author-ID lists that those fields are ever supposed to carry.

These are emergency virtual patches. They stand in the gap and stop the attack at the web-server layer, buying every site the time it needs to get onto a fixed WordPress version.

What our rules blocked

Because these rules run at the server level across our whole hosting fleet, we had a wide-angle view of the campaign as it escalated. Since the rules went live, Imunify360 has blocked more than 29 million exploitation attempts against this WordPress core chain, coming from over 223,000 unique IP addresses, protecting WordPress sites across our entire fleet.

The daily figures show the attack spreading fast and wide. Blocked attempts climbed from a few hundred thousand on release day to more than nine million per day within four days, and the number of distinct attacking networks grew right alongside them. The overwhelming majority of the traffic targeted the batch endpoint, which is how the route-confusion half of the chain carries its payload.

Trigger volume for the three wp2shell rules (77184806, 77137283, 77536622), showing the escalating daily waves after the Saturday release. To be inserted from the internal Grafana “Release rules triggers” board (last 7 days, UTC).

Why server-level protection mattered here

What mattered most about stopping this at the server is that it did not depend on anyone doing anything on the individual sites. There was nothing to install, no setting to switch on, and no per-site configuration to get right. Because Imunify360 runs at the web server level, every site on a protected server was automatically covered the moment the rule reached stable.

That is the difference between protection you have to set up site by site and protection that is simply part of the platform. A rule published to the server layer does not care whether a given site was actively maintained, kept up to date, or ever flagged as vulnerable. If it sat on a protected server, it was defended.

Staying ahead as the attack evolves

Attackers do not stop at the first working exploit. They keep changing how they package and deliver it to get around existing defenses, so we keep moving too. In the days since the first release we have already shipped several ruleset updates, broadening coverage as the attack changes, while watching legitimate traffic closely so real WordPress admin workflows keep working. That is how emergency virtual patching is meant to work: fast initial coverage, then continuous hardening as the threat shifts.

What you should do

If you run WordPress, upgrade to a fixed release – 6.9.5, 7.0.2, or 6.8.6 – at the earliest opportunity. Upgrading is the only permanent fix; a virtual patch simply holds the line until you can.

If you are protected by Imunify360, you were covered against this campaign before it peaked, and you still are. There was nothing to install and nothing to configure. That is the whole idea.