
Рецепт OpenRewrite, который обнаруживает и исправляет подавление заголовков Spring Security (CVE-2026-22732), выявляя неправильное использование заголовка Content-Length и генерируя конфигурацию для немедленной записи заголовков.
OpenRewrite-рецепт, который обнаруживает код, подверженный CVE-2026-22732 — дефекту Spring Security, при котором установка Content-Length через один из трёх методов ответа обходит OnCommittedResponseWrapper в Spring Security. Поскольку обёртка никогда не видит заголовок, onResponseCommitted() не срабатывает, и лениво добавляемые заголовки безопасности (X-Frame-Options, X-Content-Type-Options, Cache-Control и т. д.) молча отбрасываются.
Фактические триггеры, подтверждённые на уязвимой версии Spring Security 6.4.12 с Spring Boot 3.4.3 / встроенным Tomcat:
Servlet Content-Length через перегрузки, обходящие обёртку
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
Эти три перегрузки не переопределены в OnCommittedResponseWrapper. Последующие записи тела завершают объявленную длину, и контейнер фиксирует ответ без срабатывания ленивого записи заголовков.
WebFlux Content-Length через HttpHeaders
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
Безусловные фиксации ответа в WebFlux
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
Рецепт ограничен наличием Spring Security — он ничего не выдаёт в файлах, которые не ссылаются ни на один тип org.springframework.security.*, — а также затронутыми диапазонами версий Spring Security. Согласно рекомендации Spring, опубликованной 2026-03-19, затронутые диапазоны и исправленные версии:
| Серия | Затронутые | Исправленные |
|---|---|---|
| 5.7.x | 5.7.0 – 5.7.21 | 5.7.22 (Enterprise) |
| 5.8.x | 5.8.0 – 5.8.23 | 5.8.24 (Enterprise) |
| 6.3.x | 6.3.0 – 6.3.14 | 6.3.15 (Enterprise) |
| 6.4.x | 6.4.0 – 6.4.14 | 6.4.15 (Enterprise) |
| 6.5.x | 6.5.0 – 6.5.8 | 6.5.9 (OSS) |
| 7.0.x | 7.0.0 – 7.0.3 | 7.0.4 (OSS) |
Проекты, разрешающие версию Spring Security на уровне исправления или выше в своей серии (или в любой будущей серии после 7.0 / 6.5), считаются незатронутыми и не получают маркеров ни по отдельным точкам, ни по файлам. Проекты, где версию не удаётся разрешить, переходят к обычному обнаружению по шаблонам, чтобы сканер скорее сообщал о находке, которую не может опровергнуть. Таблица данных SpringSecurityVersionByProject по-прежнему записывает разрешённую версию и помечает каждый проект как затронутый или нет, чтобы вы могли проверить, что было отфильтровано.
Это выглядит опасно, но отслеживается обёрткой, поэтому заголовки безопасности записываются до фиксации ответа:
| Код | Почему это безопасно |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | Переопределено — обёртка записывает объявленную длину и вызывает onResponseCommitted(), когда тело завершается. |
response.flushBuffer() | Переопределено — вызывает doOnResponseCommitted() до super.flushBuffer(). |
response.getOutputStream().write(..) / flush() / close() | Возвращает SaveContextServletOutputStream; каждая запись/сброс/закрытие вызывает doOnResponseCommitted() перед делегированием. |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | Возвращает SaveContextPrintWriter; та же схема. |
response.addHeader("Content-Length", v) | Особый случай в обёртке — направляется через setContentLength(long). |
Конечная точка /vuln/flush в демо Semgrep утверждает, что триггером является flushBuffer(), но на уязвимой версии Spring Security 6.4.12 ответ фактически возвращает все шесть заголовков безопасности. Настоящие триггеры в демо — это вызовы setIntHeader("Content-Length", ...) в /vuln/stream и /vuln/content-length.
Запустите этот:
| Рецепт | Назначение |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | Выполняет все проверки и формирует таблицу отчёта по версиям |
Агрегатор выше состоит из двух более мелких рецептов. Вы можете вызывать их по отдельности, если нужна только одна проверка.
| Рецепт | Назначение |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | Поток заражения для литерала "Content-Length", достигающего setHeader / setIntHeader / addIntHeader (servlet) или HttpHeaders.set / add (WebFlux) |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | Безусловные фиксации в WebFlux: writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength |
Запустите этот:
| Рецепт | Назначение |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | Выбирает самое дешёвое исправление, которое реально может применить каждый проект |
Он выполняет два шага по порядку.
1. Переход на исправление в собственной серии проекта. Spring Security опубликовал исправление как 6.5.9
и 7.0.4 в Maven Central. Каждый переход ограничен предусловием FindAffectedSpringSecuritySeries,
поскольку UpgradeDependencyVersion проверяет только, что целевая версия новее — при указании
7.0.4 он охотно перетащил бы проект на 5.8 через две мажорные версии.
2. Добавление конфигурации упреждающей записи заголовков для того, что шаг 1 не смог исправить. Это генерирует
один класс @Configuration на проект:
@Bean
public static BeanPostProcessor eagerHeaderWriterFilterBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HeaderWriterFilter) {
((HeaderWriterFilter) bean).setShouldWriteHeadersEagerly(true);
}
return bean;
}
};
}
Запись заголовков заранее делает неважным, наблюдает ли обёртка за фиксацией, поэтому
это закрывает все точки в проекте сразу — включая те, до которых анализ заражения не может
добраться, например поток через Map в разделе «Известные ограничения». BeanPostProcessor видит фильтры, созданные
DSL HttpSecurity, потому что AutowireBeanFactoryObjectPostProcessor инициализирует их через
фабрику бинов. Две независимо опубликованные исправления для этой CVE используют ровно такую форму
(hmcts/idam-web-public, и
форки Spinnaker от armory-io через эквивалентный ObjectPostProcessor).
Шаг 2 покрывает проекты, которым шаг 1 не может помочь:
| Ситуация | Почему переход не работает |
|---|---|
| 5.7, 5.8, 6.3, 6.4 | Исправление поставляется только подписчикам Spring Enterprise — его нет в Maven Central |
| 6.0 - 6.2 | Для этих серий исправление никогда не выпускалось |
| Версия управляется импортированным BOM | Локально ничего не объявлено, что переход мог бы редактировать |
Последняя строка — не крайний случай. nla/bamboo разрешает 7.0.3 — серию с исправлением с открытым
исходным кодом — полностью из BOM Spring Boot, поэтому проверка версии сама по себе пропустила бы его на обоих шагах
и оставила бы уязвимым. Поэтому AddEagerHeaderWriterConfiguration откладывает переход только тогда,
когда у проекта есть доступное исправление с открытым исходным кодом и он объявляет собственную версию.