Skip to content

Instantly share code, notes, and snippets.

@marcostolosa
Created October 8, 2025 23:04
Show Gist options
  • Save marcostolosa/7d40b066a22a29fe139f295ddaab1f82 to your computer and use it in GitHub Desktop.
Save marcostolosa/7d40b066a22a29fe139f295ddaab1f82 to your computer and use it in GitHub Desktop.
Script python para extrair relatórios públicos da HackerOne, busca por CWE, exibe em cores e salva em CSV
import requests
import argparse
import csv
import sys
import time
import itertools
from pyfiglet import Figlet
# -------------------------------
# Banner com animação
# -------------------------------
def print_banner():
fig = Figlet(font="standard") # fonte segura e legível
banner = fig.renderText("Extrator de Relatorios")
print("\033[96m") # cor ciano
for line in banner.splitlines():
print(line)
time.sleep(0.02) # animação suave
print("\033[0m") # reseta a cor
print(" por github.com/marcostolosa\n")
time.sleep(0.3)
# -------------------------------
# Animação de carregamento
# -------------------------------
def loading(msg, duration=3):
spinner = itertools.cycle(["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"])
end_time = time.time() + duration
while time.time() < end_time:
sys.stdout.write(f"\r{msg} {next(spinner)}")
sys.stdout.flush()
time.sleep(0.1)
sys.stdout.write("\r" + " " * (len(msg) + 2) + "\r")
# -------------------------------
# Impressão estilizada para relatórios (animação mais rápida)
# -------------------------------
def fancy_print(title, severity, url):
colors = {
"critical": "\033[91m", # vermelho brilhante
"high": "\033[38;5;208m", # laranja
"medium": "\033[38;5;226m", # amarelo brilhante (bob esponja)
"low": "\033[92m", # verde
None: "\033[94m", # azul se não houver classificação
"": "\033[94m" # string vazia também = sem classificação
}
sev_key = severity.lower() if severity else None
color = colors.get(sev_key, "\033[97m") # branco como padrão
print(f"{color}[#] Título: {title}\033[0m")
time.sleep(0.02) # mais rápido
print(f"{color} Severidade: {severity if severity else 'Sem classificação'}\033[0m")
time.sleep(0.02) # mais rápido
print(f"{color} URL: {url}\033[0m\n")
time.sleep(0.05) # mais rápido
# -------------------------------
# Banner + carregamento falso
# -------------------------------
print_banner()
loading("Conectando à API da HackerOne")
print("[+] Conectado!")
# -------------------------------
# Configuração da API
# -------------------------------
url = "https://hackerone.com/graphql"
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/json",
"X-Product-Area": "hacktivity",
"X-Product-Feature": "overview",
}
cookies = {}
# -------------------------------
# Analisa os argumentos da linha de comando
# -------------------------------
parser = argparse.ArgumentParser(description="Busca relatórios resolvidos do HackerOne por tipo de bug")
parser.add_argument("-v", "--vulnerability", nargs="+", required=True, help="Digite o nome da vulnerabilidade")
parser.add_argument("-o", "--output", help="Nome do arquivo CSV de saída (padrão: nomedobug.csv)")
parser.add_argument("-n", "--number", type=int, default=100, help="Número de relatórios a serem buscados (padrão: 100, máx 3000)")
args = parser.parse_args()
# -------------------------------
# Valida os argumentos
# -------------------------------
bug_type = " ".join(args.vulnerability)
output_file = args.output if args.output else f"{bug_type.replace(' ', '_')}.csv"
num_reports = args.number
if num_reports <= 0:
print("[-] Erro: O número de relatórios deve ser maior que 0.")
sys.exit(1)
if num_reports > 3000:
print("[-] Erro: O máximo da API do HackerOne é 3000 relatórios. Por favor, escolha 3000 ou menos.")
sys.exit(1)
# -------------------------------
# Busca relatórios com paginação
# -------------------------------
def fetch_reports(bug_type, num_reports):
all_reports = []
page_size = 100 # imposto pelo H1
for offset in range(0, num_reports, page_size):
size = min(page_size, num_reports - offset)
payload = {
"operationName": "HacktivitySearchQuery",
"variables": {
"queryString": f'cwe:("{bug_type}") AND substate:("Resolved") AND disclosed:true',
"size": size,
"from": offset,
"sort": {
"field": "latest_disclosable_activity_at",
"direction": "DESC"
},
"product_area": "hacktivity",
"product_feature": "overview"
},
"query": """query HacktivitySearchQuery($queryString: String!, $from: Int, $size: Int, $sort: SortInput!) {
me { id __typename }
search(
index: CompleteHacktivityReportIndex
query_string: $queryString
from: $from
size: $size
sort: $sort
) {
__typename
total_count
nodes {
__typename
... on HacktivityDocument {
id
_id
severity_rating
report {
id
_id
title
url
disclosed_at
}
}
}
}
}"""
}
response = requests.post(url, headers=headers, cookies=cookies, json=payload)
res = response.json()
nodes = res["data"]["search"]["nodes"]
all_reports.extend(nodes)
# para mais cedo se menos resultados forem retornados
if len(nodes) < size:
break
return all_reports
# -------------------------------
# Salvar + Imprimir
# -------------------------------
reports = fetch_reports(bug_type, num_reports)
with open(output_file, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Título", "Severidade", "URL"]) # cabeçalho
for node in reports:
title = node["report"]["title"]
severity = node["severity_rating"]
url = node["report"]["url"]
writer.writerow([title, severity, url])
# se o usuário não especificou -o, também imprime de forma estilizada
if not args.output:
fancy_print(title, severity, url)
print(f"[+] Salvos {len(reports)} relatórios sobre '{bug_type}' em {output_file}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment