Wilker commited on
Commit
e42b704
·
1 Parent(s): 3352d77

docs: relatorio do incidente de seguranca em andamento

Browse files
Files changed (1) hide show
  1. SECURITY_INCIDENT_2026-07.md +94 -0
SECURITY_INCIDENT_2026-07.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Relatório — Ataque de Fuzzing no THC LLM Space (Julho/2026)
2
+
3
+ ## Contexto
4
+ Servidor THC LLM (Hugging Face Space, https://hulktoigo-thcllm.hf.space) sob
5
+ ataque de fuzzing automatizado há cerca de 4 dias, em horários específicos do
6
+ dia. Padrão: scanner batendo em rotas conhecidas de vazamento de config
7
+ (.env, .git, actuator, streamlit, graphql, path traversal, etc).
8
+
9
+ ## Evolução do ataque
10
+ 1. Fase inicial: IP único martelando repetidamente as mesmas rotas.
11
+ 2. Fase atual: rotação de IP (botnet/proxy) — cada IP gasta uma cota de
12
+ requisições, é banido, e o bot pula pro próximo IP. Isso sozinho gera
13
+ volume suficiente pra saturar o worker único do Space (free tier).
14
+
15
+ ## Proteções implementadas em app.py (nesta ordem histórica)
16
+
17
+ ### PROTEÇÃO 1 — já existia antes deste incidente
18
+ docs_url/redoc_url/openapi_url desativados em produção (só ativos com
19
+ THC_DEBUG=true), evitando que scanners mapeiem as rotas via /openapi.json.
20
+
21
+ ### PROTEÇÃO 2 — já existia antes deste incidente
22
+ Middleware `block_known_fuzzing_patterns` com lista `_FUZZING_PATTERNS`
23
+ (.env, .git, .streamlit, actuator, graphql, path traversal codificado etc.)
24
+ bloqueando com 400 antes de consumir modelo/RAG.
25
+
26
+ ### PROTEÇÃO 3 — adicionada neste incidente
27
+ - `get_real_ip(request)`: lê X-Forwarded-For antes de request.client.host,
28
+ porque HF Spaces expõe IPs internos do proxy (10.x.x.x / 10.16.x.x /
29
+ 10.20.x.x) no client.host — não é o IP público real do atacante.
30
+ - Rate limiting em memória por IP real: janela deslizante de 60s, máximo 60
31
+ requisições, ban de 600s (10 min) ao estourar.
32
+ - `_is_authenticated_thc_key(request)`: chamadas com header X-THC-Key válido
33
+ (comparado contra THC_MASTER_CLI_KEY ou validado via verify_api_key do
34
+ módulo auth) pulam TODO o rate limit e ban — nunca é o atacante anônimo.
35
+ - Ban rápido específico para padrão de fuzzing: contador separado e mais
36
+ agressivo (`_fuzz_hits`), 5 hits em janela de 30s já bane o IP por 600s —
37
+ em vez de esperar o limite geral de 60 req/min. Isso corta o desperdício
38
+ de ~55 requisições por IP rotacionado. CONFIRMADO funcionando nos logs
39
+ (ban disparando em ~1s contra IPs novos).
40
+
41
+ ### Correção de segurança relacionada (não é sobre o ataque em si)
42
+ THC_SESSION_SECRET (usada pelo SessionMiddleware pra assinar o cookie de
43
+ sessão do login OAuth) estava com fallback para string vazia — corrigido
44
+ para RuntimeError no boot se ausente. Causou outage curto no primeiro
45
+ deploy porque a secret ainda não estava cadastrada nos Secrets do Space;
46
+ resolvido gerando com `secrets.token_hex(32)` e cadastrando separada da
47
+ THC_MASTER_CLI_KEY (são segredos diferentes, propósitos diferentes — sessão
48
+ web vs autenticação de API).
49
+
50
+ ## Estado atual (confirmado por teste real)
51
+ - Endpoint /v1/messages (formato Anthropic): funcionando perfeitamente,
52
+ autenticado, mesmo durante ataque ativo — resposta JSON completa testada.
53
+ - Endpoint /v1/messages/stream (SSE): funcionando perfeitamente, streaming
54
+ completo testado ponta a ponta (message_start → content_block_delta* →
55
+ message_stop), autenticado, durante ataque ativo.
56
+ - Bloqueio de fuzzing: confirmado ativo (400 em rotas conhecidas).
57
+ - Ban rápido: confirmado ativo, disparando em ~1s por IP rotacionado.
58
+ - Exceção X-THC-Key: confirmada ativa (chamadas autenticadas não recebem
59
+ 429 mesmo com volume de ataque simultâneo).
60
+
61
+ ## PENDÊNCIA ABERTA — não resolvida ainda
62
+ Teste em loop rápido (10 chamadas sequenciais, 1s de intervalo) contra
63
+ /v1/chat/completions (formato OpenAI) autenticado com X-THC-Key mostra
64
+ padrão intermitente: status 200 alternado com status 000 (conexão não
65
+ completa / timeout / reset — não é resposta HTTP nenhuma, então não é rate
66
+ limit nem fuzzing block, que sempre retornariam 400/429).
67
+
68
+ Hipóteses ainda não investigadas:
69
+ 1. Worker único do Space (free tier, sem --workers) saturado processando
70
+ volume do ataque, mesmo com a exceção de rate limit já pulando a lógica
71
+ de contagem/ban para chamadas autenticadas — a saturação pode estar
72
+ acontecendo em nível de SO/rede antes mesmo do código da aplicação.
73
+ 2. Throttling na camada de infraestrutura/CDN do próprio Hugging Face,
74
+ independente do código do app.py.
75
+ 3. Comportamento do curl em loop com keep-alive de conexão.
76
+
77
+ Próximo passo de diagnóstico sugerido: rodar o mesmo teste em loop com
78
+ `curl -v` pra capturar o erro real da conexão (timeout vs reset vs DNS),
79
+ repetir em horário sem pico de ataque para comparar, e verificar se o
80
+ mesmo padrão se repete em /v1/messages sob a mesma carga (o teste anterior
81
+ foi interrompido cedo demais para confirmar).
82
+
83
+ ## Decisão em aberto
84
+ Se a saturação persistir mesmo com as proteções de código, o próximo passo
85
+ sai do escopo de código e vai para: upgrade de hardware do Space (mais
86
+ CPU/workers) ou colocar uma camada de proteção externa (ex: Cloudflare
87
+ grátis) na frente do Space antes do tráfego chegar no container.
88
+
89
+ ## Nota adicional (fora do escopo do ataque)
90
+ O endpoint /v1/messages não tem Depends(get_current_user) como os
91
+ endpoints de imagem/áudio têm — passa só pelo middleware de rate
92
+ limit/fuzzing, sem exigir autenticação formal. Verificar se isso é
93
+ intencional (compatibilidade com SDK) ou lacuna a fechar depois que o
94
+ ataque atual estiver sob controle.