
Verificador estático offline que inspeciona jars Java empacotados em busca de versões vulneráveis do netty-resolver-dns e detecta se o Spring WebClient realmente usa o resolvedor DNS do Netty.
Verificador offline para três CVEs de envenenamento de cache DNS em io.netty:netty-resolver-dns —
CVE-2026-45674, CVE-2026-47691 e CVE-2026-45673 — e para a pergunta que o
número da versão não consegue responder: sua aplicação está realmente usando esse resolver?
Se você usa Spring WebClient, a resposta é muito provavelmente sim, mesmo que
netty-resolver-dns não esteja em lugar nenhum do seu pom.xml e nada no seu código o chame.
java -jar netty-resolver-dns-check-0.1.0.jar path/to/your-app.jar
netty-resolver-dns chega quatro níveis abaixo:
spring-boot-starter-webflux
└ spring-boot-starter-reactor-netty
└ reactor-netty-http
└ reactor-netty-core
└ netty-resolver-dns (every link: compile scope)
E ele não está apenas no classpath. O HttpClient do Reactor Netty — o conector que o Spring Boot
escolhe para o WebClient sempre que o Reactor Netty está presente — resolve nomes de host com o
próprio DnsAddressResolverGroup do Netty, não com o resolver da JDK:
HttpClientConfig.defaultAddressResolverGroup() → HttpResources.getOrCreateDefaultResolver()TcpResources.getOrCreateDefaultResolver() → NameResolverProvider.newNameResolverGroup(...)NameResolverProvider.newNameResolverGroup → new DnsNameResolverBuilder() → new DnsAddressResolverGroup(builder)(O mesmo em reactor-netty 1.1.x, 1.2.x, 1.3.x e main.) A ordem de detecção de conectores do Spring Boot é
reactor > jetty > httpComponents > jdk, então o Reactor Netty vence sempre que está presente.
Não paramos na leitura do código-fonte. No experimento controlado abaixo, um app padrão Spring Boot 3.5.14
fazendo uma requisição WebClient enviou uma consulta DNS real através do resolver do Netty
(WRITE: UDP ... DefaultDnsQuestion(example.com.)).
Os avisos para CVE-2026-45674 e CVE-2026-47691 dizem claramente: "Qualquer aplicação que use o resolver DNS do Netty é impactada."
Todas as três CVEs compartilham a mesma correção: 4.1.135.Final (linha 4.1) / 4.2.15.Final (linha 4.2).
Lido de cada spring-boot-dependencies-<v>.pom (<netty.version>), 2026-09-11:
| Spring Boot | netty padrão | |
|---|---|---|
| 3.4.0 – 3.4.13 | 4.1.115 – 4.1.130 | afetado — nenhuma versão 3.4.x traz a correção |
| 3.5.0 – 3.5.14 | 4.1.121 – 4.1.132 | afetado |
| 3.5.15 + | 4.1.135 | corrigido |
| 4.0.0 – 4.0.6 | 4.2.7 – 4.2.12 | afetado |
| 4.0.7 + | 4.2.15 + | corrigido |
As versões corrigidas estão todas no Maven Central. A atualização funciona; esta ferramenta não afirma o contrário.
Um número de versão é uma linha de mvn dependency:tree. A pergunta mais difícil é se o
resolver está realmente em uso. Então construímos quatro fat jars reais de Spring Boot 3.5.14 e um em 3.5.16,
e medimos a verdade absoluta — consultas DNS realmente enviadas pelo Netty — contra o que esta ferramenta
infere apenas do jar:
| App | O que faz | Consultas DNS do Netty (runtime) | Esta ferramenta (estático) |
|---|---|---|---|
| A | WebClient.builder() padrão | 1 | em uso |
| B | .resolver(DefaultAddressResolverGroup.INSTANCE) | 0 | override encontrado → verificar manualmente |
| C | spring.http.reactiveclient.connector=jdk | 0 | override encontrado → verificar manualmente |
| D | dois WebClients, apenas um sobrescrito | 1 | override encontrado → verificar manualmente |
| E | padrão, Spring Boot 3.5.16 | 1 (no 4.1.135 corrigido) | não afetado |
O resultado honesto: a ferramenta diz de forma confiável "em uso" quando não há override, e encontra vestígios de override no seu código e configuração (3 de 3, sem falso positivo em A). Ela não consegue distinguir B (tudo sobrescrito) de D (um cliente ainda no padrão) — as evidências de bytecode são idênticas. Então um override nunca faz com que ela diga "não afetado"; ela diz "verificar manualmente", e fornece a verificação de um minuto abaixo.
--logging.level.io.netty.resolver.dns=DEBUGWRITE: UDP no log — presente: o resolver do Netty está em uso; ausente: aquela requisição não o usouNão julgue por "o DnsNameResolver foi carregado?". O App B substituiu o resolver e ainda carregou
essa classe, porque o grupo de resolvers padrão é construído antecipadamente. Apenas as linhas de consulta são prova.
java -jar netty-resolver-dns-check-0.1.0.jar <directory | jar | war>
java -jar netty-resolver-dns-check-0.1.0.jar --version-of 4.1.132.Final
Ele lê o que está realmente empacotado (fat jar BOOT-INF/lib, war WEB-INF/lib, jars aninhados),
não o pom.xml — a dependência é transitiva, então o pom diria que ela não está lá.
A presença é decidida pela classe io/netty/resolver/dns/DnsNameResolver.class, não por manifestos
de versão. Vestígios de override são procurados apenas nas suas próprias classes e em application*.properties/yml
(jars de bibliotecas referenciam essas classes por conta própria).
A saída está em chinês; as linhas de veredito e os ids das CVEs são o que os scripts devem usar como chave.
HttpClient do Reactor Netty diretamente (por exemplo, um gateway) não são
analisadas separadamente — se o Reactor Netty está presente, o caminho padrão é assumido.API de avisos do GitHub, lida em 2026-09-11:
| CVE | GHSA | Severidade | Afetado → corrigido |
|---|---|---|---|
| CVE-2026-45674 | GHSA-676x-f7gg-47vc | alta 8.7 | <= 4.1.134.Final → 4.1.135.Final · 4.2.0–4.2.14 → 4.2.15.Final |
| CVE-2026-47691 | GHSA-5pvg-856g-cp85 | alta 8.7 | mesma |
| CVE-2026-45673 | GHSA-xmv7-r254-6q78 | média 6.8 | mesma |
A tabela do Spring Boot está em BootTable.java; um teste unitário re-deriva as três afirmações da
seção 2 a partir dela, então a tabela e as afirmações não podem divergir silenciosamente.
evidence/ contém os cinco apps (A–E) e runtime.sh, que conta as linhas WRITE: UDP por app.
tools/e2e_real_jars.py executa esta ferramenta contra os mesmos cinco jars e verifica cada veredito.
Construir os apps requer Maven 3.6.3 ou mais recente.
| Código | Significado |
|---|---|
| 0 | nenhuma das três CVEs se aplica (versão corrigida) |
| 1 | versão afetada encontrada — inclusive quando vestígios de override foram encontrados |
| 2 | não é possível julgar (nenhum netty-resolver-dns encontrado, versão desconhecida, argumentos inválidos) |
| 4 | algum arquivo não pôde ser lido — "não consegui ler" nunca deve parecer um passe |
Apache License 2.0