Патчите релиз, а не только CVE: метрика актуальности версий

Процесс управления уязвимостями на сайте, как и у большинства компаний, построен вокруг цепочки «скан → CVE → KEV/EPSS → приоритет → патч». Это рабочая схема, но у неё есть слепая зона, которая быстро растёт: не каждое исправление вендора получает CVE. Присвоение CVE для найденной и исправленной у себя ошибки — это SHOULD в правилах CNA, не MUST, и вендор может просто выпустить релиз с формулировкой «несколько исправлений безопасности» без разбивки по идентификаторам. Сканер, настроенный искать совпадения по CVE, в таком релизе ничего не найдёт — для процесса, где «если не в сканере, то не произошло», такого исправления просто не существует.
Статья опирается на логику направления «Управление уязвимостями» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Идея разделить метрику актуальности версий от метрики закрытия CVE обсуждается в профессиональном сообществе управления уязвимостями, в частности в блоге Антона Чувакина (источник); мы пересказываем мысль своими словами и предлагаем свою реализацию метрики.
Что должно получиться
| Уровень | Что это значит на практике |
|---|---|
| 2. Гигиенический | Рядом с метрикой закрытия CVE в отчёте есть метрика актуальности версий, которая не зависит от того, присвоен ли исправлению идентификатор |
Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия.
Почему CVE-центричный процесс слеп к части рисков
KEV и EPSS — два источника, на которые опирается приоритизация в нашем основном процессе, — оба проиндексированы по CVE: KEV перечисляет только записи с присвоенным идентификатором, EPSS оценивает вероятность эксплуатации только для CVE. Если у находки нет CVE, оба источника молчат про неё одинаково — не потому, что риска нет, а потому, что у них просто нет ключа для поиска. «Риск-ориентированный» процесс, построенный целиком на этих источниках, наследует их слепую зону, а не компенсирует её.
На практике разрыв между количеством реально исправленных багов у вендора и количеством присвоенных CVE в одном релизе может быть большим: формулировка в примечаниях к выпуску вида «multiple security fixes» без разбивки по номерам — сигнал, что часть исправлений в CVE не попала. Крупные вендоры сетевого оборудования и ОС всё чаще публикуют плановые выпуски безопасности пачкой, без идентификатора на каждый отдельный исправленный баг.
Метрика актуальности версий
Решение — не отказ от CVE-ориентированного SLA, а добавление рядом второй, независимой метрики: доля активов, находящихся в пределах N релизов (или версий) от последнего вендорского выпуска безопасности. Эта метрика не спрашивает «закрыт ли CVE X» — она спрашивает «насколько свежая версия стоит», и работает одинаково для релиза с подробным списком CVE и для релиза с одной общей фразой про исправления.
Скрипт расчёта
#!/usr/bin/env python3
"""Метрика актуальности версий: доля активов в пределах N релизов от последнего security-релиза вендора.
Не привязано к CVE: релиз считается релизом независимо от того, получили ли его исправления отдельные идентификаторы.
Вход:
releases.txt — релизы вендора по порядку выхода, старый->новый, один на строку (как публикует вендор)
assets.csv — колонки: asset,vendor_product,installed_version
"""
import csv
import sys
def load_releases(path):
with open(path, encoding="utf-8") as f:
return [l.strip() for l in f if l.strip() and not l.startswith("#")]
def load_assets(path):
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def releases_behind(installed, releases):
if installed not in releases:
return None # версия не из публичного списка релизов - разбирать отдельно, не считать "0 отставания"
idx = releases.index(installed)
return len(releases) - 1 - idx
def main(releases_path, assets_path, threshold=1):
releases = load_releases(releases_path)
assets = load_assets(assets_path)
rows = []
unknown = 0
for a in assets:
behind = releases_behind(a["installed_version"], releases)
if behind is None:
unknown += 1
rows.append((a["asset"], a["installed_version"], behind))
within = sum(1 for _, _, b in rows if b is not None and b <= threshold)
known = len(rows) - unknown
pct = (within / known * 100) if known else 0.0
print(f"Релизов в списке: {len(releases)} (последний: {releases[-1]})")
print(f"Активов: {len(rows)}, версия не распознана: {unknown}")
print(f"В пределах {threshold} релиза(ов) от последнего: {within} из {known} распознанных ({pct:.0f}%)")
Результат проверки
На тестовом списке из пяти релизов условного продукта и пяти активов скрипт корректно определил:
Релизов в списке: 5 (последний: 2026.3)
Активов: 5, версия не распознана: 1
В пределах 1 релиза(ов) от последнего: 2 из 4 распознанных (50%)
edge-fw-4 2025.3 отстаёт на 4
edge-fw-3 2025.4 отстаёт на 3
edge-fw-2 2026.2 отстаёт на 1
edge-fw-1 2026.3 отстаёт на 0
edge-fw-5 custom-2026.3-patched неизвестная версия
Важная деталь поведения, которую стоило проверить отдельно: актив с нестандартной строкой версии (собственная сборка с патчем) не засчитан автоматически ни как «актуальный», ни как «отстающий» — он помечен как «неизвестная версия» и требует отдельного разбора. Это осознанное решение: молча засчитывать нераспознанную версию как актуальную было бы опаснее, чем явно показать её как требующую внимания.
Самопроверка на 30 минут
Прежде чем встраивать метрику в постоянный процесс, оцените размер слепой зоны на одном примере:
- Возьмите своего крупнейшего по числу активов или по доступности из интернета вендора.
- Откройте примечания к его последним трём релизам безопасности и посчитайте: сколько отдельных исправлений упомянуто и сколько из них получили собственный CVE.
- Сравните со списком находок вашего сканера за тот же период по этому вендору.
Разница между тем, что вендор исправил, и тем, что увидел сканер, — это размер вашей слепой зоны в конкретных числах, а не в абстрактном беспокойстве.
Порядок внедрения
- Для критичных и доступных извне категорий активов (периметр, VPN, сетевое оборудование) начните отслеживать версию отдельно от списка закрытых CVE.
- Спросите у поставщика сканера, как он обрабатывает бюллетень вендора без CVE: заводит ли находку под собственным идентификатором, с какой серьёзностью, и есть ли задержка между публикацией бюллетеня и появлением проверки.
- Оформляйте исключения на релиз, а не на отдельный CVE, если вендор не разбивает исправления по номерам — иначе формально «закрытых CVE нет», хотя обновление реально нужно.
- Добавьте метрику актуальности в отчёт рядом с привычным процентом закрытых CVE в срок, не вместо него.
- Если вендор публикует машиночитаемые бюллетени (формат CSAF) с версией исправления, заводите список релизов автоматически из этого источника, а не вручную по примечаниям к выпуску.
Диагностика
| Симптом | Причина и что делать |
|---|---|
| Метрика закрытия CVE стабильно близка к 100%, но вендор продолжает выпускать «срочные» обновления | Классический признак слепой зоны: CVE закрываются быстро, а версия устаревает. Посчитайте метрику актуальности отдельно |
| Много активов с «неизвестной версией» | Собственные сборки, патченные вручную версии или список релизов собран неполно. Разберите каждую вручную — это не ошибка скрипта, а реальный факт, который скрывался за молчанием сканера |
| Вендор выпускает релизы без явного перечня исправлений | Это не повод отказаться от метрики — наоборот, именно для таких вендоров она единственный доступный сигнал. Заводите релиз в список по номеру версии, не дожидаясь перечня CVE |
| Нет единого источника списка релизов вендора | Начните с ручного ведения списка по странице обновлений вендора; для крупных вендоров ищите CSAF-ленту или API releases, если они публикуют |
Ограничения
Метрика актуальности версий не заменяет приоритизацию по CVSS, KEV и EPSS — она закрывает конкретный пробел этого подхода, а не весь процесс целиком. Отставание на один релиз не равно одному конкретному риску: внутри релиза могут быть как критичные исправления, так и незначительные правки. Используйте обе метрики вместе, не выбирайте одну вместо другой.
Где это в модели зрелости
Материал относится к направлению «Управление уязвимостями» (материалы направления, карта зрелости). Уровень 2 требует процесса с приоритетами и сроками закрытия: метрика актуальности версий дополняет этот процесс там, где источники, завязанные на CVE (KEV, EPSS, сам сканер), структурно не видят часть реальных исправлений.
Как проверить
- Для критичных категорий активов есть отдельная метрика актуальности версий, не сведённая к проценту закрытых CVE.
- Проведена самопроверка на 30 минут хотя бы для одного крупного вендора, и размер разрыва известен в числах.
- Исключения для релизов без разбивки по CVE оформляются на уровне релиза, а не теряются из-за отсутствия идентификатора.
- У поставщика сканера явно спрошено, как он покрывает находки без CVE.
Источники
- Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
- Anton Chuvakin, «Rethinking Vulnerability Management: “If It’s Not in the Scanner, It Didn’t Happen” Needs to Go»: medium.com.
- CSAF (Common Security Advisory Framework), формат машиночитаемых бюллетеней: csaf.io.


















