DOM-based XSS é uma vulnerabilidade de cross-site scripting que vive inteiramente no código client-side. O próprio JavaScript da página lê entradas controláveis pelo atacante — na maioria das vezes a partir da URL — e as escreve no DOM através de um sink inseguro como innerHTML, executando o script do atacante. Diferentemente do XSS refletido ou armazenado, o payload malicioso pode nunca aparecer em nenhuma resposta do servidor.
Como funciona um ataque de DOM XSS?
A vulnerabilidade é um fluxo de dados de um source que o atacante controla para um sink que interpreta texto como código ou marcação:
// vulnerable: fragment flows straight into a sink
const name = decodeURIComponent(location.hash.slice(1));
document.querySelector("#welcome").innerHTML = "Hello " + name;
// https://example.com/#<img src=x onerror=stealCookies()>
Sources comuns: location.hash, location.search, document.referrer, window.name, dados de postMessage e armazenamento client-side. Sinks comuns: innerHTML/outerHTML, document.write, eval e Function, o .html() do jQuery, e escritas de atributos que criam manipuladores ou URLs (onclick, href com javascript:).
Como o fragmento da URL nunca é enviado ao servidor, um payload após o # pode explorar a página enquanto o servidor vê uma requisição perfeitamente normal.
XSS refletido vs armazenado vs DOM-based
| XSS refletido | XSS armazenado | DOM-based XSS | |
|---|---|---|---|
| Onde o payload vive | Na requisição, ecoado pelo servidor | No banco de dados do servidor | Na entrada client-side (muitas vezes o fragmento da URL) |
| A resposta do servidor contém o payload? | ✓ | ✓ | Muitas vezes ✗ |
| Visível para WAFs / logs do servidor | Geralmente | Geralmente | Muitas vezes não |
| Onde é corrigido | Codificação de saída no servidor | Sanitização + codificação no servidor | Código client-side: sinks seguros, sanitização |
Por que o DOM XSS é fácil de passar despercebido
As defesas do lado do servidor inspecionam requisições e respostas; o DOM XSS pode contornar ambas. Os frameworks reduzem o risco — React e afins fazem escape por padrão — mas dangerouslySetInnerHTML, o mau uso de templates e os scripts de terceiros na página o reintroduzem. E uma página moderna executa dezenas de scripts que você não escreveu: um sink inseguro em qualquer um deles é um sink inseguro na sua página. Esse problema de composição é o cerne da segurança client-side.
Como prevenir e detectar
- Prefira sinks seguros:
textContentem vez deinnerHTML; evite completamenteevale manipuladores construídos com strings. - Sanitize quando o HTML for inevitável (DOMPurify ou a emergente Sanitizer API), e adote Trusted Types onde houver suporte — eles transformam escritas em sinks inseguros em violações de política.
- Implante uma Content Security Policy rigorosa com nonces; ela bloqueia muitos estilos de payload, embora não toda a classe de ataque.
- Observe o comportamento em tempo de execução: a exploração acaba se manifestando como scripts fazendo coisas que não deveriam — novas requisições de saída, escritas no DOM em formulários de pagamento, listeners inesperados. O monitoramento client-side da cside observa sessões reais na camada do navegador, que é o único lugar onde um ataque restrito ao DOM é sequer visível.







