CVE-2026-32020 и обход авторизации Spring Security: разбираем реальные уязвимости @PreAuthorize и что проверять в код-ревью

На код-ревью Spring Boot приложения для финтех-стартапа я обнаружил, что @PreAuthorize("hasRole('ADMIN')") на generic-интерфейсе сервиса молча игнорировалась. Все защищённые эндпоинты отвечали 200 вместо 403 для обычного пользователя. Два дня отладки — и я вышел на CVE-2025-41248 и CVE-2025-41249: две уязвимости обхода авторизации Spring Security с CVSS 7.5 HIGH. Разбираю обе ситуации построчно.
CVE-2026-32020 — это не уязвимость Spring Security
По данным NVD (National Vulnerability Database — основная база уязвимостей от NIST), CVE-2026-32020 — path traversal (обход ограничений пути в файловой системе) в OpenClaw, npm-пакете. Статический файловый обработчик OpenClaw следует символическим ссылкам и позволяет читать файлы за пределами корневой директории. CVSS 4.8 MEDIUM. CWE-59 (некорректная обработка символических ссылок) и CWE-22 (Path Traversal).
CISA классифицирует решение как Track — мониторить без срочных действий. Эксплуатация в дикой природе не зафиксирована.
К Spring Security, @PreAuthorize и broken access control эта CVE отношения не имеет. Если вы пришли за разбором обхода авторизации в Spring — ниже реальные CVE, которые стоят вашего внимания.
Обход @PreAuthorize через generic-типы: CVE-2025-41248 и CVE-2025-41249
Как работает method security в Spring
Spring Security даёт method security — механизм защиты отдельных методов Java-классов аннотациями. Аннотация @PreAuthorize содержит SpEL-выражение (Spring Expression Language — встроенный язык выражений Spring), которое вычисляется до вызова метода. Вернулось false — вызов заблокирован, клиент получает 403 Forbidden.
Чтобы всё заработало, в конфигурационном классе нужна @EnableMethodSecurity. Без неё @PreAuthorize, @Secured, @RolesAllowed — мёртвый код. Spring их просто не видит.
Под капотом механизм работает через AOP-прокси (Aspect-Oriented Programming — перехват вызовов методов через обёртку-посредника). Spring создаёт прокси-объект вокруг вашего бина и проверяет аннотации безопасности перед передачей вызова реальному методу. Отсюда жёсткое требование: аннотация должна быть видна прокси в рантайме. Именно тут и ломается.
Почему аннотация теряется в generic-иерархии
Корень проблемы — в том, как Spring разрешает аннотации в иерархиях типов с unbounded generics (неограниченные обобщённые параметры вроде <T>). Когда @PreAuthorize объявлена на методе generic-интерфейса, а конкретный класс реализует этот интерфейс с конкретным типом, механизм обнаружения аннотаций не находит аннотацию на методе реализации.
CVE-2025-41248 (CVSS 7.5 HIGH, CWE-289 — Authentication Bypass by Alternate Name) затрагивает Spring Security 6.4.0–6.4.9 и 6.5.0–6.5.3. Проблема в модуле method security, активируемом через @EnableMethodSecurity. Вы не затронуты, если не используете @EnableMethodSecurity или если не применяете security-аннотации на методах в generic-иерархиях типов — два независимых условия.
CVE-2025-41249 (CVSS 7.5 HIGH, CWE-285 — Improper Authorization) — та же корневая причина, но уровнем ниже: Spring Framework (версии 5.3.0–5.3.44, 6.1.0–6.1.22, 6.2.0–6.2.10). Обе CVE раскрыты одновременно (сентябрь 2025), одним исследователем.
Вектор CVSS: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. Если вы впервые работаете с CVSS-вектором, расшифровка: AV:N — атака по сети, AC:L — низкая сложность, PR:N — привилегии не нужны, UI:N — действий пользователя не требуется, C:H — конфиденциальность нарушена серьёзно (доступ к чужим данным). CISA SSVC: Track, Exploitation: none — эксплуатация в дикой природе не зафиксирована. Но по моей оценке, разработчик с доступом к исходникам приложения соберёт эксплойт за считанные часы.
Уязвимый код: разбор до и после
Типичный паттерн, который приводит к обходу авторизации. Generic-интерфейс для CRUD-операций с аннотациями безопасности:
// Интерфейс с security-аннотациями на generic-методах
public interface CrudService<T> {
@PreAuthorize("hasRole('ADMIN')")
T findById(Long id);
@PreAuthorize("hasRole('ADMIN')")
void deleteById(Long id);
}
Конкретная реализация подставляет тип User:
@Service
public class UserService implements CrudService<User> {
@Override
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@Override
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}
В уязвимых версиях Spring Security прокси не обнаруживает @PreAuthorize на методах UserService. Аннотация объявлена на CrudService<T>, а после type erasure (стирание типов на уровне JVM — компилятор заменяет все T на Object, и метаинформация о generic-типах исчезает) связь между generic-методом и конкретной реализацией теряется для annotation resolver. Результат: любой аутентифицированный пользователь вызывает findById и deleteById без проверки роли ADMIN.
Исправление — два пути:
Первый: обновить Spring Security до 6.4.10 / 6.5.4, Spring Framework до 5.3.45 / 6.1.23 / 6.2.11.
Второй (workaround, если обновление невозможно прямо сейчас): продублировать аннотации в реализации:
@Service
public class UserService implements CrudService<User> {
@Override
@PreAuthorize("hasRole('ADMIN')")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
}
Дублирование убирает зависимость от механизма наследования аннотаций. Минус очевиден — правила повторяются в двух местах, и при рефакторинге кто-нибудь забудет обновить оба. Но это работает на всех затронутых версиях без апгрейда.
Зачем это атакующему: от broken access control до IDOR
Обход авторизации в Spring Security — категория Broken Access Control (A01:2021 по OWASP Top 10 — первое место среди рисков веб-приложений; по данным OWASP, 94% протестированных приложений содержали ту или иную форму этой проблемы).
Атакующий тут ничего не «ломает» в привычном смысле. Он вызывает легитимные API-методы, которые должны быть закрыты ролевой проверкой, но из-за бага annotation resolution — открыты.
Практический сценарий: Spring Boot API финансового приложения. Эндпоинт /api/users/{id} защищён @PreAuthorize("hasRole('ADMIN')") через generic-интерфейс. Из-за CVE-2025-41248 любой зарегистрированный пользователь вызывает GET /api/users/42 и получает данные другого пользователя — имя, email, платёжные реквизиты. Классический IDOR (Insecure Direct Object Reference — прямое обращение к объекту по идентификатору без проверки принадлежности) в Spring Boot приложении.
В терминах MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1190 — её идентификаторы) цепочка выглядит так: начальный доступ через эксплуатацию публичного приложения — Exploit Public-Facing Application (T1190, Initial Access), затем сбор данных из базы — Databases (T1213.006, Collection). Если через обход авторизации доступны учётные записи других пользователей — Valid Accounts (T1078, Privilege Escalation).
По данным Verizon DBIR 2025, 26% подтверждённых нарушений приходятся на веб-атаки как вектор проникновения, 38% утечек связаны с кражей учётных данных. IBM X-Force фиксирует: среднее время между публикацией CVE и устранением в организации — 29 месяцев. Если ориентироваться на эту цифру, подобные уязвимости могут оставаться непропатченными годами.
Свежие CVE авторизации Spring Security 2025–2026
CVE-2025-41248 и CVE-2025-41249 — не изолированные случаи. Вот актуальная картина уязвимостей авторизации:
| CVE | Суть | CVSS | Затронутые версии | Фикс |
|---|---|---|---|---|
| CVE-2025-41248 | @PreAuthorize не резолвится на generic-суперклассе | 7.5 HIGH | SS 6.4.0–6.4.9, 6.5.0–6.5.3 | SS 6.4.10 / 6.5.4 |
| CVE-2025-41249 | То же, на уровне Spring Framework | 7.5 HIGH | SF 5.3.0–5.3.44, 6.1.0–6.1.22, 6.2.0–6.2.10 | SF 5.3.45 / 6.1.23 / 6.2.11 |
| CVE-2026-59270 | Embedded LDAP: хардкодированный пароль admin + wildcard binding (CWE-863) | 9.4 CRITICAL | SS 5.7.0–5.7.25, 5.8.0–5.8.27, 6.4.0–6.4.18, 6.5.0–6.5.11, 7.0.0–7.0.6, 7.1.0 | [проверить на spring.io/security] |
| CVE-2026-22731 | Actuator Health Group: обход аутентификации через path prefix (CWE-288, CWE-306) | 8.2 HIGH | SB 3.4–4.0.2 | SB 4.0.3 / 3.5.11 / 3.4.15 |
| CVE-2026-22733 | CloudFoundry Actuator: аналогичный обход (CWE-288) | 8.2 HIGH | SS 4.0.0–4.0.3, 3.5.0–3.5.11, 3.4.0–3.4.14, 3.3.0–3.3.17, 2.7.0–2.7.31 | SS 4.0.4+ / 3.5.12+ / 3.4.15+ / 3.3.18+ / 2.7.32+ |
| CVE-2026-47841 | WebAuthn: обход user verification при распределённых сессиях (CWE-863) | 7.4 HIGH | SS 6.4.0–6.4.18, 6.5.0–6.5.11, 7.0.0–7.0.6, 7.1.0 | [проверить на spring.io/security] |
(SS — Spring Security, SF — Spring Framework, SB — Spring Boot)
CVE-2026-59270 заслуживает отдельного внимания. CVSS 9.4 CRITICAL, но CISA SSVC — Track, эксплуатация в дикой природе не зафиксирована. Это штатное поведение SSVC-модели: Track — стандартное решение для CVE без подтверждённой эксплуатации, даже при высоком severity. Суть: Embedded LDAP-сервер Spring Security регистрирует credentials uid=admin,ou=system с паролем secret прямо в исходном коде фреймворка и биндит listener на все сетевые интерфейсы хоста. Любой клиент, добравшийся до порта 33389, получает полный административный доступ к директории. Уязвимый код — стандартная конфигурация из официального Spring Security reference guide. Пароль secret в продакшене. В 2026 году.
CVE-2026-22731 и CVE-2026-22733 (описаны HeroDevs) эксплуатируют конфликт между путями Actuator и прикладными эндпоинтами. NVD подчёркивает, что условия эксплуатации и затронутые версии этих двух CVE различаются, несмотря на схожий класс уязвимости. Механика такая: если прикладной эндпоинт /healthz/admin требует аутентификации, а Actuator Health Group маппится на /healthz, Spring Boot применяет разрешительную политику Actuator ко всему prefix — и прикладной эндпоинт молча открывается без аутентификации. Ни ошибок в логах, ни предупреждений. Тишина.
Проверяем свой проект: делай раз, делай два
Предпосылки: доступ к исходному коду, установленный Maven или Gradle, Java IDE (IntelliJ IDEA, Eclipse).
Шаг 1. Определить версию Spring Security. Выполните mvn dependency:tree | grep spring-security (для Maven) или gradle dependencies | grep spring-security (для Gradle). На выходе — строка с org.springframework.security:spring-security-core:X.Y.Z. Если версия попадает в диапазон 6.4.0–6.4.9 или 6.5.0–6.5.3 — проект уязвим к CVE-2025-41248. Для Spring Framework проверьте аналогично spring-core.
Шаг 2. Найти generic-интерфейсы с security-аннотациями. В IntelliJ IDEA: Ctrl+Shift+F (Find in Files), поисковый запрос @PreAuthorize. Отфильтруйте результаты по интерфейсам и абстрактным классам с type parameter (<T>, <E>, <ID>). Аннотация на методе generic-интерфейса, а конкретная реализация не дублирует её — уязвимая точка.
Шаг 3. Подтвердить эксплуатируемость через тест. Напишите интеграционный тест с MockMvc, который вызывает защищённый эндпоинт от имени пользователя без требуемой роли. Используйте @WithMockUser(roles = "USER") (а не «ADMIN»). Тест проходит с HTTP 200 вместо 403 — уязвимость подтверждена.
Шаг 4. Исправить. Обновить зависимости до фиксированных версий. Если обновление невозможно — продублировать аннотации безопасности в конкретных классах и перезапустить тест из шага 3. Теперь должен быть 403.
Чек-лист для код-ревью проверки авторизации
Семь пунктов — каждый закрывает конкретный класс ошибок broken access control в Spring Security:
-
Generic-интерфейсы с @PreAuthorize. Аннотация на методе generic-суперкласса — убедиться, что реализация дублирует её. Иначе CVE-2025-41248.
-
Self-invocation. Вызов аннотированного метода изнутри того же бина обходит прокси. Если
serviceMethod()вызываетthis.protectedMethod(),@PreAuthorizeнаprotectedMethod()не сработает. Решение: вынести защищённый метод в отдельный бин. -
SpEL-выражения с двойным префиксом.
hasRole('ROLE_ADMIN')— ошибка: Spring Security добавляетROLE_автоматически. Правильно:hasRole('ADMIN'). С двойным префиксом проверка не пройдёт, но и не заблокирует — поведение зависит от конфигурации. -
@EnableMethodSecurityприсутствует. Без этой аннотации в@Configuration-классе все method security аннотации игнорируются. Проверить, что она не закомментирована и не потерялась при рефакторинге. -
Negative-тест в PR. Тест, который вызывает эндпоинт без нужной роли и ожидает 403 — обязателен. Тест только на happy path (200 с правильной ролью) баги авторизации не ловит.
-
Кастомный
PermissionEvaluator. Если используется@PreAuthorize("hasPermission(#id, 'read')")с кастомнымPermissionEvaluator— проверить, что он не возвращаетtrueпо умолчанию и корректно обрабатываетnull. -
Версии зависимостей. PR, который меняет версию Spring Security или Spring Framework — проверить по advisory-странице spring.io/security. Новая версия в уязвимом диапазоне — блокировать.
Для автоматизации пунктов 1 и 2 подходит Semgrep с кастомными правилами: одно правило ищет @PreAuthorize на методах интерфейсов с type parameter, второе — вызовы this.method() внутри бинов, где method аннотирован security-аннотацией. Интеграция в CI через semgrep --config custom-rules/ не даст таким PR пройти мимо.
Проблема, из-за которой эти CVE будут повторяться: @PreAuthorize создаёт иллюзию защиты. Разработчик ставит аннотацию, видит 403 в ручном тесте — и считает авторизацию закрытой. Потом кто-то рефакторит иерархию, добавляет generic-суперкласс — и аннотация молча перестаёт действовать. Ни ошибок в логах, ни warning-ов в compile-time. Spring Security за последний год получил десятки CVE по авторизации: от хардкодированных паролей LDAP (CVE-2026-59270, CVSS 9.4 CRITICAL) до обхода аутентификации через Actuator path prefix (CVE-2026-22731, CVSS 8.2 HIGH). Каждая — стандартная конфигурация из документации.
Мой вывод после нескольких лет в AppSec: декларативная авторизация через аннотации должна быть последним слоем защиты, а не единственным. Первый слой — URL-based правила в SecurityFilterChain с явными паттернами. Второй — бизнес-логика, которая проверяет принадлежность объекта текущему пользователю (не роль, а именно ownership). Аннотации @PreAuthorize — третий, страховочный слой. Уберите любой из трёх — и если система станет уязвимой, архитектура авторизации хрупкая. Если вы переходите в AppSec из разработки и хотите разобраться в базовых принципах защиты приложений системно — на IB Basics закрывают фундамент за пару месяцев без академического тона.
Эту тему и смежные навыки разбирают на практике в курсе «Профессия: инженер по безопасности приложений (AppSec)» Codeby Academy.