Перейти к содержимому

Автоматизация сбора IOC по CVE на Python: скрипт вместо ручного мониторинга Shodan

Автоматизация сбора IOC по CVE на Python: скрипт вместо ручного мониторинга Shodan
Время чтения: 11 мин.

По данным IBM X-Force Threat Intelligence Index 2025, среднее время между публикацией CVE и устранением уязвимости в организации — 29 месяцев. CrowdStrike Global Threat Report 2025 фиксирует другую сторону: после первичного доступа атакующему хватает в среднем 62 минут для горизонтального перемещения по сети. Пока ты вручную перебираешь вкладки Shodan, копипастишь IP-адреса в блокнот и парсишь глазами страницы NVD — атакующий уже движется по чужой сети. Ниже — рабочий пайплайн на Python, который принимает CVE-идентификатор, забирает данные из NVD API, находит уязвимые хосты через Shodan и выдаёт JSON с индикаторами для дальнейшего анализа. Вместо 15–40 минут ручной работы — 3–5 секунд.

Что собираем и зачем: от CVE до индикаторов компрометации

CVE (Common Vulnerabilities and Exposures) — стандартный идентификатор уязвимости вида CVE-YYYY-NNNNN. Публикуется в NVD (National Vulnerability Database — национальная база уязвимостей США) и содержит описание проблемы, оценку критичности CVSS, категорию слабости CWE и список затронутых продуктов.

IOC (Indicator of Compromise — индикатор компрометации) — артефакт, указывающий на взлом или попытку взлома: IP-адрес управляющего сервера, хэш вредоносного файла, домен в фишинговой кампании. Но тут нюанс: в контексте мониторинга свежих CVE мы работаем не совсем с IOC в классическом смысле. Правильнее называть их индикаторами экспозиции — IP-адреса хостов, на которых Shodan обнаружил уязвимую версию ПО. Это ещё не компрометация, но это поверхность атаки, которую нужно контролировать.

Бизнес-логика со стороны атакующего простая: публикация CVE с высоким CVSS — стартовый сигнал для массового сканирования. В MITRE ATT&CK (открытая база тактик и техник атак; каждая техника имеет T-код — уникальный идентификатор, например T1190) это первая фаза — Exploit Public-Facing Application (T1190, тактика Initial Access). Атакующий ищет уязвимые хосты теми же инструментами, что и мы: Shodan, Censys, ZoomEye. Задача защитника — найти свои уязвимые активы быстрее.

Разберём на конкретном примере. CVE-2025-13526 — IDOR-уязвимость (Insecure Direct Object Reference — когда можно получить доступ к чужим данным, просто подменив идентификатор в запросе) в WordPress-плагине OneClick Chat to Order, все версии до 1.0.8 включительно. CVSS 7.5 (HIGH), вектор CVSS:3.1/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 — высокий ущерб конфиденциальности. По сути — неаутентифицированный атакующий подставляет order_id в URL и выгружает имена, email, телефоны, адреса, содержимое заказов. NVD классифицирует это как CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor). Массовый сбор таких данных в ATT&CK — техника Data from Information Repositories (T1213, тактика Collection).

Автоматизация сбора IOC по CVE на Python решает три задачи. Первая — защита периметра: проверить, есть ли в вашей инфраструктуре хосты с уязвимой версией. Вторая — threat intelligence: оценить масштаб экспозиции — сколько хостов уязвимы, в каких организациях и странах. Третья — приоритизация: не каждая CVE требует экстренной реакции, и скрипт собирает контекст для решения за секунды.

Парсинг уязвимостей через NVD API на Python

NVD предоставляет REST API 2.0 — JSON-интерфейс для получения полной информации о CVE. Зачем начинать с NVD? CVE-идентификатор сам по себе — просто номер. Для поиска уязвимых хостов нужен контекст: какой продукт затронут, какие версии, насколько критична уязвимость.

Что понадобится: Python 3.10+ (проверяется через python3 --version), библиотека requests (pip install requests), API-ключ NVD — бесплатный, запрашивается на nvd.nist.gov/developers/request-an-api-key. Без ключа лимит — 5 запросов за 30 секунд, с ключом — 50.

import requests

def get_cve_info(cve_id, api_key=None):
    url = f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve_id}"
    headers = {"apiKey": api_key} if api_key else {}
    r = requests.get(url, headers=headers, timeout=30)
    r.raise_for_status()
    vuln = r.json()["vulnerabilities"][0]["cve"]
    desc = vuln["descriptions"][0]["value"]
    score = vuln.get("metrics", {}).get("cvssMetricV31", [{}])[0]
    cvss = score.get("cvssData", {}).get("baseScore", "N/A")
    return {"id": cve_id, "description": desc, "cvss": cvss}

Что тут происходит, шаг за шагом:

  1. requests.get(url, ...) — GET-запрос к NVD API с параметром cveId.
  2. r.raise_for_status() — если API вернул ошибку (429 — превышение лимита, 404 — CVE не найдена), скрипт выбросит исключение. Без этой строки скрипт молча продолжит работу с мусорными данными, а ты потом будешь гадать, почему в JSON пусто.
  3. Парсинг JSON: описание из descriptions[0]["value"], CVSS-балл из метрик версии 3.1.

Ожидаемый результат для get_cve_info("CVE-2025-13526"):

{"id": "CVE-2025-13526", "description": "The OneClick Chat to Order plugin...", "cvss": 7.5}

Если вместо JSON прилетает ошибка 403 — проверь формат API-ключа в заголовке. Пустой ответ или KeyError — CVE ещё не попала в NVD (бывает задержка до 48 часов после резервирования идентификатора).

Автоматический поиск уязвимых хостов через Shodan API на Python

Shodan — OSINT-поисковик (инструмент разведки по открытым источникам), который индексирует интернет-устройства по баннерам сервисов: HTTP-заголовки, SSH-версии, SSL-сертификаты. Фильтр vuln:CVE-XXXX-XXXXX возвращает IP-адреса хостов, где Shodan обнаружил уязвимую версию.

Почему Shodan, а не сканирование Nmap? Shodan уже просканировал интернет за тебя. Запрос к API — поиск по готовому индексу, а не активное сканирование. Быстрее, легальнее и не требует собственной инфраструктуры.

Что понадобится: API-ключ Shodan из account.shodan.io. Фильтр vuln: требует платной подписки (Membership, от $69/мес.). На бесплатном аккаунте доступен поиск по продукту/версии (product:wordpress http.component:OneClick), но без прямой привязки к CVE.

def search_shodan(cve_id, api_key):
    url = "https://api.shodan.io/shodan/host/search"
    params = {"key": api_key, "query": f"vuln:{cve_id}", "minify": "true"}
    r = requests.get(url, params=params, timeout=30)
    r.raise_for_status()
    matches = r.json().get("matches", [])
    return [{"ip": m["ip_str"], "port": m.get("port"),
             "org": m.get("org", "N/A"),
             "country": m.get("location", {}).get("country_code")}
            for m in matches]

Разбираем:

  1. GET-запрос к /shodan/host/search с фильтром vuln:CVE-2025-13526.
  2. minify=true — сокращённый ответ без полных баннеров. Ускоряет обработку и экономит трафик.
  3. Из каждого совпадения извлекаем IP, порт, организацию и код страны — минимальный набор для первичной оценки экспозиции.

На выходе — список словарей вида {"ip": "203.0.113.42", "port": 443, "org": "Example Hosting", "country": "US"}. Пустой список — Shodan не нашёл хостов. Если CVE свежая, индекс может обновиться через часы или дни. Ошибка 401 — невалидный ключ. 402 — нужна платная подписка для фильтра vuln:.

Скрипт сбора индикаторов компрометации: делай раз, делай два, делай три

Объединяем NVD и Shodan в один пайплайн. Интеграция Shodan API с CVE базой — то, чего не даёт ручной процесс: получил CVE, за один запуск собрал и контекст, и список экспонированных хостов.

Делай раз: сохрани обе функции в файл cve_ioc_collector.py. В конце добавь точку входа: cve_id = sys.argv[1] (CVE-идентификатор из командной строки), ключи загружаются через os.environ["NVD_KEY"] и os.environ["SHODAN_KEY"]. Между вызовами get_cve_info() и search_shodan() поставь time.sleep(1) — пауза предотвращает одновременное срабатывание rate limit обоих API. Результат объедини в словарь {"cve": ..., "exposed_hosts": ..., "total": len(hosts)} и запиши в JSON через json.dump() с параметром ensure_ascii=False (чтобы кириллица в описаниях отображалась нормально, а не как \u0443\u044f\u0437\u0432).

Делай два: экспортируй ключи и запусти:

  • export NVD_KEY="ваш_ключ" и export SHODAN_KEY="ваш_ключ" в терминале.
  • python3 cve_ioc_collector.py CVE-2025-13526

Если всё настроено, в текущей директории появится iocs_CVE-2025-13526.json. Проверка: cat iocs_CVE-2025-13526.json | python3 -m json.tool — вывод должен быть валидным JSON с отступами. Блок cve содержит описание и CVSS. Блок exposed_hosts — массив с IP-адресами. Поле total — число найденных хостов.

Делай три: автоматизируй запуск через cron. Команда crontab -e, новая строка:

0 8 * * * /usr/bin/python3 /home/user/cve_ioc_collector.py CVE-2025-13526 >> /var/log/ioc.log 2>&1

Запуск каждый день в 8:00. Для нескольких CVE одновременно — расширь скрипт циклом по текстовому файлу watchlist.txt (один CVE-ID на строку) с time.sleep(2) между итерациями.

Итого: вместо 15–40 минут ручной работы на каждую CVE — автоматический сбор за 3–5 секунд. Пять CVE в неделю — экономия около двух часов. Двадцать CVE — рабочий день.

Мониторинг новых CVE автоматически: EPSS, CISA KEV и расширение пайплайна

Скрипт выше работает с конкретным CVE-идентификатором. На практике нужно отслеживать новые CVE по мере публикации. NVD API поддерживает параметры pubStartDate и pubEndDate — фильтрация по дате. Запрос с диапазоном за последние сутки возвращает все свежие CVE.

Расширенный Python-скрипт для threat intelligence работает так: раз в сутки запрашивает NVD API за последние 24 часа, фильтрует по CVSS >= 7.0 (отсекает низкоприоритетные), для каждой отобранной CVE запускает поиск по Shodan, результаты сохраняет в JSON и отправляет уведомление через webhook. Telegram Bot API или Slack Incoming Webhook — обе интеграции делаются одним requests.post().

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

EPSS (Exploit Prediction Scoring System) — модель, которая оценивает вероятность (от 0 до 1), что уязвимость начнут эксплуатировать в ближайшие 30 дней. Грубо говоря, CVSS — это «насколько больно ударит», а EPSS — «насколько вероятно, что ударят». API бесплатный: https://api.first.org/data/v1/epss?cve=CVE-XXXX-XXXXX. Для CVE-2025-13526 EPSS составляет 0.0039, percentile 0.33 — ниже медианы, вероятность эксплуатации низкая. Если EPSS > 0.1 — CVE заслуживает немедленного внимания. Ниже — мониторинг без паники.

CISA KEV (каталог Known Exploited Vulnerabilities) — список уязвимостей, которые уже эксплуатируются в реальных атаках. Это не прогноз, а факт: если CVE попала в KEV — красный флаг. JSON-фид доступен публично: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json. Загружай раз в сутки, проверяй каждую новую CVE на вхождение. Для CVE-2025-13526 CISA SSVC-решение — «Track» (мониторить), exploitation = none.

Логика приоритизации в скрипте: CVE с CVSS >= 9.0 или CVE из CISA KEV — алерт немедленно. CVSS 7.0–8.9 и EPSS > 0.1 — алерт в течение часа. Остальное — ежедневный отчёт. На бумаге формула простая, но приоритизация по-настоящему ощущается, когда сам прогоняешь реальные CVE и видишь, как из 50 свежих уязвимостей только 3 требуют реакции прямо сейчас. На HackerLab.pro есть задачи в категории OSINT, где подобная разведка по открытым источникам — основной навык.

Подводные камни: rate limits, кеширование и безопасность API-ключей

Любой скрипт сбора IOC на Python упирается в ограничения API и операционные нюансы. Вот что сломается первым.

Rate limits. NVD: 5 запросов/30 сек. без ключа, 50 с ключом. Shodan: зависит от тарифа, обычно 1 запрос/сек. При массовом сборе (десятки CVE) нужна очередь с паузами. Простейший вариант — time.sleep(). Для production — собственный счётчик запросов с проверкой через datetime.now().

Кеширование. Shodan обновляет индекс не в реальном времени. Кешируй ответы локально: SQLite-база с полями cve_id, response_json, timestamp. Перед запросом — проверка: если кеш младше 6 часов, используй его. Экономит квоту и ускоряет повторные проверки.

Безопасность ключей. API-ключи NVD и Shodan — не в коде, а в переменных окружения (os.environ) или .env-файле через библиотеку python-dotenv. Это не паранойя: согласно advisory GHSA-652x-xj99-gmcc, библиотека requests в старых версиях раскрывала чувствительные данные при HTTP-редиректах на другой хост. Держи requests обновлённым: pip install --upgrade requests.

Неполнота данных. Shodan видит только то, что торчит в интернет. Внутренние хосты, VPN, облачные инстансы за балансировщиком — не попадут в результат. Для полной картины нужна интеграция с внутренним сканером уязвимостей (Nessus, OpenVAS) и CMDB (база конфигурационных единиц — реестр всех IT-активов организации). Скрипт закрывает внешний периметр, не внутренний.

Российский контекст. Shodan работает из РФ, но при нестабильности доступа к зарубежным API стоит предусмотреть обработку таймаутов и fallback. Альтернатива для парсинга уязвимостей — БДУ ФСТЭК (bdu.fstec.ru), но у неё нет публичного REST API, только XML-выгрузка. Для скрипта это означает дополнительный парсер на xml.etree.ElementTree вместо JSON.

Автоматизация сбора IOC — входной билет, а не финишная линия. Я видел десятки Python-скриптов у начинающих TI-аналитиков: все собирают данные, почти ни один не помогает принять решение. 500 IP-адресов уязвимых хостов — не результат. Результат — когда аналитик за 30 секунд понимает: «из этих 500 — три принадлежат нашей организации, один уже в CISA KEV, остальные — чужая головная боль». Следующий шаг после сбора — обогащение: проверить IP через GreyNoise (он отделяет фоновый шум массовых сканеров от целевых атак), сопоставить с внутренней CMDB, отфильтровать по ASN. Без этого слоя автоматизация мониторинга CVE производит шум, а не intelligence. Большинство организаций покупают TI-платформы за миллионы рублей и используют их как RSS-ридер для CVE-алертов. Скрипт на 50 строк Python с правильной логикой приоритизации (CVSS + EPSS + KEV) закрывает 80% задач первичного сбора. Оставшиеся 20% — контекст, который добавляет аналитик, а не платформа. Если хочешь не просто статьи, а пройти базу threat intelligence системно — IB Basics на codeby.school даёт фундамент, на котором такие скрипты становятся рабочим инструментом, а не учебным упражнением.

Эту тему и смежные навыки разбирают на практике в курсе «Основы программирования на Python» Codeby Academy.