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

0
9
Обложка статьи: Патчите релиз, а не только 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 минут

Прежде чем встраивать метрику в постоянный процесс, оцените размер слепой зоны на одном примере:

  1. Возьмите своего крупнейшего по числу активов или по доступности из интернета вендора.
  2. Откройте примечания к его последним трём релизам безопасности и посчитайте: сколько отдельных исправлений упомянуто и сколько из них получили собственный CVE.
  3. Сравните со списком находок вашего сканера за тот же период по этому вендору.

Разница между тем, что вендор исправил, и тем, что увидел сканер, — это размер вашей слепой зоны в конкретных числах, а не в абстрактном беспокойстве.

Порядок внедрения

  1. Для критичных и доступных извне категорий активов (периметр, VPN, сетевое оборудование) начните отслеживать версию отдельно от списка закрытых CVE.
  2. Спросите у поставщика сканера, как он обрабатывает бюллетень вендора без CVE: заводит ли находку под собственным идентификатором, с какой серьёзностью, и есть ли задержка между публикацией бюллетеня и появлением проверки.
  3. Оформляйте исключения на релиз, а не на отдельный CVE, если вендор не разбивает исправления по номерам — иначе формально «закрытых CVE нет», хотя обновление реально нужно.
  4. Добавьте метрику актуальности в отчёт рядом с привычным процентом закрытых CVE в срок, не вместо него.
  5. Если вендор публикует машиночитаемые бюллетени (формат CSAF) с версией исправления, заводите список релизов автоматически из этого источника, а не вручную по примечаниям к выпуску.

Диагностика

Симптом Причина и что делать
Метрика закрытия CVE стабильно близка к 100%, но вендор продолжает выпускать «срочные» обновления Классический признак слепой зоны: CVE закрываются быстро, а версия устаревает. Посчитайте метрику актуальности отдельно
Много активов с «неизвестной версией» Собственные сборки, патченные вручную версии или список релизов собран неполно. Разберите каждую вручную — это не ошибка скрипта, а реальный факт, который скрывался за молчанием сканера
Вендор выпускает релизы без явного перечня исправлений Это не повод отказаться от метрики — наоборот, именно для таких вендоров она единственный доступный сигнал. Заводите релиз в список по номеру версии, не дожидаясь перечня CVE
Нет единого источника списка релизов вендора Начните с ручного ведения списка по странице обновлений вендора; для крупных вендоров ищите CSAF-ленту или API releases, если они публикуют

Ограничения

Метрика актуальности версий не заменяет приоритизацию по CVSS, KEV и EPSS — она закрывает конкретный пробел этого подхода, а не весь процесс целиком. Отставание на один релиз не равно одному конкретному риску: внутри релиза могут быть как критичные исправления, так и незначительные правки. Используйте обе метрики вместе, не выбирайте одну вместо другой.

Где это в модели зрелости

Материал относится к направлению «Управление уязвимостями» (материалы направления, карта зрелости). Уровень 2 требует процесса с приоритетами и сроками закрытия: метрика актуальности версий дополняет этот процесс там, где источники, завязанные на CVE (KEV, EPSS, сам сканер), структурно не видят часть реальных исправлений.

Как проверить

  1. Для критичных категорий активов есть отдельная метрика актуальности версий, не сведённая к проценту закрытых CVE.
  2. Проведена самопроверка на 30 минут хотя бы для одного крупного вендора, и размер разрыва известен в числах.
  3. Исключения для релизов без разбивки по CVE оформляются на уровне релиза, а не теряются из-за отсутствия идентификатора.
  4. У поставщика сканера явно спрошено, как он покрывает находки без 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.

Комментарии закрыты.