
Офлайн-проверщик для Thymeleaf CVE-2026-40477 / CVE-2026-41901 — сообщает, какой из двух SSTI-дефектов с CVSS 9.0 затрагивает вас, и есть ли исправление для вашей версии вообще (3.0.x: его нет).
Инструмент для офлайн-проверки Thymeleaf CVE-2026-40477 / CVE-2026-41901 — без зависимостей, один jar, без подключения к сети.
Две уязвимости обхода SSTI с оценкой CVSS 9.0, и за девять дней вышло два исправленных релиза. Сначала он отвечает на вопрос «какие из них затрагивают меня», а затем отвечает на более неприятный вопрос:
Есть ли в моей ветке версий исправленный релиз, до которого можно обновиться?
Для 3.1.x — да (достаточно обновиться до 3.1.5.RELEASE).
Для 3.0.x и более ранних — нет, а ведь именно они составляют самую многочисленную группу установок.
Диапазоны затронутых версий в обоих advisory в OSV указаны как introduced: 0:
curl -s https://api.osv.dev/v1/vulns/GHSA-r4v4-5mwr-2fwr | jq '.affected[0].ranges'
# [{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.4.RELEASE"}]}]
То есть вся линейка 3.0 целиком, в пошаговом списке версий перечислены все от 3.0.15.RELEASE вплоть до 1.0.0.
А на Maven Central линейка 3.0 заканчивается на 3.0.15.RELEASE, после чего публикаций больше нет:
curl -s https://repo1.maven.org/maven2/org/thymeleaf/thymeleaf/maven-metadata.xml \
| grep -o '<version>3\.0\.[^<]*</version>' | tail -1
# <version>3.0.15.RELEASE</version>
Исправленные версии обеих CVE находятся в ветке 3.1. Поэтому у пользователей 3.0.x нет варианта «просто поменять номер версии»,
есть только миграция через границу мажорных версий — а 3.1 официально указана как содержащая обратно несовместимые изменения (дословно скопировано из официального ChangeLog.txt, раздел 3.1.0.M1):
- Removed web-API based expression security objects (#request, #response, #session, #servletContext).
- Removed support for Spring 3.x and Spring 4.x.
- Set minimum JDK compatibility level to JDK 8 project-wide (JDK 17 for thymeleaf-spring6).
В шаблонах, где использовались ${#request.…} / ${#session.…}, после обновления будет прямая ошибка — получение этих значений нужно перенести в Controller и передавать через Model.
Это миграция, требующая планирования, а не просто смена номера версии.
Количество зависимостей deps.dev (прямое чтение 2026-09-04, любой может перепроверить):
| Версия | Кол-во зависимостей | Какие из них затронуты | Можно ли обновиться в пределах линии |
|---|---|---|---|
| 3.0.11.RELEASE | 1124 | две | ❌ в линии 3.0 нет исправленной версии |
| 3.0.15.RELEASE | 678 | две | ❌ то же самое, и это уже конечная точка линии |
| 3.0.12.RELEASE | 641 | две | ❌ |
| 3.1.3.RELEASE | 826 | две | ✅ обновление до 3.1.5 |
| 3.1.2.RELEASE | 677 | две | ✅ |
| 3.1.4.RELEASE | 70 | только CVE-2026-41901 | ✅ |
| 3.1.5.RELEASE | 590 | — | уже безопасна |
Единичная версия с наибольшей базой установок — 3.0.11.RELEASE, она на 36% выше самой высокой в линии 3.1 — 3.1.3, и у неё нет выхода в пределах своей линии.
java -jar thymeleaf-check.jar <目录或 jar/war 路径> [--utf8|--gbk]
$ java -jar thymeleaf-check.jar ./target
[CRITICAL] ./target/myapp.war :: BOOT-INF/lib/thymeleaf-3.0.11.RELEASE.jar
构件:thymeleaf 版本 3.0.11.RELEASE(3.0 线,依据: MANIFEST(Implementation-Version))
命中: CVE-2026-40477 + CVE-2026-41901
受影响,且 3.0 线在 Maven Central 上没有任何修复版(该线止于 3.0.15.RELEASE)。
Код выхода: 2 = для версионной линии, в которой находится компонент, нет исправленной версии в той же линии · 1 = затронут, но можно обновиться в пределах той же линии / невозможно определить · 0 = не обнаружено · 3 = ошибка использования или пути.
Можно напрямую подключить в CI.
Сканируйте артефакты сборки (target/*.jar, *.war), а не каталог исходного кода.
Thymeleaf в подавляющем большинстве случаев подтягивается транзитивно через spring-boot-starter-thymeleaf, а сам starter не несёт версии —
в pom вообще не видно номера версии Thymeleaf. В такой ситуации инструмент прямо скажет «по pom определить нельзя», а не будет считать, что его нет.
Три критерия идентификации, по убыванию надёжности:
Implementation-Version в MANIFEST — распознаётся, даже если jar переименован инструментом перепаковки.
(В официальном jar Thymeleaf в META-INF/ есть только MANIFEST.MF, каталога maven/ нет, поэтому для него это самый надёжный критерий.)pom.propertiesВ fat jar / war каталоги BOOT-INF/lib/, WEB-INF/lib/ разбираются по отдельности, MANIFEST вложенных jar также читается.
CVE-2026-40477 | CVE-2026-41901 | |
|---|---|---|
| GHSA | GHSA-r4v4-5mwr-2fwr | GHSA-c9ph-gxww-7744 |
| CVSS | 9.0 | 9.0 |
| Затронуто | <= 3.1.3.RELEASE | <= 3.1.4.RELEASE |
| Исправлено | 3.1.4.RELEASE | 3.1.5.RELEASE |
| Раскрыто | 2026-04-15 | 2026-05-04 |
| Официальная формулировка | "fails to properly restrict the scope of accessible objects" | "fails to properly neutralize specific constructs", происходит в sandboxed (restricted) контексте |
Диапазон затронутых версий во второй включает 3.1.4, поэтому те, кто по первому advisory обновился до 3.1.4, попадают во второй.
Но не читайте это как «официально исправлено не до конца» — два advisory описывают разные механизмы,
3.1.4 (2026-04-12) и 3.1.5 (2026-04-21) — две официальные версии с разницей в девять дней,
и ни в одном официальном тексте не сказано, что предыдущее исправление было неполным. Этот инструмент лишь кладёт факты рядом, а не делает за вас этот вывод.
Каждое число в RuleTable.java можно проверить обратно:
python tools/gen_rules.py --show
Он сверяет таблицу решений с двумя первоисточниками — затронутыми диапазонами и по-версионными списками OSV, а также maven-metadata.xml из Maven Central — и завершается с ненулевым кодом при несовпадении.
Три правила:
mvn package # JDK 17;target/thymeleaf-check-0.1.0.jar
mvn test # 19 个用例
Нулевые зависимости во время выполнения — это намеренно: инструмент для диагностики должен запускаться в любом окружении сразу.
Apache-2.0
| Код | Значение |
|---|---|
0 | Сканирование завершено, и каждый файл действительно был прочитан, нет находок, требующих ваших действий |
4 | Есть файлы, которые не удалось прочитать — не zip, содержимое усечено или ошибка чтения |
🔴 4 добавлен 2026-09-09, и причину его добавления стоит объяснить ясно.
До этого непрочитанные файлы оставляли лишь строку-подсказку в выводе, а код выхода по-прежнему был 0 —
след оставляется для людей, а CI и скрипты смотрят на код выхода, поэтому «я не смог это прочитать» в автоматизации равносильно «пройдено».
Повреждённый, зашифрованный или недокачанный jar тихо превращался в фразу «проблем не обнаружено».
«Я не могу это прочитать» и «вы в безопасности» должны быть двумя разными фразами.
⚠️ Легитимный пустой jar не считается непрочитанным (запись EOCD в 22 байта — это настоящий пустой zip), он не вызывает 4 —
когда ложных тревог много, настоящие тревоги никто не смотрит.
Значения остальных кодов выхода см. в описанных выше уровнях определения и разделах использования.