Created
October 8, 2025 23:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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