Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Оборонительные ИнструментыАнализ уязвимостейЭксплуатацияВиртуализация для безопасностиВеб-безопасностьОбучение и Образование
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

Proof-of-concept, демонстрирующий CVE-2026-22732 — уязвимость Spring Security, при которой setIntHeader("Content-Length") удаляет все заголовки безопасности, с уязвимой и исправленной сборками.

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Репозиторий
2 ч 52 мин назадЕщё не проверено
Поделиться

CVE-2026-22732 — Proof of Concept

Spring Security молча отбрасывает заголовки безопасности HTTP-ответа. Только для демонстрационных / образовательных целей; запускайте только против этого локального приложения.

CVECVE-2026-22732 (CWE-425), опубликовано 2026-03-19
CVSS 3.19.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Прямая зависимостьspring-boot-starter-web + spring-boot-starter-security 2.7.18
Уязвимый компонентspring-security-web / -config / -core 5.7.11 — только транзитивно, никогда не указан в pom.xml
Исправленный компонентspring-security-web 5.7.14-0.cgr.2, достигается одним изменением <version> — см. переход
Проверено наTomcat 9.0.118, JDK 17.0.18, macOS arm64

Затронутые диапазоны: 5.7.0–5.7.21, 5.8.0–5.8.23, 6.3.0–6.3.14, 6.4.0–6.4.14, 6.5.0–6.5.8, 7.0.0–7.0.3. Spring Boot 2.7.18 фиксирует Spring Security 5.7.11, ровно внутри первого диапазона:

root@kitploit:~
$ mvn dependency:tree -Dincludes=org.springframework.security
+- org.springframework.security:spring-security-config:jar:5.7.11:compile
|  \- org.springframework.security:spring-security-core:jar:5.7.11:compile
\- org.springframework.security:spring-security-web:jar:5.7.11:compile

Запуск

root@kitploit:~
./run.sh        # терминал 1 — собирает и запускает на :8080 (фиксирует JDK 17)
./exploit.sh    # терминал 2 — обходит все эндпоинты, сравнивает заголовки

run.sh фиксирует JAVA_HOME, потому что Spring Boot 2.7.x не может работать на JDK 25, который mvn выбирает по умолчанию на этой машине. Переопределите через JAVA_HOME_17=/path/to/jdk17.

Он также по умолчанию собирается с -s settings-chainguard.xml, потому что исправленный родительский POM отсутствует в Maven Central. Установите MAVEN_SETTINGS=/path/to/your/settings.xml, чтобы указать другое расположение, или MAVEN_SETTINGS=, чтобы собирать исключительно из Central — что работает только для стандартного 2.7.18.

exploit.sh считывает фактические версии spring-security-web и spring-boot из target/*.jar, поэтому его баннер всегда сообщает то, что действительно запущено, а не захардкоженную строку.

Конфигурация

SecurityConfig применяет вообще никакой кастомизации заголовков — действуют настройки Spring Security по умолчанию, что как раз и есть то, на что полагается приложение, заботящееся о безопасности. Каждый эндпоинт возвращает одно и то же конфиденциальное тело:

root@kitploit:~
{"account":"4111-1111-1111-1111","holder":"D. Havelock","balance":"82914.55"}

Меняется только то, как контроллер записывает ответ.

Измеренные результаты

root@kitploit:~
BASELINE  standard Spring MVC return value
  /safe/account                    OK        all 6 headers delivered

CONTROL   getOutputStream(), body > 8 KB buffer
  /vuln/stream/account             OK        all 6 headers delivered

CONTROL   explicit response.flushBuffer()
  /vuln/flush/account              OK        all 6 headers delivered

EXPLOIT   setIntHeader("Content-Length", n)  <-- CVE-2026-22732
  /vuln/content-length/account     BYPASSED  ALL 6 security headers dropped

BY DESIGN application sets its own Expires header (NOT this CVE)
  /vuln/cache/account              PARTIAL   Cache-Control + Pragma dropped

Только /vuln/content-length/account меняет состояние при исправлении CVE, поэтому только по нему exploit.sh выносит свой вердикт. Остальные — контрольные.

CVE — setIntHeader("Content-Length", n) → полный обход

Три строки обычного на вид кода контроллера снимают каждый заголовок, обещанный Spring Security:

root@kitploit:~
response.setContentType("application/json");
response.setIntHeader("Content-Length", body.length);
response.getOutputStream().write(body);
root@kitploit:~
$ curl -sD - -o /dev/null http://localhost:8080/vuln/content-length/account
HTTP/1.1 200
Content-Type: application/json
Content-Length: 77
Date: Tue, 01 Sep 2026 00:53:18 GMT

Ни X-Frame-Options, ни X-Content-Type-Options, ни Cache-Control, ни Pragma, ни Expires, ни X-XSS-Protection. Сравните с /safe/account, который несёт все шесть. Ответ может быть встроен во фрейм с любого origin, подвержен MIME-sniffing и кэшируем — при этом отдавая номер карты.

Случай по замыслу — установленный приложением заголовок кэша → подавление кэширования

Исправлено: более ранняя версия этого README называла это «эксплойтом 2» и утверждала, что это условие, документированное в advisory. Это не часть CVE-2026-22732, и никакое обновление это не исправляет. CacheControlHeadersWriter побайтово идентичен в 5.7.11, 5.7.14-0.cgr.2, 6.5.8 (последняя уязвимая) и 6.5.9 (первая исправленная) — проверено сравнением sources jar. Его Javadoc прямо указывает на это поведение: «Inserts headers to prevent caching if no cache control headers have been specified.»

Это всё равно стоит демонстрировать, потому что утечка реальна и остаточный риск сохраняется после патча. CacheControlHeadersWriter прекращает работу, если Cache-Control, Expires или Pragma уже присутствует, поэтому установка любого одного из трёх подавляет все директивы no-store от Spring Security. Достаточно одной благонамеренной строки:

root@kitploit:~
response.setHeader("Expires", "Thu, 01 Jan 2099 00:00:00 GMT");
root@kitploit:~
/safe/account                  NOT cacheable  (Cache-Control: no-cache, no-store, max-age=0, must-revalidate)
/vuln/cache/account            CACHEABLE      (Cache-Control: absent / Expires: Thu, 01 Jan 2099 00:00:00 GMT)
/vuln/content-length/account   CACHEABLE      (Cache-Control: absent / Expires absent)

Expires считается «присутствующим» в таблице выше, но с дружественным атакующему значением, выбранным приложением — Expires: 0 от Spring Security был заменён, а не просто отброшен. Данные держателей карт теперь могут храниться каждым браузером и общим прокси на пути до 2099 года.

На исправленной сборке /vuln/content-length/account переключается на NOT cacheable, тогда как /vuln/cache/account остаётся точно как выше. Исправляет это только код приложения или обратный прокси — что и есть полезная вещь, которую стоит сказать вслух на демо: обновление библиотеки закрывает CVE и оставляет это нетронутым.

Два отрицательных результата, оставленных намеренно

Несколько широко распространённых разборов — включая публичный репозиторий воспроизведения — указывают response.getOutputStream() и response.flushBuffer() как триггеры, объясняя, что «ответ фиксируется до того, как Spring Security сможет внедрить свои заголовки». На Spring Security 5.7.11 это неверно. Оба эндпоинта доставляют все шесть заголовков.

/diag/committed показывает, почему это объяснение не работает. После записи 12 KB ответ действительно фиксируется внутри контроллера, но заголовки всё равно приходят:

root@kitploit:~
>>> DIAG response.isCommitted() after 12048 byte write = true
    | wrapper class = org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse

OnCommittedResponseWrapper переопределяет flushBuffer() и записи в output-stream, поэтому он выпускает свои заголовки раньше этих фиксаций. Один лишь порядок фиксации не является багом; путь с объявленным Content-Length — является. Сам Spring advisory никогда не поддерживает историю о порядке фиксации.

Сохранение этих двух эндпоинтов делает PoC опровержимым: он показывает, что не воспроизводится, так же ясно, как и то, что воспроизводится, и оба остаются зелёными на протяжении всего патча, что и делает значимым единственный эндпоинт, который действительно переключается.

Переход от уязвимого к исправленному

Одна строка в pom.xml, и ничего больше. Ни изменения исходников, ни изменения свойств, ни мажорного обновления Spring Boot:

root@kitploit:~
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.18</version>            <!-- vulnerable -->
  <version>2.7.18-0.cgr.3</version>    <!-- patched   -->
</parent>

Это перефиксирует spring-security.version с 5.7.11 на 5.7.14-0.cgr.2 (и spring-framework.version с 5.3.31 на 5.3.39-0.cgr.4) через spring-boot-dependencies родителя. Измерено:

2.7.182.7.18-0.cgr.3
/safe/accountOK 6/6OK 6/6
/vuln/stream/accountOK 6/6OK 6/6
/vuln/flush/accountOK 6/6OK 6/6
/vuln/content-length/accountBYPASSED 0/6OK 6/6
/vuln/cache/accountPARTIAL 4/6PARTIAL 4/6 (by design)
exploit.sh verdictVULNERABLEPATCHED

Бэкпорт — это и есть upstream-исправление

При сравнении sources jar spring-security-web важен только один файл. 5.7.11 → 5.7.14-0.cgr.2 добавляет переопределения setHeader / setIntHeader / addIntHeader в OnCommittedResponseWrapper, каждое из которых направляет Content-Length через setContentLength():

root@kitploit:~
@Override
public void setIntHeader(String name, int value) {
    checkContentLengthHeader(name, value);   // <-- added
    super.setIntHeader(name, value);
}

До исправления это делал только addHeader, поэтому setIntHeader("Content-Length", n) оставлял отслеживаемую длину обёртки на 0, onResponseCommitted() никогда не срабатывал, и HeaderWriterFilter никогда не записывал свои заголовки до того, как Tomcat фиксировал ответ.

Тот же фрагмент появляется дословно при сравнении upstream 6.5.8 (последняя уязвимая) с 6.5.9 (первая исправленная), так что это официальное исправление, перенесённое бэкпортом, а не перереализация. Сборка Chainguard добавляет две проверки на null, которых нет в upstream 6.5.9 (value != null в перегрузке для String, и (csq != null) ? csq.length() : 4 в append).

Другие меры защиты

Spring Security 5.7.x — end-of-life в upstream; исправления OSS выходят только в 6.4.15 / 6.5.9 / 7.0.4+. Если пересобранный 5.7.x не вариант:

  1. Уйти с 5.7.x — миграция на Spring Boot 3.x.
  2. Обходной путь — установить HeaderWriterFilter.shouldWriteHeadersEagerly = true через ObjectPostProcessor. Согласно advisory это меняет поведение: записанные приложением заголовки тогда переопределяют только конкретные заголовки, а не подавляют заголовки Spring Security. Это также исправляет /vuln/cache/account, чего не делает обновление версии.
  3. Коммерческая поддержка — бэкпорты Tanzu Spring Enterprise для 5.7.x/5.8.x.
  4. Эшелонированная защита — устанавливайте заголовки на обратном прокси / ingress, чтобы отброшенный заголовок приложения не был единственным средством контроля. Это единственный из перечисленных вариантов, который покрывает оба эндпоинта.

Ничто из этого не подключено к этому проекту, поэтому уязвимое поведение является поведением по умолчанию, а исправленное состояние достижимо единственным изменением <version> выше.

Патчинг встроенного Tomcat без обновления Spring Boot

Boot 2.7.18 фиксирует Tomcat 9.0.83, который grype . помечает 34 CVE (4 Critical). Все они исправлены в 9.0.118 или ниже, а 9.0.118 — самый новый релиз 9.0.x — так что одно свойство очищает весь набор:

root@kitploit:~
<properties>
  <tomcat.version>9.0.118</tomcat.version>
</properties>

spring-boot-dependencies объявляет каждый артефакт tomcat-embed-* через это единственное свойство, поэтому его переопределение перефиксирует core, el и websocket вместе. Проверено:

root@kitploit:~
$ mvn dependency:tree | grep tomcat-embed
tomcat-embed-core:jar:9.0.118:compile
tomcat-embed-el:jar:9.0.118:compile
tomcat-embed-websocket:jar:9.0.118:compile

Ограничение: оставайтесь на линии 9.0.x. Tomcat 10+ перевёл Servlet API на jakarta.*, тогда как Spring Framework 5.3 компилируется против javax.servlet, поэтому обновление до 10.x/11.x падает во время выполнения с NoClassDefFoundError на типах servlet.

Патчинг всего остального, всё ещё в пределах каждой мажорной линии

Тот же механизм применён к остальным управляемым зависимостям Boot 2.7.18. Никаких мажорных обновлений и никакого обновления Spring Boot:

СвойствоПо умолчанию Boot 2.7.18Зафиксировано здесьПричина потолка
tomcat.version9.0.839.0.118последняя 9.0.x; 10+ это jakarta.*
spring-framework.version5.3.315.3.39последняя OSS 5.3.x на Central
jackson-bom.version2.13.52.22.2последняя 2.x
log4j2.version2.17.22.26.1последняя 2.x
snakeyaml.version1.301.33последняя 1.x; исправление для оставшейся CVE — 2.0
logback.version1.2.121.2.13последняя 1.2.x — см. ниже
spring-security.version5.7.11оставлено как естьэто предмет демо

Эти переопределения взаимодействуют с исправленным родителем, поэтому знайте, что они делают, прежде чем демонстрировать. На 2.7.18-0.cgr.3 родитель уже предоставляет tomcat.version 9.0.118, logback.version 1.2.13 и snakeyaml.version 1.33 — эти три строки становятся точными дубликатами и могут быть удалены без каких-либо изменений. Строки jackson-bom.version и log4j2.version всё ещё делают реальную работу: исправленный родитель сохраняет стандартные 2.13.5 / 2.17.2 от Boot, поэтому переопределения побеждают, и эти две зависимости разрешаются в обычные upstream-сборки, а не в собранные Chainguard. spring-framework.version закомментирован намеренно, что и позволяет пройти 5.3.39-0.cgr.4 от родителя.

Прогресс grype .

СостояниеНаходкиРазбивка
Стандартный Boot 2.7.18997C / 39H / 38M / 15L
+ обновление Tomcat653C / 23H / 29M / 10L
+ все обновления в пределах мажорной линии433C / 12H / 18M / 10L

Полностью очищено: Tomcat (34), Jackson (7), log4j (1). В целом 99 → 43, Highs 39 → 12.

Проверено после каждого обновления: приложение загружается на Tomcat/9.0.118, все шесть эндпоинтов возвращают 200, и CVE воспроизводится побайтово. Spring Boot всё ещё 2.7.18, а Spring Security всё ещё 5.7.11, поэтому CVE-2026-22732 не затронута — в чём и смысл этого раздела, а также его честный предел: патчинг всего вокруг не делает ничего для CVE в application-framework. Для исправления этой нужно обновление родителя, а не свойство.

Почему Logback останавливается на 1.2.13

Последняя 1.x — 1.6.3 — та же мажорная версия, так что номинально в области охвата. Она не работает. Logback 1.3+ заменил SLF4J 1.7 StaticLoggerBinder на провайдер SLF4J 2.x ServiceLoader, а LogbackLoggingSystem из Boot 2.7 вызывает StaticLoggerBinder напрямую. Измерено с 1.5.38:

root@kitploit:~
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"
Caused by: java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(...:304)

Чтобы выйти из этого, нужны SLF4J 2.x (мажорное обновление) и система логирования Boot 3.x. 1.2.13 — это реальный потолок, оставляющий 6 находок Logback (2 Medium, 4 Low) неисправимыми на этой линии.

Оставшиеся 43

КомпонентПочему нельзя исправить в пределах мажорной версии
spring-webmvc / -expression / -core / -context (25)5.3.39 — последняя OSS 5.3.x; у 14 из 15 находок webmvc вообще нет исправления, а 5.3.42, которую указывает grype, только коммерческая
logback-core (6)нужен SLF4J 2.x, см. выше
spring-security-* (8)линия EOL; CVE-2026-22732 намеренна
spring-boot / -autoconfigure (3)для 2.7.x исправление не опубликовано
snakeyaml (1)CVE-2022-1471 исправлена только в 2.0

Две из трёх оставшихся Critical стоит прочитать внимательно, а не по баллам:

  • CVE-2016-1000027 (spring-web, исправление 6.0.0) — десериализация через HttpInvokerServiceExporter. Это приложение не использует HTTP Invoker, поэтому здесь она недостижима.
  • CVE-2024-38821 (spring-security-web, исправление 5.7.13) — обход авторизации статических ресурсов в WebFlux. Это servlet-приложение, поэтому тоже недостижима. Она исправима в пределах мажорной версии (5.7.13/5.7.14 есть на Central) и была оставлена только чтобы этот раздел оставался зафиксированным на 5.7.11. Исправленный родитель очищает её как побочный эффект, поскольку 5.7.14-0.cgr.2 прошёл версию исправления.
  • CVE-2026-22732 — намеренна в уязвимом состоянии; очищается обновлением родителя.

Остаток структурный: Spring Framework 5.3.x и Spring Security 5.7.x оба EOL. Это, а не Tomcat или Jackson, — настоящий аргумент за миграцию на Boot 3.x.

Структура

root@kitploit:~
pom.xml                     parent + 2 starters, nothing else. Flip the <version> to switch state.
run.sh                      build + run on JDK 17, via settings-chainguard.xml
exploit.sh                  header diff, cache impact, clickjacking check, CVE-scoped verdict
settings-chainguard.xml     Chainguard Libraries repo -- required for the patched parent
src/main/java/com/example/poc/
  PocApplication.java       @SpringBootApplication
  SecurityConfig.java       permitAll, zero header customisation
  AccountController.java    baseline, 2 exploits, 2 controls, 1 diagnostic

Аутентификация — permitAll, а CSRF отключён, чтобы curl работал без аутентификации — ни то, ни другое не является частью этой CVE.

Источники

  • spring.io/security/cve-2026-22732 — официальный advisory
  • GHSA-mf92-479x-3373
  • NVD CVE-2026-22732
  • Broadcom / Tanzu write-up
  • HeroDevs analysis
  • semgrep/cve-2026-22732-demo — воспроизведение, чьи утверждения о stream/flush здесь не подтвердились
  • Red Hat Bugzilla #2449306
Скачать инструмент