
CVE Reproduction: cve-2026-63030_60137-wordpress_rce_reproduction
Discoverer: Searchlight Cyber (via GPT-5.6 Sol)
Severity: Critical — CVSS 9.8 (CVE-2026-63030), CVSS 9.1 (CVE-2026-60137)
Status: Actively exploited — added to CISA KEV
Alias: "wp2shell"
This is an unauthenticated remote code execution chain in WordPress Core composed of two CVEs:
| CVE | Component | Type | CWE |
|---|---|---|---|
| CVE-2026-63030 | WP REST API batch endpoint | Route confusion / access-control bypass | CWE-436 |
| CVE-2026-60137 | WP_Query author__not_in parameter | SQL injection | CWE-89 |
Individually each flaw is serious; combined they allow an unauthenticated attacker to execute arbitrary PHP on the target and deploy webshells.
The WordPress REST API exposes a batch endpoint at /wp-json/wp/v2/batch. Due to a flaw in route resolution (introduced in WP 6.9's rewritten routing middleware), the batch processor does not correctly validate that internal sub-requests in a batch inherit the authentication and permission checks of the outer request. An attacker can craft a batch payload that:
wp/v2/users, wp/v2/plugins) without the required capability checks.WP_REST_Server::dispatch() to pass forged route parameters to internal handlers.This bypasses all authentication guards for read operations and some write operations when combined with parameter smuggling.
author__not_in SQL InjectionThe WP_Query class accepts an author__not_in parameter intended to exclude posts by specific author IDs. A type-coercion bug in the WP_Query::get_posts() method (introduced in WP 6.9's query sanitization refactor) causes author__not_in values to be interpolated directly into the SQL WHERE clause without proper parameterisation when supplied as a specially crafted associative array.
The injection occurs in the post_author IN (...) generated SQL. By nesting arrays with raw SQL fragments an attacker can break out of the IN clause and execute arbitrary UNION / INTO OUTFILE statements — provided the database user has FILE privilege.
Attacker (unauthenticated)
│
├─ POST /wp-json/wp/v2/batch
│ └─ CVE-2026-63030: Bypass auth, invoke internal query API
│ └─ CVE-2026-60137: Inject SQL via author__not_in
│ └─ UNION SELECT ... INTO OUTFILE 'webshell.php'
│ └─ Webshell deployed to web root
│ └─ RCE achieved
The batch endpoint provides the vector; the SQLi provides the payload delivery.
| Version range | Full chain |
|---|---|
| WordPress 6.9.0 – 6.9.4 | ✅ Both CVEs present |
| WordPress 7.0.0 – 7.0.1 | ✅ Both CVEs present |
| WordPress ≤ 6.8.x | CVE-2026-63030 only (SQLi not exploitable) |
Patches were shipped in WordPress 6.9.5 and 7.0.2 (both issued 2026-07-16).
/wp-json/wp/v2/batch returns 200 (not 403/404).--target flag (see below)./wp-content/webshell.php?cmd=id.python exploit.py --target https://target.example.com --action deploy-webshell
python exploit.py --target https://target.example.com --action exec --command "id"
python exploit.py --target https://target.example.com --action exec --command "uname -a"
POST /wp-json/wp/v2/batch at the WAF / reverse proxy.FILE privilege from the WordPress MySQL user.define('DISALLOW_FILE_EDIT', true); and define('DISALLOW_FILE_MODS', true);..php files under wp-content/.author__not_in with non-integer values in request logs.