Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
wp2shell — PoC para CVE-2026-63030 + CVE-2026-60137, também conhecido como WP2Shell | Kitploit
Ferramentas/GitHubGitHub/crypto-cat/wp2shell
Scanners de VulnerabilidadesAnálise de CódigoExploraçãoSegurança WebAprendizado e Educação
GitHubcrypto-cat/wp2shell

wp2shell

PoC para CVE-2026-63030 + CVE-2026-60137, também conhecido como WP2Shell

Ver Repositório
3há 1 mêsAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

wp2shell

Execução remota de código pré-autenticação para WordPress 6.9.0–6.9.4 e 7.0.0–7.0.1.

Encadeia o CVE-2026-63030 (confusão de rota em lote com SQLi) com o CVE-2026-60137 (reentrada de changeset do customizer) para obter criação de administrador não autenticado e execução de comandos no sistema operacional. Nenhuma quebra de senha necessária.

wp2shell demo

Créditos a hashkitten pela descoberta; leia a análise técnica completa da SLCyber aqui.

A Vulnerabilidade

O processador de lotes da REST API do WordPress (serve_batch_request_v1) tem um bug de indexação off-by-one: quando wp_parse_url() falha em um caminho de sub-requisição, o WP_Error resultante é enviado para $validation[] mas não para $matches[]. Isso dessincroniza os dois arrays — cada requisição subsequente é despachada sob o handler errado.

Ao aninhar um lote cuidadosamente estruturado dentro de outro lote, um atacante pode:

  1. Direcionar uma requisição validada pelo schema de um endpoint através do callback de um endpoint completamente diferente
  2. Injetar SQL não sanitizado através de author__not_in (a conversão string→array ignora absint())
  3. Usar UNION SELECT para envenenar o cache de objetos do WordPress com objetos de post falsos
  4. Disparar uma publicação automática de changeset que eleva privilégios e, em seguida, reentrar na REST API com contexto de administrador

Quando a preparação estiver completa (descobrindo o prefixo da tabela e o ID do administrador), o payload de escalonamento é disparado em uma única requisição HTTP — envenenamento de cache, escalonamento de privilégios e criação de usuário acontecem todos no servidor em uma única ida e volta.

Como a Cadeia Funciona

root@kitploit:~
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)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Envenenamento de cache (7 posts falsos via UNION):

  • Um post gatilho com um shortcode [embed] no conteúdo
  • Um post de changeset (customize_changeset, status future, data no passado)
  • Um parceiro de loop externo (parent=changeset, criando o Loop 1)
  • Um alvo oEmbed (ID anti-recursão dinâmico, parent=changeset, conteúdo vazio)
  • Um post de item de menu de navegação (envenenado como post_type=nav_menu_item para a verificação is_nav_menu_item)
  • Um post de reentrada (post_type=request, post_status=parse, parent=inner)
  • Um parceiro de loop interno (parent=re-entry, criando o Loop 2)

Fluxo de Execução:

  1. O UNION envenena o cache de objetos com todos os 7 posts falsos
  2. O handler de posts renderiza o conteúdo do post gatilho → o shortcode [embed] é disparado
  3. A consulta ao cache oEmbed encontra um post de suporte com conteúdo vazio → cai para wp_update_post
  4. wp_update_post lê o changeset em cache (parent=outer) → a verificação de hierarquia detecta o Loop 1
  5. O fix-up grava o changeset no banco de dados com status future → converte automaticamente para publish
  6. _wp_customize_publish_changeset é disparado → wp_set_current_user(admin_id) → contexto de administrador ativo
  7. O changeset processa nav_menu_item[real_id] — o cache diz type=nav_menu_item → caminho UPDATE
  8. object_id resolve para um post em cache com post_parent=re-entry → wp_update_post no post real
  9. A verificação de hierarquia ( diferente de zero) detecta o Loop 2 (re-entry ↔ inner)

Uma variável de sessão MySQL anti-recursão (@_wp2s) garante que a cadeia seja disparada exatamente uma vez e não entre em loop.

Recursos

  • Três modos de extração com detecção automática: UNION (1 requisição/valor), baseado em erro via EXTRACTVALUE (~30 caracteres/requisição), busca binária booleana cega (~7 requisições/caractere)
  • RCE pré-autenticação completo — sem credenciais, sem quebra de senha, escalonamento dispara em uma única ida e volta
  • Descoberta automática — prefixo da tabela via INFORMATION_SCHEMA, ID do usuário admin via capabilities meta
  • Pós-exploração — webshell de plugin com autenticação por token, shell interativo que rastreia o diretório de trabalho (CWD), leitura/escrita de arquivos
  • Modo de limpeza — --cleanup exclui o usuário criado e remove o webshell ao sair
  • Zero dependências — apenas stdlib, arquivo único, executa em Python 3.8+

Instalação

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

Sem pip install, sem virtualenv. É um único arquivo.

Uso

Verificar se um alvo é vulnerável

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

Extrair dados

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

Exploração completa

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

Shell autenticado (com credenciais existentes)

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

Requisitos para RCE Completo

Os comandos check e read funcionam em qualquer alvo afetado. A cadeia exploit tem três requisitos adicionais:

Se o alvo usar Redis ou Memcached como cache de objetos, split_the_query é forçado a ficar ativo independentemente de per_page, e as linhas UNION são descartadas durante a busca somente por ID. O comando read ainda funciona (a extração cega não precisa que o UNION sobreviva no cache), mas exploit falhará.

Versões Afetadas

BranchVulnerávelCorrigida
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

O patch adiciona $matches[] = $single_request; para casos de erro (corrigindo o off-by-one) e uma proteção de reentrada em serve_request().

Arquitetura

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

Detalhes Técnicos

Por que /wp/v2/widgets como rota de origem?

O controller de Widgets não registra per_page, orderby ou author_exclude no schema do endpoint. Esses parâmetros passam pela validação intactos (parâmetros desconhecidos são ignorados pelo validador de schema). Quando o desync despacha essa requisição através do controller de Posts, esses valores brutos fluem diretamente para WP_Query.

Por que per_page=500?

class-wp-query.php:3375 — o split_the_query exige !empty($limits) && posts_per_page < 500. Com per_page=500, a condição 500 < 500 é falsa, então o split_the_query é desativado. A consulta completa (incluindo UNION) é executada como uma única instrução, e todas as linhas injetadas sobrevivem no conjunto de resultados e no cache.

Por que nav_menu_item[real_id] (ID positivo)?

Usar um ID de post positivo entra no caminho de UPDATE em nav-menu.php:614, que chama wp_update_post com um $post_id diferente de zero. Isso é crítico porque wp_check_post_hierarchy_for_loops em post.php:8070 retorna cedo quando $post_id = 0 (posts novos). O cache é envenenado com post_type=nav_menu_item para esse ID, para que is_nav_menu_item() passe na verificação de tipo em nav-menu.php:426. O caminho de UPDATE então dispara a verificação de hierarquia que detecta o Loop 2.

Por que dois loops de hierarquia?

O Loop 1 (changeset ↔ outer) dispara a publicação do changeset e define o contexto de administrador. O Loop 2 (re-entry ↔ inner) é disparado durante a janela de administrador (dentro da chamada save() da configuração do item de menu de navegação no loop de publicação do changeset) e aciona parse_request → reentrada na REST. Os loops são independentes porque o fix-up do Loop 2 precisa gravar o post de reentrada no banco de dados durante a janela de administrador na linha 3581 — antes do reset na linha 3589.

Aviso Legal

Esta ferramenta é publicada para fins autorizados de teste de segurança e pesquisa. Use-a apenas contra sistemas que você possua ou para os quais tenha autorização explícita por escrito para testar. O acesso não autorizado a sistemas de computador é ilegal.

Créditos

Pesquisa e desenvolvimento por CryptoCat.

Baixar ferramenta
$post_id
  • O fix-up chama wp_update_post(re-entry) → grava type=request, status=parse no banco de dados
  • wp_transition_post_status dispara do_action("parse_request") → rest_api_loaded() → serve_request()
  • A REST API reentra e reprocessa o lote inteiro com privilégios de administrador
  • POST /wp/v2/users na parte final é bem-sucedido → administrador criado → die()
  • RequisitoPorquêWordPress padrão?
    Pelo menos um post publicadoo oEmbed precisa de uma URL local para acionar o processamento de embedSim (Hello World)
    Sem cache de objetos persistenteO split-the-query precisa estar desativado para que as linhas UNION sobrevivamSim (padrão de cache de arquivo)
    API REST acessívelA reentrada via parse_request precisa do servidor RESTSim
    Escrita direta no sistema de arquivosO upload de plugins precisa de FS_METHOD=direct ou do PHP sendo dono do wp-contentSim (na maioria dos hosts)