
Comprobador estático sin conexión que inspecciona los jars de Java empaquetados en busca de versiones vulnerables de netty-resolver-dns y detecta si Spring WebClient realmente utiliza el resolutor DNS de Netty.
Comprobador offline para tres CVE de envenenamiento de caché DNS en io.netty:netty-resolver-dns —
CVE-2026-45674, CVE-2026-47691 y CVE-2026-45673 — y para la pregunta que el
número de versión no puede responder: ¿está tu aplicación usando realmente ese resolver?
Si usas Spring WebClient, la respuesta es muy probablemente sí, aunque
netty-resolver-dns no aparezca en tu pom.xml y nada en tu código lo llame.
java -jar netty-resolver-dns-check-0.1.0.jar path/to/your-app.jar
netty-resolver-dns llega cuatro niveles de profundidad:
spring-boot-starter-webflux
└ spring-boot-starter-reactor-netty
└ reactor-netty-http
└ reactor-netty-core
└ netty-resolver-dns (every link: compile scope)
Y no está solo en el classpath. El HttpClient de Reactor Netty — el conector que Spring Boot
elige para WebClient siempre que Reactor Netty esté presente — resuelve nombres de host con el
propio DnsAddressResolverGroup de Netty, no con el resolver del JDK:
HttpClientConfig.defaultAddressResolverGroup() → HttpResources.getOrCreateDefaultResolver()TcpResources.getOrCreateDefaultResolver() → NameResolverProvider.newNameResolverGroup(...)NameResolverProvider.newNameResolverGroup → new DnsNameResolverBuilder() → new DnsAddressResolverGroup(builder)(Lo mismo en reactor-netty 1.1.x, 1.2.x, 1.3.x y main.) El orden de detección de conectores de Spring Boot es
reactor > jetty > httpComponents > jdk, así que Reactor Netty gana siempre que esté ahí.
No nos detuvimos en leer el código fuente. En el experimento controlado de abajo, una app por defecto de Spring Boot 3.5.14
que hacía una petición WebClient envió una consulta DNS real a través del resolver de Netty
(WRITE: UDP ... DefaultDnsQuestion(example.com.)).
Los avisos de CVE-2026-45674 y CVE-2026-47691 lo dicen claramente: "Cualquier aplicación que use el resolver DNS de Netty se ve afectada."
Los tres CVE comparten la misma corrección: 4.1.135.Final (línea 4.1) / 4.2.15.Final (línea 4.2).
Leído de cada spring-boot-dependencies-<v>.pom (<netty.version>), 2026-09-11:
| Spring Boot | netty por defecto | |
|---|---|---|
| 3.4.0 – 3.4.13 | 4.1.115 – 4.1.130 | afectado — ninguna versión 3.4.x incluye la corrección |
| 3.5.0 – 3.5.14 | 4.1.121 – 4.1.132 | afectado |
| 3.5.15 + | 4.1.135 | corregido |
| 4.0.0 – 4.0.6 | 4.2.7 – 4.2.12 | afectado |
| 4.0.7 + | 4.2.15 + | corregido |
Las versiones corregidas están todas en Maven Central. Actualizar funciona; esta herramienta no afirma lo contrario.
Un número de versión es una línea de mvn dependency:tree. La pregunta más difícil es si el
resolver está realmente en uso. Así que construimos cuatro fat jars reales de Spring Boot 3.5.14 y uno en 3.5.16,
y medimos la verdad sobre el terreno — consultas DNS realmente enviadas por Netty — frente a lo que esta herramienta
infiere solo del jar:
| App | Qué hace | Consultas DNS de Netty (runtime) | Esta herramienta (estático) |
|---|---|---|---|
| A | WebClient.builder() por defecto | 1 | en uso |
| B | .resolver(DefaultAddressResolverGroup.INSTANCE) | 0 | override encontrado → comprobar manualmente |
| C | spring.http.reactiveclient.connector=jdk | 0 | override encontrado → comprobar manualmente |
| D | dos WebClients, solo uno con override | 1 | override encontrado → comprobar manualmente |
| E | por defecto, Spring Boot 3.5.16 | 1 (en el 4.1.135 corregido) | no afectado |
El resultado honesto: la herramienta dice de forma fiable "en uso" cuando no hay override, y encuentra rastros de override en tu código y configuración (3 de 3, sin falso positivo en A). No puede distinguir B (todo con override) de D (un cliente aún en el valor por defecto) — su evidencia en bytecode es idéntica. Así que un override nunca hace que diga "no afectado"; dice "comprobar manualmente", y te da la comprobación de un minuto de abajo.
--logging.level.io.netty.resolver.dns=DEBUGWRITE: UDP en el log — presente: el resolver de Netty está en uso; ausente: esa petición no lo usóNo juzgues por "¿se cargó DnsNameResolver?". La app B reemplazó el resolver y aun así cargó
esa clase, porque el grupo de resolvers por defecto se construye de forma anticipada. Solo las líneas de consulta son prueba.
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
Lee lo que está realmente empaquetado (fat jar BOOT-INF/lib, war WEB-INF/lib, jars anidados),
no pom.xml — la dependencia es transitiva, así que el pom te diría que no está ahí.
La presencia se decide por la clase io/netty/resolver/dns/DnsNameResolver.class, no por manifiestos
de versión. Los rastros de override se buscan solo en tus propias clases y en application*.properties/yml
(los jars de bibliotecas referencian estas clases por sí mismos).
La salida está en chino; las líneas de veredicto y los ids de CVE son en lo que los scripts deben basarse.
HttpClient de Reactor Netty directamente (por ejemplo una pasarela) no se
analizan por separado — si Reactor Netty está presente, se asume la ruta por defecto.API de avisos de GitHub, leída el 2026-09-11:
| CVE | GHSA | Gravedad | Afectado → corregido |
|---|---|---|---|
| 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 | igual |
| CVE-2026-45673 | GHSA-xmv7-r254-6q78 | media 6.8 | igual |
La tabla de Spring Boot está en BootTable.java; una prueba unitaria vuelve a derivar las tres afirmaciones de
la sección 2 a partir de ella, así que la tabla y las afirmaciones no pueden divergir en silencio.
evidence/ contiene las cinco apps (A–E) y runtime.sh, que cuenta las líneas WRITE: UDP por app.
tools/e2e_real_jars.py ejecuta esta herramienta contra los mismos cinco jars y verifica cada veredicto.
Compilar las apps requiere Maven 3.6.3 o superior.
| Código | Significado |
|---|---|
| 0 | ninguno de los tres CVE aplica (versión corregida) |
| 1 | versión afectada encontrada — incluso cuando se encontraron rastros de override |
| 2 | no se puede juzgar (no se encontró netty-resolver-dns, versión desconocida, argumentos incorrectos) |
| 4 | algún archivo no se pudo leer — "no pude leerlo" nunca debe parecer un aprobado |
Apache License 2.0