CVE Tools
Back to blog

wp2shell: how two WordPress core bugs chain into unauthenticated RCE (CVE-2026-63030)

A REST batch-route confusion plus an author__not_in SQL injection turn a stock WordPress 6.9/7.0 install into a pre-auth shell. Here's the chain, who's affected, and what to do today.

wp2shell: how two WordPress core bugs chain into unauthenticated RCE (CVE-2026-63030). A REST batch-route confusion plus an author__not_in SQL injection turn a stock WordPress 6.9/7.0 install into a p
wp2shell: how two WordPress core bugs chain into unauthenticated RCE (CVE-2026-63030). A REST batch-route confusion plus an author__not_in SQL injection turn a stock WordPress 6.9/7.0 install into a p

For a bug to matter at internet scale it has to be reachable, unauthenticated, and sitting on software everyone runs. CVE-2026-63030 is all three: a pre-authentication remote-code-execution chain in WordPress core -- not a plugin -- that a researcher branded wp2shell. It works on a stock install with no account, no plugin, no user interaction. On the world's most-used CMS, that is the rarest and most dangerous shape a vulnerability can take.

What the flaws actually are

wp2shell is two bugs working together. The first, CVE-2026-63030, is a route-confusion flaw (CWE-436) in the WordPress REST API's batch endpoint -- the one the block editor uses to bundle several API calls into a single request. When a sub-request errors while it is being built, WordPress records the error in one internal list but forgets to add a matching entry to the list that holds each sub-request's permission check. The two lists fall out of step by one, so a request the attacker controls gets dispatched under a different request's permission callback -- walking straight past the check that should have blocked it.

The second bug, CVE-2026-60137, is a SQL injection (CWE-89) in WP_Query's author__not_in parameter. WordPress expected an array of integers there; if a string arrives instead, the sanitisation guard is skipped and the raw value is dropped straight into a NOT IN (...) SQL clause. On its own that flaw needs trusted code to reach it -- which is why the route confusion matters. Chained, the batch bug lets an anonymous attacker steer a crafted author_exclude value into that vulnerable query, delivering the SQL injection with no login at all. On a default install, that read primitive is enough to reach code execution.

The exploitation chain

From an anonymous HTTP request to code execution, the path is short. The attacker sends a single POST to the batch endpoint -- reachable as both /wp-json/batch/v1 and /?rest_route=/batch/v1, so it works even without pretty permalinks -- carrying a batch envelope whose malformed first sub-request triggers the index desync. That smuggles a author_exclude value into the vulnerable query for the unauthenticated SQL injection, and on a default install (no persistent object cache) the chain converts into remote code execution. Follow the stages below, and note the prevent/detect chokepoints at each step -- patching removes the first two entirely.

wp2shell exploitation chain -- anonymous POST to code execution

  1. Unauthenticated attacker (WP 6.9.0-6.9.4 / 7.0.0-7.0.1) — No account, no plugin, no user interaction. **Prevent:** patch to 7.0.2 / 6.9.5 -- the single most effective control.
  2. Fingerprint the REST batch endpoint — `GET /wp-json/` for the `batch/v1` namespace, or probe `/?rest_route=/batch/v1`. **Prevent:** WAF-block both endpoint forms if you cannot patch.
  3. POST a malformed batch envelope — A broken first sub-request path shifts WordPress's internal request/permission arrays by one. **Detect:** anonymous POST to /batch/v1 returning HTTP 207.
  4. Route/permission desync -- CVE-2026-63030 — Request i runs under request i+1's permission check -- the allow-list is bypassed. CWE-436. **Prevent:** the 7.0.2/6.9.5 fix re-aligns the arrays.
  5. Smuggled author_exclude -> SQL injection -- CVE-2026-60137 — A string reaches WP_Query's author__not_in past the guard, interpolated raw into NOT IN (...). Unauthenticated, time-based blind. CWE-89.
  6. Persistent object cache enabled? — The RCE step works only WITHOUT a persistent object cache (the default). With Redis/Memcached object cache, impact is limited to database read.
  7. SQLi -> code execution as the web-server user — The exact primitive is embargoed by the researcher. Result: full site control and a pivot into the hosting environment.
  8. Unauthenticated database read (admin hashes, PII) — Even without RCE, the injection dumps the database -- admin password hashes, user data. **Respond:** rotate admin creds and wp-config salts.
  9. Webshell / admin takeover / full site control — Persistence, then monetization: access resale, SEO spam, coin-miners, WooCommerce card-skimming. **Detect:** new admin users, unexpected plugins.
  10. Detect & verify (Nuclei / WAF / logs) — Official Nuclei template (safe time-based check) + the anonymous-POST-to-/batch/v1 -> 207 log signal.

Am I affected?

Get the version math right, because 6.8.x is a different story from 6.9+. The RCE chain needs the 6.9+ batch endpoint; the SQL injection alone reaches back to 6.8. If you run 6.8.x you are exposed to the injection but not the full shell, and you only need 6.8.6.

BranchSQLi (CVE-2026-60137)Full RCE chain (CVE-2026-63030)Fixed in
Below 6.8Not affectedNot affected--
6.8.0 - 6.8.5Affected (SQLi only)Not affected6.8.6 (SQLi fix)
6.9.0 - 6.9.4AffectedAffected6.9.5 (both)
7.0.0 - 7.0.1AffectedAffected7.0.2 (both)
7.1 (beta)Affected pre-beta2Affected pre-beta27.1 beta2 (both)

Timeline

Disclosed and patched the same day, PoC within 24 hours

  1. Emergency release + forced auto-updates
    WordPress ships 7.0.2 / 6.9.5 / 6.8.6, publishing both advisories and enabling forced auto-updates. Searchlight/Assetnote (Adam Kues) withhold exploit details to give defenders time.
  2. WAF coverage
    Cloudflare ships managed + free WAF rules; Wordfence pushes a firewall rule to paid tiers.
  3. Public PoC appears
    A working detection PoC lands on GitHub and researchers begin reversing the patch; maturity moves from none-public to functional in about a day.
  4. No confirmed in-the-wild compromise; not on CISA KEV
    Authoritative reporting still shows no confirmed exploitation and no KEV listing -- only opportunistic scanning and unverified early signals.

Exploited in the wild -- what we know and don't

Be precise here, because the vendor chatter is noisier than the evidence. The discoverer's advisory makes no exploitation claim; Rapid7 says it is not aware of publicly confirmed in-the-wild exploitation; The Hacker News notes the bug is not on CISA's KEV catalogue and none had been reported as of 18 July. A couple of vendors mention "first indications" of activity and one reports possible exploitation on disclosure day, but those signals are ambiguous (scanning versus exploitation) and unconfirmed at a primary source. The realistic picture as of 19 July is opportunistic probing plus a public read primitive -- not a confirmed compromise campaign.

Detection & hunting

The catch for log hunters: the malicious parameters live in the POST body, which standard webserver access logs do not record -- they show only the path. So the highest-signal, always-available indicator is any anonymous POST to /batch/v1 that returns HTTP 207, because that endpoint is almost never called without authentication in normal traffic. If your WAF or reverse proxy logs bodies, hunt for author_exclude / author__not_in, SLEEP(, and -- - markers as well. Two deployable detections:

Deployable detections for wp2shell2 rules
nuclei ×1sigma ×1

One verified public check plus one author-written log rule. Confirm the endpoint is not called anonymously in your baseline before alerting.

nucleiWordPress wp2shell pre-auth blind SQLi (official ProjectDiscovery template)GitHub
The unauthenticated time-based blind SQL injection via the REST batch endpoint -- a safe, non-destructive check (SLEEP timing + HTTP 207). nuclei; network reach to the target
sigmaAnonymous POST to REST batch endpoint (author-written, untested)
Anonymous access to /wp-json/batch/v1 or /?rest_route=/batch/v1, and (where bodies/query are logged) author_exclude / SQLi markers. webserver or proxy access logs
title: WordPress wp2shell REST Batch-Route Exploit Attempt (CVE-2026-63030 / CVE-2026-60137)
status: experimental
description: Anonymous access to the WordPress REST batch endpoint used by the wp2shell pre-auth RCE chain.
logsource:
  category: webserver
detection:
  batch_endpoint:
    cs-method: 'POST'
    cs-uri-stem|contains:
      - '/wp-json/batch/v1'
      - '/batch/v1'
  batch_query:
    cs-uri-query|contains: 'rest_route=/batch/v1'
  condition: batch_endpoint or batch_query
falsepositives:
  - Authenticated block-editor / bulk REST batch usage
level: high
tags:
  - attack.initial_access
  - attack.t1190
  • Check exposure (on host): wp core version -- vulnerable band is 6.9.0-6.9.4 / 7.0.0-7.0.1 (RCE) and 6.8.0-6.8.5 (SQLi only).
  • Check the surface: curl -s https://TARGET/wp-json/ | grep -i 'batch/v1', and probe both /wp-json/batch/v1 and /?rest_route=/batch/v1.
  • Assume-breach hunt (if you were unpatched and exposed before ~17 Jul): grep access logs for anonymous POST .../batch/v1 returning 207; review for new admin users, unexpected plugins, and .php webshells in wp-content/.

What to do now

  1. Patch. Update to 7.0.2 or 6.9.5 (6.8.6 if you are on 6.8.x). Then verify: wp core update && wp core version.
  2. If you can't patch immediately, WAF-block anonymous access to BOTH /wp-json/batch/v1 AND /?rest_route=/batch/v1 -- the query-string form is the common miss. Cloudflare and Wordfence managed rules already cover it.
  3. Verify the fix. Re-run the Nuclei check and confirm no match; confirm the installed build; a patched host should not return an exploitable 207 to a malformed batch request.
  4. If you had an exposed, unpatched window, rotate all admin passwords and the wp-config secret keys/SALTS to invalidate sessions, then hunt for webshells and rogue admins. The injection reads admin password hashes, so credential rotation is time-sensitive.

FAQ

What is wp2shell?
A nickname for the exploit chain combining CVE-2026-63030 (a WordPress REST batch-route confusion) with CVE-2026-60137 (an author__not_in SQL injection). Together they give an unauthenticated attacker SQL injection and, on a default install, remote code execution on WordPress 6.9.0-6.9.4 and 7.0.0-7.0.1.
Is CVE-2026-63030 being exploited in the wild?
As of 19 July 2026 there is no independently confirmed in-the-wild compromise, and it is not on CISA's KEV catalogue. A public detection PoC exists and opportunistic scanning is expected; a couple of vendors report unverified 'first indications,' but the authoritative reporting says no confirmed exploitation yet. Patch regardless -- the risk profile is exactly the kind that turns into mass exploitation once a reliable exploit circulates.
Am I safe because WordPress auto-updates?
Probably, but verify. WordPress forced the security release through the auto-update system on 17 July 2026, so many sites self-healed. Staging, version-pinned, and auto-update-disabled installs will not have updated. Run wp core version and confirm 7.0.2 / 6.9.5 / 6.8.6 or newer -- do not assume.
I'm on 6.8.x -- do I need to worry?
You are exposed to the SQL injection (CVE-2026-60137) but not the full RCE chain, which needs the 6.9+ batch endpoint. Update to 6.8.6 to fix the injection.
Is there a public exploit?
A detection-grade PoC and an official Nuclei template are public and prove the unauthenticated database read (including admin password hashes). Two GitHub repos claim a full RCE chain, but their end-to-end reliability is unverified, and the discoverer withheld the exact SQLi-to-RCE step. There was no Metasploit or ExploitDB module as of 19 July 2026.

Sources