Разбираем три самые интересные уязвимости Spring за июнь

В июне 2026 года был опубликован список из 71 уязвимости в экосистеме Spring. Из них 31 получила уровень опасности High, 36 — Medium и четыре — Low. Для сравнения: за весь 2025 год было обнаружено 16 уязвимостей.
Из общего списка уязвимостей мы выбрали три, в которых сочетаются относительно понятный путь до уязвимого кода и серьёзные последствия. Нас интересовало, как ошибка проявляется в распространённых сценариях: при обработке JSON Patch, аутентификации через LDAP и передаче сообщений через Kafka.
Разберём чуть подробнее три самые интересные уязвимости по соотношению «достижимость × последствия»:
CVE-2026-41729 — SpEL-инъекция через ключ
Mapв JSON Patch (Spring Data REST).CVE-2026-41720 — обход аутентификации пустым паролем в Spring LDAP (unauthenticated bind).
CVE-2026-41731 — небезопасная десериализация заголовков в Spring for Apache Kafka (префиксное сопоставление доверенных пакетов).
1. CVE-2026-41729: SpEL-инъекция через ключ Map в JSON Patch
CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
В чём суть
Spring Data REST принимает JSON Patch (RFC 6902) с Content-Type: application/json-patch+json. Каждая операция патча (add, replace, test и т.д.) задаёт путь к полю в формате JSON Pointer. Когда сохраняемый объект (корневой агрегат) содержит свойство-Map, сегмент пути JSON Pointer трактуется как ключ Map и без экранирования подставляется в SpEL-выражение для обращения к значению по этому ключу. Специально сформированный ключ нарушает структуру выражения metadata['<ключ>']: закрывает строковый литерал раньше времени и позволяет добавить другое SpEL-выражение, которое будет вычислено в контексте корневого агрегата.
Затронуты оба пути обработки: чтение (когда патч обращается к текущему значению) и запись (когда патч изменяет данные).
Класс ошибки — CWE-917 (недостаточная нейтрализация специальных элементов при построении выражения).
Технический механизм
Для обычного объекта путь /profile/name преобразуется в последовательность обращений к Java-свойствам (profile.name). Если свойство имеет тип Map, к его значению нужно обратиться по ключу:
Путь JSON Pointer: /metadata/<map-key>
Выражение SpEL: metadata['<map-key>']Проблема в том, что <map-key> не экранируется перед тем, как попасть внутрь строкового литерала '...':
Ключ (ожидание): "config"
Результат: metadata['config']
Ключ (атака): "x'] + T(java.lang.System).getProperty('os.name') + ['"
Результат (уязвимо): metadata['x'] + T(...).getProperty('os.name') + ['']
└─ вне намеренного ключа вычисляется произвольный SpEL-кодТак ключ, который должен был быть обычным идентификатором, превращает выражение в цепочку произвольных вызовов, выполняющихся в контексте сохраняемого объекта и его вложенных полей.
Поток данных при атаке:
HTTP PATCH
→ Content-Type: application/json-patch+json
→ разбор операций JSON Patch
→ извлечение пути (JSON Pointer)
→ определение, что свойство имеет тип Map
→ построение конструкции metadata['<ключ>'] без экранирования ключа Map
→ вычисление SpEL в контексте корневого агрегата
→ чтение или изменение данных за пределами намеренного ключаУщерб для организации
Доступ к данным за пределами указанного ключа — выражение вычисляется в контексте всего корневого агрегата, поэтому доступны все его поля и вложенные объекты, даже не предназначенные для выдачи (
C:H).Несанкционированное изменение данных — путь записи позволяет менять не те поля, которые указаны в патче, в том числе в обход ограничений «только для чтения» модели (
I:H).Потенциальное выполнение кода — при наличии в контексте вычисления полезных классов выражение может дойти до вызова произвольных методов JVM (вектор RCE-класса).
Высокая достижимость — триггер не требует разработки специфичного кода: достаточно обычной модели с Map-полем и стандартного Spring Data REST, поэтому уязвимых приложений много, а барьер атаки
AC:L/PR:Lнизкий.Для организации: утечка персональных данных (инцидент по 152-ФЗ, уведомление регулятора, штрафы), тихая порча доменных данных (позднее обнаружение через аудит, а не через атаку), мошенничество при компрометации платёжных/залоговых полей.
Итог: самая опасная из трёх по последствиям — потеря конфиденциальности и целостности на высоком уровне с низким порогом эксплуатации, при том что JSON Patch открыт по умолчанию. Усугубляется тем, что первый шаг эксплуатации не является явной аномалией для центра мониторинга информационной безопасности.
2. CVE-2026-41720: обход аутентификации пустым паролем в Spring LDAP
CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
В чём суть
Реализации DirContextAuthenticationStrategy в Spring LDAP не отклоняют bind-запрос, в котором указано непустое имя пользователя в паре с пустым (null) паролем. По RFC 4513 §5.1.2 такой запрос называется unauthenticated bind: клиент сообщает имя, но не предъявляет секрет. Если LDAP-сервер разрешает такой bind, проверка пароля фактически не выполняется.
Ранний аналог в Spring Security — CVE-2014-0079 (та же первопричина: empty-password bind прошёл проверку).
Технический механизм
Протокол LDAP различает три случая:
Bind-запрос | Пароль | Семантика |
|---|---|---|
Пустой DN | пустой | Анонимный доступ (anonymous) |
Непустой DN | пустой | Unauthenticated bind |
Непустой DN | непустой | Обычная проверка пароля |
Сервер с ослабленной конфигурацией (например, разрешённый allow bind anon dn в OpenLDAP / 389 DS) может признать unauthenticated bind успешным, не проверив пароль. Spring LDAP на клиенте воспринимает успешный bind как успешную аутентификацию и не сверяет «пароль был» — стратегия просто не фильтровала пустой пароль до отправки запроса.
Уязвимый путь затрагивает все способы аутентификации через:
AbstractContextSource(и его наследникиDefaultSpringSecurityContextSource);LdapTemplate.authenticate(...);LdapClient.authenticate(...).
Поток атаки:
Атакующий знает имя пользователя (admin / сервисный аккаунт / пользователь)
→ отправляет bind: dn=uid=<username>,ou=...,dc=corp password=""
→ Spring LDAP не отбрасывает запрос как «пустой пароль»
→ сервер выполняет unauthenticated bind и возвращает success
→ приложение считает пользователя аутентифицированнымУщерб для организации
Компрометация корпоративного поставщика идентификации — LDAP часто обслуживает всю экосистему: одну атаку можно переиспользовать против любого подключённого приложения.
Доступ под привилегированными учётками — сервисные и админ-аккаунты становятся самой привлекательной целью, т.к. не привязаны к человеческим процессам MFA/смены пароля.
Отсутствие следов — аутентификация формально «успешна», события входа не содержат признаков аномалии; SIEM и блокировки по числу неудачных попыток не срабатывают (попытка не «неудачная»).
Латеральное движение и долгое присутствие — сессия от имени легитимного пользователя позволяет спокойно обходить продуктовую защиту приложений.
Итог: критичность в слое идентификации, как только сервер разрешает unauthenticated bind.
3. CVE-2026-41731: небезопасная десериализация заголовков в Spring for Apache Kafka
CVSS 3.1: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
В чём суть
JsonKafkaHeaderMapper и устаревший DefaultKafkaHeaderMapper сопоставляли заголовки типа сообщения со списком «доверенных пакетов» по префиксу: доверие одному пакету автоматически означало доверие всем его подпакетам. В сочетании со стандартной Jackson-десериализацией бинов продюсер мог подложить в заголовок имя произвольного JDK-класса — консьюмер десериализовал его, включая классы, чьи конструкторы несут побочные эффекты (выделение файловых дескрипторов, создание пулов потоков).
Класс ошибки — CWE-502 (десериализация недоверенных данных) с первопричиной «некорректная проверка доверенного диапазона по префиксу».
Технический механизм
Spring for Apache Kafka передаёт информацию о типе через заголовки сообщения: TypeId, KeyTypeId, ContentTypeId. Тип-информацию из этих заголовков читают оба механизма JSON-десериализации:
JsonDeserializer— низкоуровневый десериализатор консьюмера (spring.kafka.consumer.value-deserializer) — снимает заголовок напрямую;JsonMessageConverter(например,ByteArrayJsonMessageConverter) — преобразователь уровня контейнера прослушивания, за которым уже стоит байтовый десериализатор.
Оба пути проверяют указанный в заголовке класс против списка доверенных пакетов через маппер заголовков (JsonKafkaHeaderMapper или устаревший DefaultKafkaHeaderMapper).
Уязвимость в логике проверки:
Доверенные пакеты (конфигурация): com.example.app
Проверка по префиксу (уязвимо): com.example.app.SomeType → OK
com.example.appx.Evil → OK (префикс совпал!)
java.lang.ThreadPool → OK, если доверен java.*
java.io.FileOutputStream → OK, если доверен java.Доверие по префиксу означает: если в списке есть com.example или (частая практика) java.util, java.io, java.lang либо корневой пакет приложения, то «пропускается» весь диапазон подпакетов — включая стандартные библиотечные классы JVM. Дальше работает десериализация по умолчанию: Jackson инстанцирует указанный класс через конструктор, а любой класс с побочным эффектом в конструкторе выполняет его прямо при чтении сообщения.
Поток атаки:
Продюсер (недоверенный, имеет доступ на запись в топик)
→ публикует сообщение с заголовком TypeId: java.io.FileOutputStream / java.util.concurrent.ScheduledThreadPoolExecutor ...
→ Kafka доставляет сообщение в консьюмер (@KafkaListener)
→ JsonKafkaHeaderMapper проверяет заголовок типом против доверенных пакетов по префиксу
→ проверка проходит («доверен java.*»)
→ Jackson создаёт экземпляр указанного класса
→ побочный эффект конструктора срабатывает в процессе консьюмераУщерб для организации
RCE-класс при широком доверенном диапазоне — десериализация произвольных типов в процессе JVM (
C:H/I:H/A:H): чтение/изменение данных на узле консьюмера и потенциальное исполнение кода.Латентность и «вслепую» — сообщение лежит в топике и срабатывает в момент обработки любым из потребителей; источник отделён во времени от последствия, что сильно затрудняет связывание инцидента с конкретным событием.
Каскадный охват — в событийно-ориентированной архитектуре один скомпрометированный продюсер отравляет сообщения всем консьюмерам топика сразу: неделя расследований вместо одного запроса.
Тихий отказ в обслуживании через ресурсы — исчерпание файловых дескрипторов или потоков выглядит как падение производительности «на ровном месте», а не как атака.
Для организации: в микросервисной Spring-системе Kafka — ядро обмена, поэтому доверенный контур «производители → топик → потребители» должен быть строгим; размытие этой границы из-за широких доверенных пакетов обнуляет изоляцию сервисов.
Итог: самый «серьёзный по классу» вектор из трёх — десериализация недоверенных данных с потенциальным RCE у каждого потребителя, что особенно критично для событийно-ориентированных архитектур, где Kafka является системообразующим компонентом.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.