Разбираем метод глубокого анализа уязвимостей (Deep-dive), когда простая рекомендация из SCA-отчета не решает проблему, и нужно копать глубже — вплоть до кода библиотеки, контекста использования и ручной проверки уязвимости.

Вступление
AppSec-инженерам, занимающимся практикой SCA, наверняка знаком следующий алгоритм работы с уязвимостями:
Создать SBOM-файл кодовой базы;
Загрузить SBOM-файл в инструмент SCA;
Выгрузить результаты проверки инструмента;
Сделать отчет примерно такого содержания.
Название кодовой базы |
Уязвимая библиотека |
Версия уязвимой библиотеки |
ID уязвимости |
Причина уязвимости |
Рекомендации по исправлению |
---|---|---|---|---|---|
test-code |
vllm (pip) |
0.7.0 |
CVE-2025-32444 |
vLLM integration with mooncake is vaulnerable to remote code execution due to using |
Обновить до версии 0.8.5 |
Мы порекомендовали обновить библиотеку до более новой версии, так как разработчик уже подумал за нас и исправил уязвимость. В большинстве случаев этого хватает. Во всяком случае, на первых этапах.
С вами Вера Багно, AppSec-инженер компании Swordfish Security. В этой статье речь пойдет о тех случаях, когда рекомендация «обновитесь!» не решает проблему. Мы рассмотрим, в каких ситуациях нужно более глубоко погружаться в причины возникновения уязвимости, разбираться в ее актуальности для конкретного продукта и самостоятельно находить способы обойти проблему уязвимости в библиотеке. Иными словами, в этой статье речь пойдет о Deep-dive исследовании.
Эта статья будет интересна как специалистам ИБ, так и разработчикам, желающим сделать свой продукт безопаснее.
Deep-dive и его этапы
0. А нужен ли Deep-dive?
Сразу обозначу, что исследование Deep-dive очень трудоемкое — оно требует много времени на изучение уязвимости и ее выявление в разрабатываемом продукте. Это стоит учитывать, когда принимается решение о начале исследования. Исходя из собственного опыта, мы вывели несколько критериев, по которым вы можете понять, нужно ли заниматься Deep-dive.
-
Безопасной версии библиотеки не существует.
Например, это новая уязвимость высокой критичности, для которой еще не выпущено ни исправленной версии, ни патча.
-
Невозможно обновиться на более безопасную версию / переход на другую версию сопряжен со слишком большими трудностями.
В качестве примера можем привести фреймворк Spring Boot и ситуации с переходом с версии 5 на версию 6. Многие компании и продукты сталкивались с необходимостью обновиться до версии 6, потому что в ней исправлены многие уязвимости прошлой версии. Однако вместе с этими исправлениями была нарушена совместимость, что привело к невозможности простого обновления. Из-за этого переход на новую версию получается трудоёмким, эта задача уходит в бэклог команды и как правило висит там долгое время.
Таким образом, мы начинаем исследовать уязвимость в тех случаях, когда обновление невозможно или связано с большими трудозатратами команды разработки.
1. Верификация компонента и уязвимости в SCA-сканере
Следует убедиться, что сканер корректно идентифицировал библиотеку, ее версию и уязвимость. Сканеры могут выдавать ошибки — это связано с внутренней работой сканера, с актуальностью БД, откуда он берет информацию об уязвимости, и с файлом, на основании которого генерируется SBOM.
-
Смотрим отчет сканера — ищем название библиотеки, версию библиотеки и идентификатор уязвимости. Удостоверяемся, что сканер корректно идентифицировал библиотеку (
groupId:artifactId:версия
в Maven илиname@версия
в npm/PyPI). Таким образом мы исключаем ошибку сканера при идентификации версии библиотеки: проверяем, используется ли у нас именно та версия, которая указана в CVE и в сканере. Если версия отличается (например, используется минорная версия или патч), то следует проверить, есть ли backport-фикс, или же уязвимость актуальна. Для разных языков программирования информация о компонентах содержится в разных файлах:В Java:
pom.xml
,build.gradle
,MANIFEST.MF
.В Python:
requirements.txt
,setup.py
,pyproject.toml
,poetry.lock
.В JavaScript:
package.json
,package-lock.json
,yarn.lock
.
-
Проверяем, действительно ли уязвимость присутствует в данной версии библиотеки.
Рекомендуем искать уязвимость по ее идентификатору на следующих сайтах:
После этого смотрим диапазон уязвимых версий — на сайтах NVD и GHSA это есть, если только уязвимость не находится в обработке. Желательно проверять сразу несколько источников, так как информация может отличаться.
Если со стороны сканера ошибок нет и уязвимость актуальна, переходим к следующему шагу.
2. Анализ CVE и источников уязвимости
На этом этапе мы ищем информацию по уязвимости, чтобы проверить ее критичность, условия эксплуатации и актуальность вектора атаки.
-
Изучаем информацию об уязвимости из источников (NVD, GHSA и др.). Нас интересует следующее:
Описание уязвимости. Порой уязвимость в библиотеке связана с неверно настроенной конфигурацией или с конкретной функцией (конкретным методом), которая неправильно обрабатывает входные данные.
Векторы атак (CVSSv2 или CVSSv3) и их актуальность для нашего приложения. Если оно предназначено для использования внутри закрытого контура, уязвимость с вектором Network (AV:N в CVSSv3) не будет считаться опасной.
Наличие информации о существующих эксплоитах для уязвимости (Exploit-DB, PoC).
Наличие официального исправления (GitHub commit, issue tracker).
-
Проверяем, можем ли мы использовать что-либо из перечисленного выше, чтобы отметить уязвимость как неактуальную для приложения. Если нет, то смотрим, используется ли небезопасная конфигурация или функция в нашем исходном коде, и можно ли исправить ее. Например, написать проверку недоверенных данных (или иным образом «закрыть» уязвимость собственным кодом). Также может встретиться рекомендация от разработчика, как быстро пропатчить уязвимость:
Конфигурационные фиксы (например, отключение JNDI в Log4j).
Патчинг на лету (например, через JVMTI-агенты).
Workaround — обходное решение для закрытия уязвимости.
3. Проверка эксплуатируемости в контексте приложения
Если уязвимость теоретически есть, но на практике уязвимый метод не используется, то риск может быть низким. Для многих уязвимостей доступна оценка EPSS (Exploit Prediction Scoring System). Она используется для измерения вероятности эксплуатации уязвимости в реальном мире. EPSS для уязвимостей можно найти в Github Advisory (не всегда) или в инструменте (например, Dependency-Track показывает EPSS для уязвимостей в проекте).

Примеры уязвимостей, для эксплуатации которых нужны дополнительные условия:
Log4Shell (CVE-2021-44228): Есть ли логирование с пользовательским вводом?
SQL-инъекция в библиотеке: Вызываются ли опасные методы?
Инструменты:
Статический анализ (SAST) — ищем вызовы уязвимых методов. Один из методов — сделать срез и/или проследить data‑flow (подробнее — в статьях «Секреты успешного SCA» и «Какие ваши доказательства»)
-
Поиск кода:
grep -r "уязвимый_класс" src/
Semgrep, CodeQL.
Поиск существующих эксплоитов.
Дополнительно можно попробовать проэксплуатировать уязвимость в тестовом окружении. Чаще всего так далеко вы заходить не будете, но все же, если эта опция доступна (по нашему опыту — чаще нет, чем да). Попытка эксплуатации может дать полезное представление о том, как работает уязвимость и чем она реально может грозить. Для ручного сканирования можно воспользоваться следующими инструментами:
Burp Suite / ZAP для тестирования
nmap
Использование отдельных эксплоитов
На всякий случай напомним, что эксплуатировать уязвимости на продакшене — это почти всегда плохая идея, приложение необходимо поднимать на отдельном тестовом стенде.
Разбираем уязвимости
Для примера возьмем приложение DVJA. Мы просканировали его инструментом SCA и обнаружили несколько уязвимостей. Для разбора возьмем две уязвимости:
Компонент spring-expression, критичность critical, CVE-2018-1270, GHSA: GHSA-p5hg-3xm3-gcjg
Компонент struts2-core, критичность critical, СVE-2017-5638, GHSA-j77q-2qqg-6989
На примере этих двух уязвимостей проведем анализ Deep-dive и узнаем, стоит ли нам опасаться последствий их эксплуатации. Стоит ли срочно обновляться, или в данном случае риск минимален и можно не паниковать?
Уязвимость CVE-2018-1270 в spring-expression
1. Верификация компонента и уязвимости
Найдем всю информацию, которая касается компонента и содержащейся в нем уязвимости. Инструмент Dependency-Track, который мы использовали для SCA-сканирования, предлагает просмотреть, где в дереве зависимостей находится данный компонент. Как видим, он располагается на третьем и четвертом уровнях транзитивных зависимостей, поэтому напрямую его обновить достаточно проблематично.

Убедимся, что данная версия была идентифицирована верно. Откроем SBOM-файл и поищем эту библиотеку.
{
"type" : "framework",
"bom-ref" : "fd6f1ca8-51ca-483d-b3fe-0e83bda60ec8",
"group" : "org.springframework",
"name" : "spring-expression",
"version" : "3.0.5.RELEASE",
"description" : "Spring Framework Parent",
<...>
}
Название библиотеки и ее версия совпадает. В файле pom.xml эту библиотеку искать смысла нет — транзитивные зависимости там не фиксируются.
2. Анализ CVE и источников уязвимости
Начнем с описания уязвимости:
Spring Framework версии 5.0 до 5.0.5, версии 4.3 до 4.3.15 и более старые неподдерживаемые версии позволяют приложениям раскрывать STOMP через конечные точки WebSocket с помощью простого брокера STOMP в памяти через модуль spring-messaging. Злонамеренный пользователь (или атакующий) может создать сообщение для брокера, которое может привести к атаке удаленного выполнения кода (RCE).
Видим риск RCE, ставки растут.
Из описаний уязвимостей вынесем главное: атака идет на STOMP через WebSocket. STOMP — это простой (или потоковый) текстовый протокол обмена сообщениями (Simple (or Streaming) Text Orientated Messaging Protocol), разработанный для асинхронной передачи сообщений между клиентами через промежуточные серверы.
Вектор атаки: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (атака через сеть, сложность атаки низкая, привилегии не требуются, затрагиваются конфиденциальность, целостность и доступность). Если бы мы разворачивали приложение DVJA внутри закрытого контура компании, критичность уязвимости можно было бы снизить (исходя из предположения, что сотрудники вряд ли будут атаковать приложение).
EPSS 89.353% (то есть эксплуатация более чем реальна).
Эксплоиты существуют. Но нам их не покажут (даже Wayback Machine оказался бессилен).

Аналогичная история с официальной документацией — обе ссылки из References возвращают 404. Но можно найти эксплоит на GitHub — обычно исследователи публикуют свои самописные эксплоиты в репозиториях с именем уязвимости, для которой этот эксплоит написан.
Как видим, уязвимость существует уже больше 6 лет. Понятно, что это тестовый пример, да и проект DVJA не развивается примерно столько же времени, однако на практике часто можно найти устаревшие компоненты даже в более молодых и активно развивающихся проектах.
3. Достижима ли уязвимость?
На этом этапе мы сканируем исходный код инструментами SAST, чтобы проверить, используются ли в коде уязвимые функции и можно ли проэксплуатировать найденные уязвимости.
Так как на предыдущем этапе мы выяснили, что атака идет с помощью WebSocket, можно идти к разработчику и спрашивать, используется ли WebSocket непосредственно в приложении DVJA.
Исходный код в его нынешнем виде сканировать SAST-ом не имеет смысла — внешние библиотеки все равно отсутствуют. Достаточно погрепать код и обнаружить, что библиотека непосредственно нигде не вызывается.
А вот с динамическим анализом интереснее (повторюсь, использование инструментов динамического анализа выходит за рамки Deep-dive-исследования, но иногда эту практику можно использовать в дополнение к Deep-dive). Во-первых, мы можем поднять приложение в Docker и поисследовать его. Во-вторых, если протокол WebSocket в нем используется, то мы можем увидеть это в DevTools, поставив фильтр по WS:

И... вебсокетов тут нет.
Но чтобы точно удостовериться, что никаких проблем не будет, мы запустили найденный на этапе 2 эксплоит с измененными под наш стенд параметрами. И этот эксплоит ожидаемо тоже ничего не нашел.
Уязвимость СVE-2017-5638 в struts2-core
1. Верификация компонента и уязвимости
Найдем всю информацию, которая касается компонента и содержащейся в нем уязвимости. В инструменте Dependency-Track в графе зависимостей можно увидеть, что этот модуль находится и в прямой, и в транзитивной зависимости:

Убедимся, что данная версия была идентифицирована верно. Откроем SBOM-файл и поищем эту библиотеку:
"components":[
{
"publisher":"Apache Software Foundation",
"group":"org.apache.struts",
"name":"struts2-core",
"version":"2.3.30",
"description":"Apache Struts 2",
"scope":"required",
"licenses":[
{
"license":{
"id":"Apache-2.0",
"url":"https://opensource.org/licenses/Apache-2.0"
}
}
],
"purl":"pkg:maven/org.apache.struts/struts2-core@2.3.30?type=jar",
}
Название библиотеки и ее версия совпадают. В файле pom.xml библиотека struts2-core тоже присутствует.
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>${struts2.version}</version>
</dependency>
2. Анализ CVE и источников уязвимости
А теперь рассмотрим уязвимость. Ознакомимся с ее описанием:
Версии Apache Struts до 2.3.32 и 2.5.10.1 содержат некорректную обработку исключений и генерацию сообщений об ошибках при попытках загрузки файлов с использованием анализатора Jakarta Multipart, что позволяет удаленным злоумышленникам выполнять произвольные команды с помощью специально созданного HTTP-заголовка Content-Type, Content-Disposition или Content-Length, что было продемонстрировано в марте 2017 года с использованием заголовка Content-Type, содержащего строку #cmd=.
Поскольку атака может быть реализована при помощи HTTP-заголовка, для эксплуатации уязвимости необходимо использование анализатора Jakarta Multipart.
Вектор атаки: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H. Атака по сети, без привилегий, уязвимость бьет по конфиденциальности, целостности и доступности.
EPSS 94,267% (очень высокий риск эксплуатации).
Эксплоиты существуют. В отличие от предыдущей уязвимости, можно даже не гуглить их по названию уязвимости, эксплоит указан в ссылках на GHSA.
Аналогично можно найти два workaround:
Реализовать фильтр сервлета, который будет проверять Content-Type и отбрасывать запросы с подозрительными значениями, не соответствующими multipart/form-data.
Переключиться на другую реализацию парсера Multipart (для Apache Struts 2.3.8 - 2.5.5 в случае использования парсера Jakarta multipart по умолчанию или Apache Struts 2.3.20 - 2.5.5 при использовании альтернативного парсера jakarta-stream multipart). Другой вариант — удалить перехватчик загрузки файлов из стека, просто определить свой собственный стек и установить его в качестве стека по умолчанию. Это будет работать только для Struts 2.5.8 - 2.5.10. (У нас версия 2.3.30, работа с перехватчиком загрузки файлов не подходит).
3. Достижима ли уязвимость?
На этом этапе мы сканируем исходный код, чтобы проверить, используются ли в нем уязвимые функции и можно ли проэксплуатировать найденные уязвимости. Достаточно погрепать код и обнаружить, что классы библиотеки импортируются в BaseController.java:

Для того чтобы убедиться в достижимости библиотек, используем инструмент depscan от OWASP — в нем вшит инструмент atom, позволяющий анализировать потоки данных в коде.
Запустим команду в директории dvja:depscan --profile research -t java -i . --reports-dir ./report-analyzer --explain
И получим вывод инструмента dep-scan:

Только библиотека struts2-core была помечена как достижимая.
Попробуем найти уязвимую вызываемую функцию. Если поискать Jakarta.Multiparse с помощью grep, то мы ничего не обнаружим:

Продолжим поиски информации. Например, отсюда можно выяснить, что ранее пакет назывался javax. Ищем javax.servlet.http. Мы обнаружили импорт библиотек javax.servlet.http.Cookie и javax.servlet.http.HttpServletRequest:

Чтобы выяснить, какие функции вызываются, можно открыть файл df.json (он генерируется автоматически при проверке depscan), в котором depscan отобразил dataflow, или поток данных, для приложения DVJA. В этом JSON-файле поищем javax.servlet.http:

Обратим внимание на поле fullName — в нем указано, что эта функция принадлежит struts2. В поле label значение CALL — функция вызывается в BaseController.java (parentClassName) в строке 60 (lineNumber). Проверим код в BaseController.java — и увидим вызов этой функции.

Благодаря файлу df.json можно найти точки входа для уязвимых функций вместе с номером строки, в которой они вызываются. И вот с этим уже можно идти к разработчику для разговора.
Можно попробовать проверить наличие этой уязвимости на развернутом сервере при помощи nmap или других инструментов:
nmap --script http-vuln-cve2017-5638 -p 80,443,8080 127.0.0.1

Результат по Deep-dive двух уязвимостей
Найденная уязвимая библиотека не используется напрямую в приложении DVJA, соответственно, уязвимость CVE-2018-1270 не является актуальной. Ей можно понизить степень критичности или вообще пометить ее как FalsePositive. Уязвимость CVE-2017-5638 является актуальной для приложения и мы можем использовать workaround, чтобы ее митигировать.
spring-expression 3.0.5 RELEASE |
struts2-core 2.3 |
---|---|
CVE-2018-1270 |
CVE-2017-5638 |
Транзитивная зависимость |
Прямая зависимость |
FalsePositive |
TruePositive |
Вывод
Deep‑dive в SCA — это не только работа со сканерами. Часто это анализ контекста. Для разных приложений пути исследования, результаты и рекомендации по исправлению будут отличаться. Иногда «уязвимость» есть в проекте, но не представляет риска из‑за отсутствия эксплуатационного пути.
В этой статье мы попытались стандартизировать путь Deep‑dive, обозначив наиболее важные вехи. Но даже в выбранных нами примерах не все прошло гладко: не все источники информации были доступны. В реальной разработке тоже не всегда будет возможность развернуть приложение для его глубокого исследования, не всегда будет достаточно времени, чтобы проводить такое исследование по каждой уязвимой библиотеке.
Четкой стандартизации методов исследования для Deep‑dive нет и быть не может. Все будет упираться в самостоятельный поиск информации, исследование поведения приложения и общение с разработчиками.