Credential Stuffing no Login Admin

Só leitura — não altera nada no alvo. Só em alvo seu ou autorizado por escrito.

O vetor

Vetor real: atacante obtém uma lista de e-mail + senha vazada de outro site ( breach da Collection #1, LinkedIn, etc.) e testa cada par no seu /api/admin/auth/login. Como a maioria das pessoas reutiliza senhas, ~0,1–1% dos pares funcionam. O atacante ganha acesso admin ao seu CMS sem nunca ter "hackeado" nada — só testou credenciais vazadas. O que testamos: (a) o login responde de forma diferente para e-mail válido vs. inválido (enumeration), (b) não há rate limit nem lockout, (c) o hash é SHA-256 rápido (GPU testa bilhões/s).

Os passos

Preparação:

BASE="https://seu-app.exemplo.workers.dev"
# Seu admin email (público no código): voce@exemplo.com

Passo 1 — Enumeration de e-mail via timing:

# E-mail inexistente (não computa hash → rápido):
curl -s -o /dev/null -X POST "$BASE/api/admin/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"naoexiste@test.com","password":"senha123"}' \
  -w "Email falso: HTTP %{http_code} | %{time_total}s\n"

# E-mail real, senha errada (computa hash SHA-256 → lento):
curl -s -o /dev/null -X POST "$BASE/api/admin/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"voce@exemplo.com","password":"senha123"}' \
  -w "Email real:  HTTP %{http_code} | %{time_total}s\n"

O que esperar

Resultado esperado (validado ao vivo 04/09/2026):

Email falso: HTTP 401 | 0.142s
Email real:  HTTP 401 | 0.815s

Confirmado: diferença de 5,7× no tempo. Atacante distingue e-mail válido de inválido medindo latência.

Passo 2 — Simular 10 tentativas seguidas (sem lockout):

for i in $(seq 1 10); do
  curl -s -o /dev/null -X POST "$BASE/api/admin/auth/login" \
    -H "Content-Type: application/json" \
    -d '{"email":"voce@exemplo.com","password":"tentativa_'$i'"}' \
    -w "Tentativa $i: HTTP %{http_code} | %{time_total}s\n"
done

Resultado esperado: 10× HTTP 401, zero bloqueio, zero delay crescente, zero CAPTCHA. Um atacante pode fazer milhares de tentativas sem interrupção.

Passo 3 — Verificar hash no código (SHA-256 = GPU-friendly):

cd /d/Antigravity-Monetizacao_TikTok/app
grep -n "SHA-256\|crypto.subtle.digest\|adminHash" app/api/admin/auth/login/route.ts

Resultado esperado:

4: const DEFAULT_ADMIN_HASH = 'f053dd0b...';
5: const DEFAULT_SALT = 'antigravity_cms_salt_2027';
61: const hashBuffer = await crypto.subtle.digest('SHA-256', data);
66: if (hashHex !== adminHash) {

🔍 SHA-256 com salt fixo. Uma GPU RTX 4090 testa ~22 bilhões de SHA-256/s. Mesmo com salt, se a senha for fraca (dicionário), é quebrável em segundos. bcrypt/argon2 (custo configurável) reduziria para milhares de tentativas por segundo.

Análise — o que isto significa:

  • Atacante confirma que voce@exemplo.com é o admin (timing difference).
  • Sem lockout, ele testa uma lista de 10.000 senhas vazadas sem interrupção.
  • Se sua senha de admin é reutilizada de outro site que vazou, ele entra.
  • O hash SHA-256 não protege se a senha for fraca — GPU quebra em segundos.

Como corrigir

Correção necessária (P1):

  1. Tempo constante: sempre executar hash compare (mesmo para e-mail inexistente — usar hash dummy).
  2. Lockout: após 5 falhas, bloquear 15min por IP + exponencial backoff.
  3. Rate limit: máx. 10 tentativas/min por IP (cf-connecting-ip).
  4. 2FA/TOTP: segundo fator obrigatório (TOTP com app Google Authenticator).
  5. Hash migration: trocar SHA-256 por bcrypt (cost ≥ 12) ou argon2id (m=64MB, t=3, p=1).
  6. HIBP: ao definir senha, verificar se está em https://api.pwnedpasswords.com/range/ (k-anonymity — só envia 5 chars do hash).

PurpleLab // plataforma de treino ofensivo + defensivo // Fase 1 — portal dinamico