Spring4Shell (CVE-2022-22965): снова Java, снова критическая RCE
RCE-уязвимость в Spring Framework, активная эксплуатация с 30 марта. За сутки прошлись по всем клиентским Spring Boot приложениям - реестр версий и WAF-правила до патча.
Spring4Shell CVE-2022-22965: RCE-уязвимость в Spring Framework, активная эксплуатация с 30 марта 2022
Дежавю не заставило себя ждать. 30 марта в публичный доступ утёк PoC эксплоита для CVE-2022-22965 - уязвимости в Spring Framework, которую уже прозвали Spring4Shell. Снова Java. Снова RCE без аутентификации. Снова паника в телеграм-каналах по ИБ и срочные письма от вендоров. С декабрьского Log4Shell-марафона прошло три месяца.
Что за уязвимость
Суть в механизме data binding Spring MVC. При определённых условиях атакующий может записать произвольные данные через class.classLoader, получив в итоге выполнение кода на сервере. Вектор - HTTP-запрос, жертва ничего не нажимает.
Условия срабатывания не тривиальные, но и не фантастические:
- Spring Framework 5.3.0-5.3.17 или 5.2.0-5.2.19 (и более старые неподдерживаемые ветки).
- JDK 9+. На Java 8 уязвимость не работает из-за различий в ClassLoader. Это важно - часть старых приложений на Java 8 автоматически выпадает из зоны риска.
- Развёртывание как WAR на Tomcat. Executable JAR (стандартный Spring Boot) менее очевидно уязвим - там другой ClassLoader, механика другая.
- Нет ограничений на поля в @ModelAttribute. Если разработчики явно задали allowedFields или использовали allowlist - риск снижается.
На момент появления PoC официального патча ещё не было. VMware (владелец Spring) выпустила патч 31 марта - Spring Framework 5.3.18 и 5.2.20.
Дежавю по-крупному
Декабрьский Log4Shell научил одному: Java-приложения в инфраструктуре клиентов живут в самых неожиданных местах. Тогда мы потратили дни на выяснение «а что вообще запущено». Повторять этот опыт не хотелось.
Примерно за последние месяцы после Log4Shell-аудита мы настояли на том, чтобы у клиентов появились хотя бы базовые реестры Java-компонентов. Не SBOM в полном смысле, но список: что запущено, на чём, какая версия фреймворка. Именно это сейчас и пригодилось.
Как прошли за сутки
30 марта вечером информация о PoC разошлась достаточно широко, чтобы стало понятно: завтра будет жарко. Запустили проверку по реестру, который собирали с января.
По каждому Spring-приложению проверяли три вещи:
- Версия Spring Framework. Если 5.3.x до 5.3.17 или 5.2.x до 5.2.19 - в приоритет.
- Версия JDK. Приложения на Java 8 отмечали отдельно - формально меньший риск, но расслабляться не стали.
- Способ деплоя. WAR на Tomcat против executable JAR - разная механика уязвимости.
Картина оказалась пёстрой. Часть приложений - Spring Boot на относительно свежих версиях, часть - легаси WAR на Tomcat с Spring 4.x или 5.2.x. Именно старые WAR-развёртывания вызвали максимальный интерес.
Патча ещё не было - работали с тем, что есть. Для периметровых приложений развернули WAF-правила, блокирующие запросы, в параметрах которых встречается class., Class., %43lass и вариации. Это грубый фильтр и он даёт ложные срабатывания, но лучше разобраться с ложными срабатываниями, чем получить shell на продакшен.
Для части приложений применили mitigation от Spring на уровне кода - добавление глобального DataBinder с setDisallowedFields для class-иерархии. Где был прямой доступ к коду и возможность быстро задеплоить - сделали это.
Что помогло, что нет
Реестр Java-компонентов, собранный после Log4Shell, сократил время первичного скана раза в три по сравнению с декабрём. Вместо «что вообще есть» мы сразу шли к конкретным приложениям. Это не откровение, это просто работает.
Что не помогло: несколько приложений в реестре оказались с устаревшими данными. Версия Spring обновилась в январе без обновления реестра. Небольшая расплата за то, что реестр живёт в документе, а не генерируется автоматически из артефактов.
WAF-правила - временная мера с понятными ограничениями. Обход через urlencoding или другие вариации параметров возможен, фильтр не гарантирует ничего. Это пластырь до патча, не более.
В managed-инфраструктуре к концу 31 марта, когда VMware выпустила 5.3.18, у большинства клиентов обновление было либо уже задеплоено, либо в процессе. Несколько старых WAR-приложений на Tomcat - отдельная история: там обновление Spring зависит от процесса сборки и CI/CD, который в некоторых случаях живёт в 2015 году.
Параллели с Log4Shell
Сравнение напрашивается, но оно не полное. Log4Shell был проще в эксплуатации - jndi lookup можно было засунуть практически куда угодно, в любое поле любого HTTP-запроса. Spring4Shell требует более конкретных условий: нужен Spring MVC с определённым паттерном контроллера, нужен Tomcat в WAR-режиме, нужен JDK 9+.
Это не значит, что можно расслабиться. Это значит, что поверхность атаки несколько уже, и приоритизировать легче. Приложения на embedded Tomcat с JAR-деплоем и Spring Boot - под вопросом, но не в первой очереди. Легаси WAR на внешнем Tomcat с JDK 11 - первая очередь.
Тем не менее ощущение знакомое: снова Java, снова публичный PoC до патча, снова день-два в режиме «разобраться что есть и прикрыть». После декабря хотя бы знаем, как это делать быстрее.
Патч вышел, сейчас основная работа - прокатить обновления там, где это требует согласований или нестандартного процесса деплоя. История продолжается.