EresusSecurity
Back to Research
Vulnerability Analysis

wp2shell: Unauthenticated WordPress Core RCE via REST API Batch-Route Confusion (CVE-2026-63030 + CVE-2026-60137)

Eresus Security Research TeamSecurity Researcher
July 20, 2026
6 min read

Threat summary

On 17 July 2026, the WordPress core team shipped 6.8.6, 6.9.5, and 7.0.2 to fix a chain now widely tracked as wp2shell: an unauthenticated remote code execution path built from two separate bugs, CVE-2026-63030 (REST API batch-route confusion) and CVE-2026-60137 (SQL injection in WP_Query via the author__not_in parameter). Chained together, they let an anonymous, unauthenticated visitor reach code execution on a default WordPress installation — no plugin, no theme, no account required.

The chain was discovered by Adam Kues of Searchlight Cyber, whose team delayed publishing technical specifics past patch day specifically because of the install base at stake — WordPress powers well over 500 million sites.

Because the attack needs nothing beyond a stock install, and public proof-of-concept exploitation has been circulating since patch day, this is a same-day patch priority for any WordPress site the reader operates or manages, not a routine update.

What actually happened

WordPress's REST API exposes a batch endpoint, /wp-json/batch/v1, that lets a client bundle several sub-requests into a single call. Internally, the handler tracks those sub-requests using two parallel arrays. When one sub-request in the batch errors out, the two arrays fall out of sync by one position — a classic off-by-one confusion. The practical effect is that a later sub-request in the same batch gets processed under a different sub-request's routing and permission context than the one it actually declared.

That confusion (CVE-2026-63030) is the door. Walked through it, an attacker's input can reach the vulnerable code path behind CVE-2026-60137: unsanitised handling of the author__not_in parameter in WP_Query, which becomes a SQL injection. Chained, the two bugs take an anonymous HTTP request to arbitrary SQL execution and from there to remote code execution — hence the "wp2shell" name circulating in the security press.

Affected versions and patches

  • CVE-2026-60137 (SQL injection): WordPress 6.8.0–6.8.5, 6.9.0–6.9.4, 7.0.0–7.0.1.
  • CVE-2026-63030 (batch-route confusion, the piece that turns the SQLi into unauthenticated RCE): WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1. The 6.8.x line is affected by the SQL injection but is not described by the official advisory as reachable through the full unauthenticated RCE chain.
  • Fixed in: 6.8.6, 6.9.5, 7.0.2.

Given the severity, WordPress.org enabled forced automatic background updates for affected sites. That is a safety net, not a guarantee — hosts with auto-updates disabled, heavily customised deployments, or update mechanisms that silently fail will not be covered by it. Verify the installed version directly; do not assume the background update completed.

Exploitation status

Public write-ups describing the full mechanism, and working proof-of-concept code, appeared within days of the patch — a now-familiar pattern where patch diffing lets researchers (and attackers) reconstruct the vulnerable path quickly. Multiple outlets have reported exploitation attempts in the wild against unpatched sites shortly after disclosure. Treat any WordPress install still on an affected version as presumed-targeted, not merely presumed-vulnerable.

Immediate response checklist

  1. Confirm the running version on every WordPress instance you operate — dashboard, WP-CLI (wp core version), or your fleet-management tooling. Do not rely on forced auto-update alone.
  2. Update to 6.8.6, 6.9.5, or 7.0.2 (or later) immediately on anything still exposed.
  3. If you cannot patch immediately, block /wp-json/batch/v1 and the equivalent ?rest_route=/batch/v1 query-string form at your WAF or reverse proxy as a stopgap. This removes the entry point the chain depends on, at the cost of any legitimate integration that uses the batch endpoint. Searchlight Cyber's own advisory lists two further options for this gap: disabling the REST API entirely on sites that don't need it (e.g. via a "Disable WP REST API" style plugin), or gating the batch route behind authentication with a small compensating plugin.
  4. Assume compromise is possible on any site that was on an affected version and internet-reachable between the vulnerability becoming exploitable and your patch landing. A successful chain can end in a webshell — patching closes the door but does not evict an attacker who already walked through it.
  5. Hunt for indicators of compromise before marking the incident closed: unexpected admin users, unfamiliar files in wp-content/uploads or theme/plugin directories, modified wp-config.php or core files, unexpected scheduled tasks (wp_cron), and outbound connections from the web server process that don't match normal application behaviour.
  6. Rotate secrets that a successful SQL injection could have exposed — database credentials, authentication salts/keys in wp-config.php, and any API keys stored in the database — if compromise cannot be ruled out.

Detection guidance

Because the entry point is a single REST endpoint, log review is tractable even without deep WordPress instrumentation:

  • Search access logs for POST requests to /wp-json/batch/v1 or rest_route=/batch/v1, especially with unusually large or malformed request bodies, or a high volume from a single source in a short window.
  • Look for batch payloads containing nested requests with SQL-metacharacter-heavy author__not_in values.
  • Correlate any hits with subsequent file-write activity or new admin-level user creation in the same timeframe.

None of this replaces patching — it narrows where to look if you suspect a site was exposed before the fix landed.

Frequently asked questions

Does this only affect sites with vulnerable plugins?

No. Both CVEs are in WordPress core, and the chain works against a stock installation with no third-party plugins or themes installed. Plugin exposure is a separate, additional concern — not a precondition here.

Is blocking the batch endpoint at the WAF a real fix?

It is a stopgap that removes the known entry point, useful when immediate patching isn't possible. It does not address the underlying SQL injection, and any other reachable path to the same WP_Query parameter would not be covered. Patch as soon as you can and treat the WAF rule as a bridge, not a destination.

We have forced auto-updates enabled — are we covered?

Likely, but verify. Auto-update can fail silently on constrained hosting, heavily modified core installs, or filesystem-permission issues. Confirm the version number directly rather than trusting the setting alone.

Evidence-led response with Eresus Guard

Eresus Guard brings DAST, SAST, SCA, and configuration checks into one evidence-led workflow. For an incident like wp2shell, that means being able to show — per asset — which WordPress version was running, when the patch landed, what compensating control (if any) was active in the gap, and what the compromise-hunt found. Explore the Eresus Guard workspace or plan a scoping call.

Sources

Security Validation

Have you tested this risk in your own system?

Eresus Security delivers real exploit evidence through penetration testing, AI agent security, and red team operations.

Request a pilot test

Related Services