Como Escrever um Report que Paga: do título à defesa do impacto
A estrutura de report que não gera questionamento na triagem — título, resumo de negócio, passos reproduzíveis, impacto em PII e como defender o bounty quando o triador contesta.
Achar o bug é metade do trabalho
Você passou a madrugada no Burp, trocou um id, viu a fatura de outro cliente aparecer na tela e o coração disparou: IDOR confirmado. Aí você abre a plataforma, escreve três linhas (“dá pra ver dados de outro usuário trocando o id”), anexa um print borrado e manda. Duas semanas depois volta um Need More Info ou, pior, um Informative. Zero bounty.
💡 IDOR: falha de autorização em que você troca um identificador (ex.:
id) e acessa o objeto de outro usuário. Detalhe no Glossário.
Acontece o tempo todo. O bug era real, mas o report não estava. Bug bounty é um jogo de comunicação tanto quanto de técnica: do outro lado tem um triador que recebe dezenas de reports por dia, não conhece a sua aplicação tão bem quanto o time de produto, e precisa reproduzir, validar e classificar o seu achado no menor tempo possível. Se ele não conseguir reproduzir em poucos minutos, ou não entender o impacto no negócio, o seu report perde prioridade, ou cai pra Informative.
Analogia: achar o bug é como pescar um peixe enorme. O report é como você apresenta o peixe na balança. Se chegar com o peixe escondido num saco molhado e disser “confia, é grande”, ninguém paga. Você precisa pôr na balança, fotografar e mostrar o número.
Este post é o manual do “peixe na balança”: a estrutura que faz um report ser aceito de primeira, o que separa report pago de report rejeitado, e (a parte que ninguém ensina) como defender o impacto quando o triador questiona. No fim tem um exemplo completo, anonimizado, que você pode usar de molde.
O que é um “bom report” (e por que isso importa)
Um report é o documento que transfere a sua certeza para a cabeça do triador. Você tem 100% de certeza que o bug existe — você viu. O triador tem 0%. O report é a ponte. Se a ponte tiver buracos (passo faltando, print ilegível, impacto vago), a certeza não atravessa.
A própria HackerOne descreve um report de qualidade como aquele que tem título claro e conciso, passos detalhados de reprodução com screenshots ou trechos de código, e que explica por que o problema importa. A Bugcrowd recomenda organizar a descrição em quatro blocos: Overview (resumo), Walkthrough and POC (como reproduzir), Vulnerability Evidence (evidências) e Demonstrated Impact (risco e impacto). É o mesmo esqueleto em todas as plataformas.
Analogia: pense no report como uma receita de bolo. Se a receita diz “misture os ingredientes e asse”, ninguém replica. Se diz “150g de farinha, 2 ovos, 180°C por 40 minutos”, qualquer um faz o mesmo bolo. O triador precisa fazer o seu bolo seguindo a sua receita — sem adivinhar nenhuma quantidade.
Por que um report bom paga mais (e mais rápido)
Não é só sobre “ser aceito”. A qualidade do report mexe direto no bolso:
- Triagem mais rápida = menos chance de duplicado. Em programas movimentados, o primeiro report válido e reproduzível de um bug leva o bounty; os seguintes viram
Duplicate. Um report confuso fica parado emNeed More Infoenquanto outro pesquisador manda o mesmo bug bem-feito e leva a grana. - Impacto bem demonstrado puxa a severidade pra cima. O mesmo IDOR pode ser classificado como
Médio(“dá pra ver um dado de outro usuário”) ouCrítico(“vaza PII de toda a base, violando a LGPD”). (PII é dado pessoal identificável: nome, CPF, e-mail, telefone; ver Glossário.) A diferença entre esses dois é quase só o report: quem mostra escala e nomeia o dado sensível recebe a faixa de cima. - Boa reputação destrava convites privados. Reports limpos elevam seu signal na plataforma; signal alto traz convites pra programas privados, que pagam mais e têm menos concorrência.
Na prática, falhas como IDOR/Broken Access Control pagam de algumas centenas (impacto baixo, dado pouco sensível) a dezenas de milhares (PII em escala, ação crítica). E aqui vai a regra de ouro do dinheiro:
💡 O bounty segue o impacto, e o report é quem prova o impacto. Dois pesquisadores acham o mesmo bug; quem provou o impacto maior leva mais.
Como funciona por trás: o caminho do report na triagem
Entender o que o triador faz com o seu report ajuda a escrevê-lo. O fluxo típico é:
1
2
3
4
5
6
Você envia → Triador lê o TÍTULO (decide prioridade inicial)
→ lê o RESUMO (entende o risco ao negócio)
→ segue os PASSOS (tenta REPRODUZIR — passo crítico)
→ confere as EVIDÊNCIAS (bate com o que ele viu?)
→ avalia o IMPACTO (define a SEVERIDADE / bounty)
→ classifica: Triaged / Need More Info / Informative / Duplicate / N/A
O ponto de falha mais comum é o “tenta reproduzir”. Se nesse passo faltar uma informação (um header, qual conta usar, o valor exato do parâmetro), o triador não consegue reproduzir e devolve Need More Info. Cada ida e volta atrasa dias e esfria o report. Por isso a parte de passos é a mais importante de todas: ela tem que ser à prova de ambiguidade.
A estrutura do report “que não gera questionamento”
Esta é a estrutura testada, derivada de dezenas de reports aceitos e dos guias oficiais. Cada plataforma tem campos diferentes, mas estes blocos nunca podem faltar.
Na hora de enviar, você preenche os campos do formulário da plataforma, não cola tudo num lugar só. A Bugcrowd separa em Target, VRT (você escolhe a categoria da falha, e ela já sugere a prioridade P1–P5), Bug URL e Description (é aqui que vai o seu texto: resumo, passos, PoC e impacto). A HackerOne usa Title, Asset, Weakness/Severity e Description. Escolher a categoria e o asset certos importa: errar isso já começa o report no pé errado. E um detalhe que pega quem está começando: em plataforma internacional (Bugcrowd, HackerOne), escreva em inglês — é a língua da triagem lá fora.
1. Título — [Classe] - Nome curto e direto
O título é a primeira (e às vezes única) coisa que o triador lê antes de priorizar. Fórmula:
1
2
3
4
5
6
[Classe] - O que dá pra fazer + onde
Exemplos:
[IDOR] - Exposição de faturas e NFS-e (PII) via troca de chargeId
[BFLA] - Usuário comum bloqueia loja de terceiros no /api/admin/store/block
[SQLi] - Injeção time-based no parâmetro `search` do /api/v1/produtos
- Classe entre colchetes: IDOR, XSS, SSRF, RCE, BFLA… ajuda o triador a rotear. (BFLA é abuso de função restrita, ex.: usuário comum executa ação de admin; ver Glossário.)
- Concreto, não genérico: “Exposição de faturas (PII)” é melhor que “Falha de segurança grave”. O guia da Bugcrowd pede título descritivo e conciso, identificando tipo, local e impacto.
2. Resumo — o risco para o negócio (não a parte técnica)
Este é o erro nº 1 dos iniciantes: o resumo descreve a mecânica (“o endpoint não valida o dono do objeto”) em vez do risco (“qualquer cliente autenticado lê os dados pessoais de todos os outros clientes”). O triador (e o gestor do programa que decide o bounty) pensa em negócio: dado vazado, dinheiro, reputação, conformidade legal.
1
2
3
4
5
6
7
RUIM: "O endpoint charge-details aceita um chargeId arbitrário sem checar
a sessão." (isso é mecânica, não impacto)
BOM: "Qualquer cliente autenticado consegue ler faturas e notas fiscais
— com nome completo, CPF e valores — de TODOS os outros clientes da
plataforma, sem autorização. Vazamento de PII em massa, com violação
direta da LGPD."
Analogia: o resumo é a manchete do jornal. “Cano furou no porão” não vende. “Vazamento alaga 200 apartamentos do prédio” vende. Mesma água, manchete diferente.
3. Plataforma / Alvo e 4. Contexto
Diga onde (programa, domínio, app, ex.: app.exemplo.com) e qual módulo/funcionalidade é afetado e por quê. Contexto curto orienta o triador: “Módulo Financeiro → Faturas, acessível a qualquer conta de cliente após login.”
5. Criticidade / Severidade — justificada, não chutada
💡 CVSS (Common Vulnerability Scoring System): escala de 0–10 que quantifica o risco; vem com um vetor que descreve a falha (calculadora em first.org).
Não escreva só “Crítico”. Justifique com um framework reconhecido, porque é isso que o programa usa pra calibrar bounty:
| Ferramenta | Pra que serve | Link |
|---|---|---|
| CVSS v3.1 | Score numérico (0–10) + vetor que descreve a falha | first.org/cvss/calculator/3.1 |
| CWE | Classifica o tipo de fraqueza (ex.: CWE-639 = IDOR) | cwe.mitre.org |
| VRT (Bugcrowd) | Taxonomia que mapeia a classe → severidade que os programas usam | bugcrowd.com/vulnerability-rating-taxonomy |
| OWASP Risk Rating | Severidade = Probabilidade × Impacto, focado em appsec | owasp.org/…/OWASP_Risk_Rating_Methodology |
Para o IDOR que expõe PII em massa, o vetor CVSS costuma ficar assim:
1
2
3
4
5
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N → 6.5 (Médio)
# AV:N rede | AC:L fácil | PR:L precisa de login | C:H confidencialidade alta
# Se também ALTERA dado de terceiros, I:H sobe o score pra 8.1 (Alto).
# Se o vazamento cruza a fronteira de outra conta/tenant, dá pra argumentar S:C
# (Scope Changed) — o que também eleva o número.
⚠️ Não infle. Marcar tudo como
Críticoqueima sua credibilidade. Use o framework, mostre o vetor, deixe o número falar. Se ele derMédio, defenda oMédio— e argumente o que poderia subir (escala, dado sensível) na seção de impacto.
6. Como reproduzir — a parte mais importante
Passos numerados, escritos para que qualquer pessoa reproduza sem adivinhar nada. As regras:
- Comece do zero: “criar 2 contas (A e B)”, “logar com a Conta B”, etc. Não assuma estado.
- Referencie a imagem dentro de cada passo (
PRINT1,PRINT2…). O triador lê o passo e olha o print correspondente, não um amontoado de prints no fim. - Inclua o comando/request exato, com o valor real usado (anonimizado no post, mas concreto no report). Marque o ponto da manipulação.
1
2
3
4
5
6
1. Logar como Conta B (cliente comum) e ir em Financeiro > Faturas (PRINT1).
2. Abrir uma fatura própria e interceptar a request no Burp (PRINT2):
GET /api/billing/statement/charge-details?chargeId=10051002
3. Enviar ao Repeater e trocar o chargeId pelo de outro cliente (PRINT3):
chargeId=10051002 -> chargeId=10051001 # <- único valor alterado
4. A resposta retorna a fatura de OUTRO cliente: nome, CPF e link da NFS-e (PRINT4).
Analogia: passos são o GPS turn-by-turn, não o mapa inteiro jogado na mesa. “Vire à direita em 200m” — não “se vira aí”.
7. Evidências (PoC) — prints e/ou vídeo, sempre legíveis
A Bugcrowd recomenda fortemente evidência ilustrativa, de preferência vídeo de PoC, ou screenshots no mínimo. Regras de print que paga:
- Seja explícito no que cada print mostra: legenda do tipo “resposta exibindo CPF de cliente que NÃO é a conta logada”.
- Circule/destaque o ponto crítico (o
chargeIdtrocado, o CPF que vazou). - Mostre o contexto que prova que o dado não é seu: a conta logada no canto, o nome diferente na resposta.
- Para enumeração/escala, um print do Burp Intruder com várias respostas
200de clientes distintos vale mais que mil palavras.
8. Impacto — nomeie o dado e mostre a escala
Repita o risco de negócio do resumo, agora detalhado: que dado exatamente vaza (sempre destaque PII — CPF, e-mail, telefone, endereço, dados financeiros), quantas contas dá pra afetar (escala), e o que um atacante consegue fazer com isso (fraude, engenharia social, ATO ou Account Takeover, tomada de conta da vítima). É a seção que define a faixa de bounty.
Aceito vs. Rejeitado: a tabela que importa
| O que faz ACEITAR | O que faz REJEITAR |
|---|---|
Título [Classe] concreto | Título genérico (“vulnerabilidade encontrada”) |
| Resumo em termos de negócio/PII | Resumo só técnico, sem impacto |
| Passos do zero, reproduzíveis | “É só trocar o id” sem dizer qual conta/valor |
| Request/comando exato + ponto marcado | Print único, borrado, sem contexto |
| Impacto com dado nomeado + escala | “Pode ser perigoso” (sem provar nada) |
| Severidade justificada (CVSS/CWE/VRT) | “Crítico” sem justificativa, ou inflado |
| Certeza total antes de enviar | Enviar “na dúvida, vai que cola” |
| Respeito ao escopo do programa | Reportar algo fora de escopo |
A lição mais repetida por quem vive de triagem: relatório bem feito + certeza absoluta da vulnerabilidade = aprovação rápida. Tenha certeza antes de reportar; report no chute queima reputação.
Como DEFENDER o impacto quando o triador questiona
Vai acontecer: você manda um report sólido e o triador responde “impacto baixo”, “não vemos risco real” ou rebaixa a severidade. Isso não é o fim. É uma negociação, e tem um roteiro que já reverteu downgrade:
- Confirme que NÃO está fora de escopo. Releia a policy. Defender algo fora de escopo é perder tempo (e signal). Se está no escopo, siga.
- Escreva um argumento detalhado do impacto. Não responda “discordo”. Explique, em termos de negócio, o pior cenário realista: “Com enumeração de IDs sequenciais, um atacante baixa a base inteira de PII; isso é vazamento de dados pessoais sob a LGPD, com risco de multa e dano reputacional.”
- Mostre que a falha é aceita/paga por outras empresas. Diga que essa classe é reconhecida e recompensada em vários programas, não é um caso de borda.
- Linke reports públicos da mesma classe já pagos (precedente). Esse é o golpe de misericórdia. Repositórios como o reddelexc/hackerone-reports reúnem reports públicos por tipo de bug. Um link de um IDOR/PII pago num programa parecido tira o “é só teoria” da mesa.
Analogia: é como contestar uma multa. Não adianta dizer “achei injusto”. Você junta a foto, o artigo da lei e um caso anterior em que a mesma multa foi cancelada. Evidência + precedente ganha a discussão.
Dois aprendizados práticos sobre triagem que mudam o jogo:
Informativenão é necessariamente o fim. Há casos de bônus pago meses depois por persistência. Vale manter o report aberto e acompanhar com educação. Falhas tidas como “fracas” (rate limit, por exemplo) já renderam bounty quando bem argumentadas e dentro do escopo.- Não junte achados distintos. Um XSS em dois campos diferentes da mesma página = dois reports. Juntar pode fazer o programa pagar por um só. Já o mesmo bug em vários subdomínios = um report (juntar evita auto-duplicação).
💡 Caso real (report meu, aceito na NASA): mandei um Open Redirect pro VDP da NASA (no Bugcrowd) e ele foi marcado como Triaged (validado na triagem). Essa classe costuma cair pra informativo, e o que segurou a severidade foi provar que era controle faltando, não comportamento intencional: no report eu mostrei que endpoints-irmãos do mesmo host validavam o destino e só aquele esquecia, ainda por cima num host crítico de SSO (onde o usuário digita a senha). É o roteiro acima na prática: o fato desarmando o “é de propósito”. (O endpoint e o PoC completo eu seguro até a NASA corrigir — disclosure responsável; vira um post próprio depois.)
Erros comuns que derrubam reports
- Resumo técnico em vez de risco de negócio: o erro nº 1 (já cobrimos).
- Passos com buracos, “logue e troque o id” sem dizer com qual conta nem qual valor.
- Print único e ilegível, sem destacar o ponto, sem mostrar que o dado é de outra pessoa.
- Não provar escala: mostrar 1 caso quando dava pra mostrar que afeta a base inteira (a escala é o que vira
Crítico). - Severidade inflada, marcar
Críticosem vetor CVSS coerente queima credibilidade. - Reportar sem ler o template/CVE: nem todo achado de scanner é bug. Ferramentas como o Nuclei apontam coisas que, ao ler o que o template realmente faz, não têm impacto. Entenda a CVE antes de reportar.
- Reportar no chute, sem ter 100% de certeza de que é reproduzível.
- Ignorar o escopo: reportar ativo/classe fora de escopo, ou usar técnica proibida (ex.: automação pesada onde a policy proíbe).
Exemplo completo de report (fictício-realista, anonimizado)
Cenário fictício, baseado em padrões reais de bug bounty. Domínios, IDs e dados são placeholders.
Título: [IDOR] - Exposição de faturas e NFS-e (PII) via troca de chargeId no Financeiro
Plataforma / Alvo: HackerOne · app.exemplo.com
Resumo (risco ao negócio): Qualquer cliente autenticado consegue ler as faturas e notas fiscais (NFS-e) de todos os outros clientes da plataforma (incluindo nome completo, CPF e valores) apenas trocando o parâmetro chargeId na request. Como os IDs são sequenciais, é possível enumerar e extrair a base inteira de PII. Vazamento de dados pessoais em massa, com violação direta da LGPD e risco de fraude/engenharia social contra os clientes.
Contexto: Módulo Financeiro → Faturas, disponível a qualquer conta de cliente após o login. O endpoint que carrega o detalhe da cobrança não valida se o chargeId solicitado pertence ao usuário da sessão.
Criticidade: Alto → Crítico
- CWE-639: Authorization Bypass Through User-Controlled Key.
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N → 6.5 (Médio) é o piso pela leitura de um único objeto. Como o acesso cruza a fronteira de outras contas (autorização de um componente expõe dado de outro), o vetor sobe pra CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N → 7.7 (Alto) com S:C (Scope Changed).
- Argumento de severidade: somando a escala (base inteira via IDs sequenciais) e a sensibilidade do dado (CPF + financeiro, sob LGPD), o impacto de negócio é tratado como Crítico pela maioria dos programas. É o número que defendo na seção de impacto.
- VRT (Bugcrowd): Broken Access Control → IDOR → Sensitive Data Disclosure.
Como reproduzir:
- Criar/usar 2 contas de cliente comum (Conta A = vítima; Conta B = atacante). Logar como Conta B e ir em Financeiro → Faturas (PRINT1).
Abrir uma fatura própria e interceptar a request no Burp (PRINT2):
1 2 3
GET /api/billing/statement/charge-details?chargeId=10051002 HTTP/2 Host: app.exemplo.com Authorization: Bearer <token_da_Conta_B>
Enviar ao Repeater e trocar apenas o
chargeIdpelo de uma cobrança da Conta A (PRINT3):1 2 3
GET /api/billing/statement/charge-details?chargeId=10051001 HTTP/2 # <- único valor alterado Host: app.exemplo.com Authorization: Bearer <token_da_Conta_B>A resposta retorna a fatura da Conta A, com PII (PRINT4):
1 2 3 4 5
HTTP/2 200 OK Content-Type: application/json {"chargeId":10051001,"customerName":"<nome da Conta A>","cpf":"000.000.000-00", "invoiceUrl":"/files/nfse/10051001.pdf","amount":299.50}
- Provar escala: no Burp Intruder, posição
SnipernochargeId, payload numérico10050000–10052000. Centenas de respostas200 OKcom clientes distintos confirmam o vazamento em massa (PRINT5).
Evidências (PoC):
- PRINT1: tela de Faturas logado como Conta B.
- PRINT2/PRINT3: Repeater com o
chargeIdoriginal e o trocado (destacado em vermelho). - PRINT4: resposta exibindo
customerName/cpfque não são da conta logada. - PRINT5: Intruder com múltiplos
200de clientes diferentes (prova de escala).
Impacto: Leitura não autorizada de faturas e NFS-e de toda a base de clientes: nome completo, CPF, valores e link de nota fiscal. Como o chargeId é sequencial, o vazamento é automatizável e em massa. Habilita fraude, engenharia social direcionada e configura tratamento irregular de dados pessoais sob a LGPD.
Severidade sugerida: Crítico. Correção: validar, no servidor, que o chargeId solicitado pertence ao usuário da sessão (autorização object-level).
Defesa em camadas (pro report ficar redondo, e pra você saber o que recomendar)
Um report bom também aponta a correção. A raiz do exemplo é falta de checagem de propriedade no servidor:
1
2
3
4
5
6
7
8
// ERRADO — confia no chargeId que veio do cliente, sem checar o dono
$fatura = Fatura::find($_GET['chargeId']);
return response()->json($fatura); // entrega pra qualquer um autenticado
// CORRETO — só devolve se a cobrança for do usuário logado (object-level)
$fatura = Fatura::where('id', $request->chargeId)
->where('cliente_id', auth()->id()) // <- a checagem que faltava
->firstOrFail(); // 404 se não for dono
1
2
3
// Node/Express — autorização por dono do objeto
const fatura = await Fatura.findOne({ _id: req.query.chargeId, clienteId: req.user.id });
if (!fatura) return res.sendStatus(404); // não vaza nem a existência do recurso
Princípios que matam a classe inteira: deny by default, nunca confiar em identificador vindo do cliente pra decidir autorização, IDs imprevisíveis (UUID v4) como reforço, que não substituem a checagem de dono, e teste de autorização no CI (“Conta B não acessa objeto de Conta A”). (A fundo no post de Broken Access Control.)
Ferramentas que ajudam a montar o report
- Burp Suite: Repeater (mostrar a troca), Intruder (provar escala), e o próprio histórico pra extrair a request exata.
- CVSS Calculator (FIRST.org) gera o vetor e o score pra justificar a severidade: first.org/cvss/calculator/3.1.
- VRT da Bugcrowd mapeia a classe → severidade esperada: bugcrowd.com/vulnerability-rating-taxonomy.
- Gravador de tela (OBS, asciinema pra CLI): vídeo de PoC é o que mais convence em casos complexos.
- Editor de imagem (Flameshot, Greenshot) pra circular/anotar prints. Print sem destaque é metade do print.
Checklist do report que paga
- Título
[Classe] - o que dá pra fazer + onde, concreto. - Resumo em termos de negócio/PII, não de mecânica.
- Plataforma, alvo e contexto (módulo afetado) ditos.
- Severidade justificada com CVSS + CWE (+ VRT).
- Passos numerados, do zero, qualquer um reproduz.
- Request/comando exato com o ponto da manipulação marcado.
- Cada passo referencia o print correspondente (PRINT1, PRINT2…).
- Prints legíveis, destacados e que provam que o dado é de outra pessoa.
- Escala demonstrada (Intruder / enumeração) quando aplicável.
- Impacto com dado nomeado (PII) e o que o atacante faz.
- Sugestão de correção incluída.
- Conferi o escopo e tenho certeza de que reproduz.
O que você precisa lembrar
- O report transfere a sua certeza pro triador: sem buracos, ele reproduz e paga.
- Resumo = risco ao negócio (PII, dinheiro, LGPD); mecânica fica nos passos.
- Passos reproduzíveis do zero + prints destacados é o que mais derruba ou aprova report.
- Impacto + escala definem o bounty; nomeie o dado e mostre quantas contas afeta.
- Triador questionou? Escopo → argumento → “é pago por outras empresas” → link de precedente.
💡 Dica de ouro: escreva o report pensando “um pesquisador que nunca viu essa aplicação consegue reproduzir só com o que está escrito aqui?”. Se a resposta é sim, o triador também consegue — e report que reproduz de primeira é report que paga.
Nota ética
Tudo aqui é pra testes autorizados: programas de bug bounty dentro do escopo, pentests contratados e labs legais. Ao reportar, respeite a policy, não exfiltre mais dado do que o necessário pra provar o bug (em PoC de PII, mascare CPF/dados nos prints) e pratique disclosure responsável: dê tempo pro programa corrigir antes de tornar público. Report existe pra proteger quem usa o sistema, não pra exibir acesso indevido.
Referências
- HackerOne — Quality Reports
- HackerOne — Report Templates
- Bugcrowd Docs — Reporting a Bug
- Bugcrowd — How to write excellent reports (LevelUp)
- Bugcrowd — Vulnerability Rating Taxonomy (VRT)
- FIRST.org — CVSS v3.1 Calculator
- MITRE — CWE-639: Authorization Bypass Through User-Controlled Key
- OWASP — Risk Rating Methodology
- reddelexc/hackerone-reports — reports públicos por tipo de bug
Próximo na série: aplique este modelo no post de Broken Access Control — IDOR, BOLA e BFLA · base: Recon & Discovery
📚 Parte do Guia Completo de Bug Bounty — o índice da série, do básico ao avançado.
