
Laboratório Docker para reproduzir o CVE-2025-11262, um XSS cego armazenado não autenticado no plugin Link Whisper Free para WordPress. Inclui alvos vulneráveis e corrigidos, script PoC e análise de causa raiz.
Este repositório contém um laboratório Docker local para reproduzir a CVE-2025-11262, uma vulnerabilidade de cross-site scripting armazenado não autenticado que afeta o plugin WordPress Link Whisper Free.
O laboratório compara duas versões do plugin:
| Serviço | Versão do plugin | Finalidade | URL |
|---|---|---|---|
vuln | 0.9.0 | Alvo vulnerável | http://127.0.0.1:8081 |
patched | 0.9.1 | Alvo corrigido para comparação | http://127.0.0.1:8082 |
A cadeia de vulnerabilidade demonstrada é:
Requisição REST não autenticada
→ user_id controlado pelo atacante é persistido
→ um usuário privilegiado do WordPress abre a página de Assinatura AI do Link Whisper
→ o valor armazenado é renderizado em um contexto JavaScript do admin
→ alert("CVE-2025-11262-LAB") é executado na versão vulnerável
O atacante não precisa estar logado para plantar o payload armazenado. O JavaScript é executado posteriormente quando um usuário privilegiado do WordPress abre a página de administração afetada.
Este laboratório foi projetado apenas para pesquisa local controlada, entendimento do código-fonte e demonstração de portfólio.
| Afirmação | Evidência | Como verificar neste laboratório |
|---|---|---|
Link Whisper Free 0.9.0 é vulnerável. | Avisos públicos identificam Link Whisper Free até a versão 0.9.0 inclusive como afetada. | Execute o PoC contra http://127.0.0.1:8081 e abra a URL de administração impressa. |
Link Whisper Free 0.9.1 contém a correção. | Aviso público e dados de changelog identificam 0.9.1 como a versão corrigida. | Execute o mesmo PoC contra http://127.0.0.1:8082; nenhum alerta deve aparecer. |
| O plantio do payload é não autenticado. | O PoC envia uma requisição POST sem cookies, login ou nonce do WordPress. | Inspecione poc/poc.py; ele só requer --url. |
| O impacto visível é acionado na área de administração do WordPress. | O valor armazenado é renderizado quando a página de Assinatura AI do Link Whisper é aberta por um usuário privilegiado. | Após executar o PoC, faça login como admin e abra a URL de administração impressa. |
O alvo corrigido ainda pode retornar "ok" na camada HTTP. | Testes locais mostraram que ambos os alvos podem retornar "ok"; a diferença significativa é se o payload é persistido e executado. | Compare o comportamento do navegador em 8081 e 8082. |
Este é o resumo da Causa Raiz para substituir no README. É publicamente seguro, não menciona arquivos internos como vuln_detail.txt e é baseado no laboratório/fonte que você está usando agora.
A CVE-2025-11262 é causada por uma cadeia de injeção de JavaScript armazenado no Link Whisper Free 0.9.0.
O problema não é uma única chamada de escape ausente. É uma cadeia de múltiplos comportamentos inseguros:
Endpoint REST não autenticado
→ validação insuficiente de user_id
→ armazenamento persistente em wpil_ai_access_user_id
→ renderização insegura em um contexto JavaScript do admin
→ XSS armazenado quando um usuário privilegiado abre a página de Assinatura AI
O Link Whisper Free registra um endpoint REST de autenticação AI sob o namespace REST do plugin:
const REST_SLUG = 'link-whisper';
const AI_AUTH = 'ai-auth';
O endpoint é registrado como uma rota POST:
register_rest_route(self::REST_SLUG, self::AI_AUTH, [
'methods' => 'POST',
'callback' => [
$this,
'ai_auth_handler'
],
'permission_callback' => "__return_true",
'show_in_index' => false
]);
Como o callback de permissão é __return_true, o endpoint é acessível sem autenticação.
No laboratório, o endpoint efetivo é:
/wp-json/link-whisper/ai-auth
Isso significa que um atacante não autenticado pode enviar uma requisição ao endpoint sem uma sessão WordPress, nonce ou conta de administrador.
No Link Whisper Free 0.9.0, o manipulador lê parâmetros controlados pelo atacante da requisição REST:
public function ai_auth_handler( WP_REST_Request $request )
{
if(!empty($request->get_param('access_token'))){
$token = $request->get_param('access_token');
$user_id = $request->get_param('user_id');
$uid = (int)$request->get_param('uid');
$uemail = $request->get_param('uemail');
if(!empty($token) && false !== strpos($token, 'ai-')){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', $uemail);
update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);
update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);
update_option('wpil_ai_access_authorized', true);
}
return 'ok';
}
return new WP_Error(400, 'Bad request', [ 'status' => 404 ]);
}
O comportamento vulnerável é a condição de validação fraca:
if(!empty($token) && false !== strpos($token, 'ai-')){
Isso apenas verifica se o token de acesso fornecido contém a string ai-.
Não há validação rigorosa de user_id antes de ser armazenado:
update_option('wpil_ai_access_user_id', $user_id);
Como resultado, JavaScript controlado pelo atacante pode ser persistido na tabela de opções do WordPress.
O valor user_id controlado pelo atacante é armazenado na opção do WordPress:
wpil_ai_access_user_id
Neste laboratório, o PoC envia o seguinte payload local:
</script><script>alert("CVE-2025-11262-LAB")</script>
No serviço vulnerável, o payload é armazenado como o valor de wpil_ai_access_user_id.
O atacante não precisa estar logado para plantar o payload. O payload é plantado através do endpoint REST não autenticado.
O valor armazenado é posteriormente recuperado através da lógica de configurações do plugin:
public static function get_linkwhisper_ai_user_id(){
return get_option('wpil_ai_access_user_id', '');
}
O valor é atribuído a $ai_id e renderizado na página de administração da Assinatura AI.
No Link Whisper Free 0.9.0, o valor é inserido diretamente em uma string JavaScript:
body: JSON.stringify({
ai_id: "<?php echo $ai_id;?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? $sub->subscription_id: null;?>"
})
Como $ai_id não é escapado antes de ser inserido no contexto JavaScript, um payload armazenado pode quebrar a string pretendida e executar JavaScript quando a página de administração for aberta.
Com o payload do laboratório, a saída vulnerável renderizada torna-se equivalente a:
body: JSON.stringify({
ai_id: "</script><script>alert("CVE-2025-11262-LAB")</script>",
subscription_id: ""
})
Em um navegador, a tag </script> injetada termina o bloco de script original, e o bloco <script> injetado é executado.
O payload é plantado por um atacante não autenticado, mas a execução requer que um usuário privilegiado do WordPress abra a página de administração afetada:
/wp-admin/admin.php?page=link_whisper_ai_subscription
Neste laboratório, a página afetada é aberta como administrador do WordPress para acionar o diálogo de alerta.
Isso torna o problema um XSS armazenado não autenticado direcionado a administradores do WordPress autenticados ou usuários privilegiados que podem acessar a página de administração da Assinatura AI do Link Whisper.
O Link Whisper Free 0.9.1 adiciona validação mais rigorosa antes de armazenar os valores de autenticação AI.