
Exploit de prova de conceito para CVE-2026-64638: XSS refletido no login do WordPress combinado com DOM clobbering para obter assunção de conta de administrador e execução remota de código.
Software: WordPress Core ≤ 7.0.2 (todas as versões anteriores à 7.0.3)
CVSS: 8.9 (Alto)
CWE: CWE-79 — Neutralização Incorreta de Entrada Durante a Geração de Páginas Web
Autenticação Necessária: Nenhuma (Pré-Auth)
Interação do Usuário: Ativa (o admin precisa clicar em 1 link)
Impacto: XSS → Tomada de Conta → Execução Remota de Código
WordPress é o sistema de gerenciamento de conteúdo mais popular do mundo, sendo responsável por mais de 40% de todos os sites na internet. Todo site WordPress tem uma página de login em /wp-login.php — este é um endpoint público que qualquer pessoa pode acessar sem autenticação.
Quando um usuário insere um nome de usuário incorreto, o WordPress exibe uma mensagem de erro contendo exatamente o nome de usuário que o usuário acabou de digitar: “O nome de usuário X não está registrado neste site.” O problema está no fato de que o valor do nome de usuário é colocado diretamente na resposta HTML sem passar por nenhuma função de escape — um atacante só precisa inserir HTML/JavaScript em vez de um nome de usuário real, e o código será executado no navegador.
Esta é uma falha de XSS Refletido — o payload está contido na requisição e é refletido de volta de forma idêntica pelo servidor no HTML. O que a torna perigosa é que a falha reside na página de login — um local frequentemente acessado por admins, onde cookies de sessão de admin podem ser roubados.
A equipe de pesquisa descobriu ainda que este XSS pode ser encadeado com uma vulnerabilidade de DOM clobbering no emoji-loader do WordPress, permitindo que JavaScript seja carregado de um servidor externo. A partir daí, um atacante pode criar uma nova conta de admin → instalar um plugin contendo um webshell → executar código PHP no servidor. Esta cadeia de exploração é chamada de XSS2Shell.
| Atributo | Valor |
|---|---|
| CVE ID | CVE-2026-64638 |
| Pontuação CVSS | 8.9 (Alto) |
| Software | WordPress Core ≤ 7.0.2 |
| Autenticação | Nenhuma necessária (Pré-Auth) |
| Interação do Usuário | Requer 1 clique (admin clica no link) |
| Complexidade do Ataque | Alta |
| Corrigido | WordPress 7.0.3 (08/06/2026) |
| Relator | equipe pwn.ai via HackerOne |
| Relatório HackerOne | #3877102 |
O WordPress carrega suporte a emojis em todas as páginas (incluindo a página de login) através do arquivo emoji-loader.js. Este script lê a configuração de um elemento com id="wp-emoji-settings":
// Before patch (vulnerable)
const settings = JSON.parse(
document.getElementById('wp-emoji-settings').textContent
);
document.getElementById() retorna o primeiro elemento no DOM com um id correspondente. Se um atacante injeta um <div id="wp-emoji-settings"> antes da tag de script original, getElementById lerá o conteúdo do atacante em vez da configuração real. Esta técnica é chamada de DOM clobbering — sobrescrever o comportamento do JavaScript injetando elementos HTML.
A configuração de emoji contém uma URL para carregar um arquivo JavaScript (concatemoji). O atacante controla esta URL → carrega um arquivo JS de um servidor externo → executa código arbitrário no contexto do navegador.
Uma vez que a execução de JavaScript no contexto de admin é alcançada, o atacante tem privilégios totais de admin do WordPress:
/wp-admin/user-new.php com a sessão do admin/wp-admin/plugin-install.phpQualquer um dos 3 métodos acima permite a execução de código PHP no servidor — ou seja, RCE.
A partir do commit de correção 0d6d42e no wordpress-develop, identifiquei 3 locais no arquivo wp-includes/user.php onde o nome de usuário/email é colocado diretamente na mensagem de erro:
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php
Local 1 — Linha 189 (nome de usuário não existe):
Antes :
// BEFORE (vulnerable):
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
$username // ← no escaping
)

Depois :
// fixed:
sprintf(
__( 'The username <strong>%s</strong> is not registered...' ),
esc_html( $username ) // ← escaped
)
Local 2 — Linha 216 (senha incorreta):
Antes :

// BEFORE:
'<strong>' . $username . '</strong>' // ← no escaping
Depois :
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'
Local 3 — Linha 299 (senha incorreta para email):
Antes :

// BEFORE:
'<strong>' . $email . '</strong>' // ← no escaping
Depois :
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'
Fluxo de dados da requisição POST até a mensagem de erro:
$_POST['log'] ← user input from login form
↓
wp_signon() [user.php:51]
$credentials['user_login'] = wp_unslash($_POST['log']) ← only removes backslashes
↓
wp_authenticate($username, $password) [pluggable.php:689]
$username = sanitize_user($username) ← strips HTML tags, but has a bypass
↓
wp_authenticate_username_password() [user.php:153]
get_user_by('login', $username) ← user not found
↓
sprintf('The username <strong>%s</strong>...', $username) ← XSS!
↓
WP_Error → login_header() → wp_admin_notice()
wp_kses_post(...) ← filters HTML but allows <div>, <a>, through
↓
HTML response → browser render → JavaScript execute
O WordPress tem 2 camadas de filtragem antes que o nome de usuário chegue ao HTML:
Camada 1: sanitize_user() — Chama strip_tags() para remover tags HTML. No entanto, o strip_tags() do PHP tem limitações conhecidas: formatação de tag não padrão pode contornar o filtro.
Camada 2: wp_kses_post() — Permite a passagem de um subconjunto seguro de HTML, incluindo <div>, <a>, `` com certos atributos (mas remove manipuladores de eventos como onerror, onload). Crucialmente: wp_kses_post permite <div id="wp-emoji-settings"> — exatamente o elemento necessário para DOM clobbering.
A equipe pwn.ai encontrou uma maneira de contornar ambas as camadas para injetar um payload útil. Detalhes técnicos específicos não foram divulgados publicamente.
Para confirmar visualmente que o nome de usuário vai direto para o HTML sem escape, usei Xdebug + VS Code para colocar breakpoints em pontos-chave da cadeia de execução.
Passo 1 — Inserir o payload XSS no formulário de login:
Acesse http://localhost:8282/wp-login.php, insira o nome de usuário como `` e clique em Log In. Um popup de alerta aparece — o XSS funciona.
