
Автономный статический анализатор, который проверяет упакованные Java-архивы на наличие уязвимых версий netty-resolver-dns и определяет, действительно ли Spring WebClient использует DNS-резолвер Netty.
Автономный чекер для трёх CVE, связанных с отравлением DNS-кэша, в io.netty:netty-resolver-dns —
CVE-2026-45674, CVE-2026-47691 и CVE-2026-45673 — а также для вопроса, на который
номер версии ответить не может: действительно ли ваше приложение использует этот резолвер?
Если вы используете Spring WebClient, ответ, скорее всего, да, даже если
netty-resolver-dns нигде нет в вашем pom.xml и ничто в вашем коде его не вызывает.
java -jar netty-resolver-dns-check-0.1.0.jar path/to/your-app.jar
netty-resolver-dns приходит на четыре уровня вглубь:
spring-boot-starter-webflux
└ spring-boot-starter-reactor-netty
└ reactor-netty-http
└ reactor-netty-core
└ netty-resolver-dns (каждое звено: compile scope)
И он не просто присутствует в classpath. HttpClient из Reactor Netty — коннектор, который Spring Boot
выбирает для WebClient всякий раз, когда присутствует Reactor Netty — разрешает имена хостов с помощью
собственного DnsAddressResolverGroup из Netty, а не резолвера JDK:
HttpClientConfig.defaultAddressResolverGroup() → HttpResources.getOrCreateDefaultResolver()TcpResources.getOrCreateDefaultResolver() → NameResolverProvider.newNameResolverGroup(...)NameResolverProvider.newNameResolverGroup → new DnsNameResolverBuilder() → new DnsAddressResolverGroup(builder)(То же самое в reactor-netty 1.1.x, 1.2.x, 1.3.x и main.) Порядок определения коннектора в Spring Boot —
reactor > jetty > httpComponents > jdk, поэтому Reactor Netty побеждает всякий раз, когда он есть.
Мы не остановились на чтении исходников. В контролируемом эксперименте ниже приложение на стандартном
Spring Boot 3.5.14, выполняющее один запрос через WebClient, отправило один реальный DNS-запрос через
резолвер Netty (WRITE: UDP ... DefaultDnsQuestion(example.com.)).
В рекомендациях по CVE-2026-45674 и CVE-2026-47691 сказано прямо: «Любое приложение, использующее DNS-резолвер Netty, затронуто».
Все три CVE имеют одно и то же исправление: 4.1.135.Final (линейка 4.1) / 4.2.15.Final (линейка 4.2).
Данные взяты из каждого spring-boot-dependencies-<v>.pom (<netty.version>), 2026-09-11:
| Spring Boot | netty по умолчанию | |
|---|---|---|
| 3.4.0 – 3.4.13 | 4.1.115 – 4.1.130 | уязвимо — ни один релиз 3.4.x не содержит исправления |
| 3.5.0 – 3.5.14 | 4.1.121 – 4.1.132 | уязвимо |
| 3.5.15 + | 4.1.135 | исправлено |
| 4.0.0 – 4.0.6 | 4.2.7 – 4.2.12 | уязвимо |
| 4.0.7 + | 4.2.15 + | исправлено |
Исправленные версии есть в Maven Central. Обновление работает; этот инструмент не утверждает обратного.
Номер версии — это одна строка из mvn dependency:tree. Более сложный вопрос — действительно ли
резолвер используется. Поэтому мы собрали четыре реальных fat jar на Spring Boot 3.5.14 и один на 3.5.16
и измерили истину в последней инстанции — DNS-запросы, фактически отправленные Netty — в сравнении с тем,
что этот инструмент выводит из одного лишь jar:
| Приложение | Что оно делает | DNS-запросы Netty (во время выполнения) | Этот инструмент (статически) |
|---|---|---|---|
| A | стандартный WebClient.builder() | 1 | используется |
| B | .resolver(DefaultAddressResolverGroup.INSTANCE) | 0 | найдено переопределение → проверьте вручную |
| C | spring.http.reactiveclient.connector=jdk | 0 | найдено переопределение → проверьте вручную |
| D | два WebClient, переопределён только один | 1 | найдено переопределение → проверьте вручную |
| E | стандартный, Spring Boot 3.5.16 | 1 (на исправленном 4.1.135) | не уязвимо |
Честный результат: инструмент надёжно говорит «используется», когда переопределения нет, и находит следы переопределения в вашем коде и конфигурации (3 из 3, без ложного срабатывания на A). Он не может отличить B (всё переопределено) от D (один клиент всё ещё на стандартном резолвере) — их байткод-свидетельства идентичны. Поэтому переопределение никогда не заставляет его сказать «не уязвимо»; он говорит «проверьте вручную» и даёт вам одноминутную проверку ниже.
--logging.level.io.netty.resolver.dns=DEBUGWRITE: UDP в логе — есть: резолвер Netty используется; нет: этот запрос его не использовалНе судите по критерию «загружался ли DnsNameResolver?». Приложение B заменило резолвер и всё равно
загрузило этот класс, потому что группа резолверов по умолчанию создаётся заранее. Доказательство — только
строки запросов.
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
Он читает то, что фактически упаковано (fat jar BOOT-INF/lib, war WEB-INF/lib, вложенные jar),
а не pom.xml — зависимость транзитивная, поэтому pom сказал бы вам, что её там нет.
Наличие определяется по классу io/netty/resolver/dns/DnsNameResolver.class, а не по манифестам версий.
Следы переопределения ищутся только в ваших собственных классах и application*.properties/yml
(библиотечные jar сами ссылаются на эти классы).
Вывод на китайском; строки вердикта и идентификаторы CVE — то, на что должны ориентироваться скрипты.
HttpClient из Reactor Netty напрямую (например, шлюз), не анализируются
отдельно — если Reactor Netty присутствует, предполагается стандартный путь.GitHub advisory API, прочитано 2026-09-11:
| CVE | GHSA | Серьёзность | Уязвимо → исправлено |
|---|---|---|---|
| CVE-2026-45674 | GHSA-676x-f7gg-47vc | высокая 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 | высокая 8.7 | то же |
| CVE-2026-45673 | GHSA-xmv7-r254-6q78 | средняя 6.8 | то же |
Таблица Spring Boot находится в BootTable.java; модульный тест заново выводит три утверждения из
раздела 2 на её основе, поэтому таблица и утверждения не могут незаметно разойтись.
evidence/ содержит пять приложений (A–E) и runtime.sh, который подсчитывает строки WRITE: UDP для
каждого приложения. tools/e2e_real_jars.py запускает этот инструмент против тех же пяти jar и проверяет
каждый вердикт. Для сборки приложений требуется Maven 3.6.3 или новее.
| Код | Значение |
|---|---|
| 0 | ни одна из трёх CVE не применима (исправленная версия) |
| 1 | найдена уязвимая версия — в том числе когда найдены следы переопределения |
| 2 | невозможно судить (не найден netty-resolver-dns, неизвестная версия, неверные аргументы) |
| 4 | не удалось прочитать какой-то файл — «я не смог это прочитать» никогда не должно выглядеть как успех |
Apache License 2.0