Как мы Zabbix с MaxPatrol подружили

от автора

Здравствуйте! Меня зовут Игорь, и я руковожу несколькими направлениями в команде DevOps-инженеров, включая направление мониторинга.

Сегодня я хочу рассказать вам о нашем уникальном решении для Zabbix. Это решение позволяет быстро уведомлять о найденных уязвимостях, формировать список этих уязвимостей и предоставлять дополнительную информацию о них.

Возьмите вкусняшек, чайку и присаживайтесь поудобнее.

Пролог

Впервые я столкнулся с подобной задачей лет семь-восемь назад, когда работал администратором бок о бок с командой безопасности (КБ).

Перед нами стояла задача найти инструмент для поиска уязвимостей на Linux-хостах и создать понятный отчёт. Мы выбрали инструмент Vulners. Однако было одно условие: чтобы сгенерировать отчёт, нужно было выполнить команду по сбору пакетов (rpm/dpkg) с определёнными аргументами и отправить весь список в API Vulners.

Команда безопасности (КБ) выразила некую обеспокоенность этим условием и попросила выполнить задачу локально, без привлечения третьих лиц.

После некоторых размышлений и изучения информации я обнаружил, что можно загрузить бюллетени с уязвимостями для каждой операционной системы через API в формате JSON (сейчас эта услуга платная). Затем мне осталось только написать скрипт, который будет собирать и анализировать эти данные, формировать отчёт и каким-то образом информировать администраторов и команду безопасности о выявленных уязвимостях.

Через неделю или две я создал этот инструмент. Сразу же мы начали получать подробные отчёты на почту, а в Zabbix появлялось предупреждение о наличии критических уязвимостей на хостах. Это позволяло нам быстро реагировать и устранять уязвимости в соответствии с установленными регламентами.

Концепция

Судьба привела меня к решению похожей задачи и на текущем месте работы. Задача была знакомая, и сначала я попытался восстановить свой старый скрипт. Тогда я узнал, что API стал платным.

Затем я начал искать в интернете и наткнулся на интересный инструмент vuls.io. Мы запустили пилотный проект (параллельно закупая MaxPatrol VM), передали веб-интерфейс КБ и начали тестирование.

В целом инструмент выполняет свою задачу, но когда количество хостов не сотни, а тысячи, веб-интерфейс с отчётами становится неинформативным, сильно тормозит и иногда даже падает. Кроме того, отчёт не содержит всей необходимой информации, и нам было сложно понять, как перенести её в Zabbix. Однако на этом этапе я определил, что нам нужно получить от инструмента:

  • интеграцию с Zabbix;

  • список обновляемых пакетов;

  • сортировку по уровню критичности.

Пришло время для MaxPatrol VM. Мы его установили, создали пользователей и ключи, распределили конфигурации и ключи по всей инфраструктуре и передали команде безопасности (КБ) для настройки сканирования инфраструктуры.

После внедрения системы к нам начали активно поступать задачи, связанные с уязвимостями, списками хостов и карточками активов в MaxPatrol. Однако не всё было гладко:

  • Чтобы собрать список обновляемых пакетов, необходимо перейти в карточку и углубиться в саму уязвимость, и так для каждой.

  • Есть сложный собственный язык запросов, без помощи не разобраться.

  • Если уязвимость затрагивает несколько пакетов, то они отображаются в поиске как отдельные хосты, а не объединяются (хотя можно всё посмотреть в карточке).

Также были замечания по поводу настройки сканирования — еë нет. Нам нужно было только собрать список пакетов по хостам и проверить их на наличие уязвимостей. Скорость сканирования некоторых хостов была очень низкой и достигала суток по непонятным причинам.

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

  • Необходима автоматизация процессов, включая отправку оповещений, создание отчётов и обновление пакетов.

  • Требуется сформировать команду для обновления необходимых пакетов в зависимости от используемой операционной системы.

  • Нужны подробные инструкции и руководства о том, как работать с инструментом и что делать.

Реализация

Необходимо создать скрипт, который будет отправлять запросы в MaxPatrol и собирать нужные данные для Zabbix в виде массива.

maxpatrol_vulns.py
#!/usr/bin/env python3 import requests import json import csv import sys import argparse  from urllib3.exceptions import InsecureRequestWarning from io import StringIO  oauth2Payload = {     "client_id": "mpx",     "grant_type": "password",     "response_type": "code id_token",     "scope": "offline_access mpx.api ptkb.api", } maxErrors = 50   class MaxPatrolClient():      def __init__(self, oauth2Payload):         parser = argparse.ArgumentParser()         parser.add_argument("-u", "--user", help="Username for login in MaxPatrol", required=True)         parser.add_argument("-p", "--password", help="Password for login in MaxPatrol", required=True)         parser.add_argument("-c", "--client-secret", help="Client secret for OAuth2", required=True)         parser.add_argument("-H", "--host", help="MaxPatrol host", required=True)         parser.add_argument("-s", "--severity", help="The importance of vulnerability", default="critical")         args = parser.parse_args()          self.hostUrl = "https://" + args.host         self.severity = args.severity          creds = {             "client_secret": args.client_secret,             "username": args.user,             "password": args.password         }          # Suppress only the single warning from urllib3 needed.         requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)         self.headers = {           'Accept': 'application/json',           'Authorization': f'Bearer {self.createToken(self.hostUrl, {**oauth2Payload, **creds} )}',           'Content-Type': 'application/json'         }         self.errorCount = 0 # Sum of all atemps to make request, prevent entering in crash loop      def createToken(self, hostUrl, oauth2Payload):         response = requests.post(hostUrl + ":3334/connect/token", data=oauth2Payload, verify=False)         return json.loads(response.text)['access_token']      def pushQuery(self, query):         # Push query to processing queue         url = f"{self.hostUrl}/api/assets_temporal_readmodel/v1/assets_grid"         response = requests.post(url, headers=self.headers, data=self.createQueryPayload(query), verify=False)          # Get result after query processed         data = json.loads(response.text)         url = f"{self.hostUrl}/api/assets_temporal_readmodel/v1/assets_grid/export?pdqlToken={data['token']}"         self.errorCount += 1 # Increase errorCount         response = requests.get(url, headers=self.headers, verify=False)          if response.status_code == 404:              if self.errorCount >= maxErrors:                 print(f"[ERROR] TO MANY ATTEMPS, last response: {response.text}")                 sys.exit(1)              return self.pushQuery(query)         self.errorCount = 0         return response.text      def getVulnedPackages(self):         # Push query to processing queue         if self.severity == "critical":             query = f"select(@UnixHost, UnixHost.UnameNodeName, UnixHost.Packages.@NodeVulners.CVSS3Score, UnixHost.Packages.@NodeVulners.istrend, UnixHost.Packages.@NodeVulners.metrics.Exploitable, \                 UnixHost.Packages.Name, UnixHost.Packages.Version, UnixHost.Packages.@NodeVulners.KB, UnixHost.@Id, UnixHost.Packages.@NodeVulners, \                 UnixHost.Packages.@NodeVulners.Status, UnixHost.OsName, UnixHost.@Importance) | filter(UnixHost.Packages.@NodeVulners) | \                 filter((UnixHost.Packages.@NodeVulners.Status not in ['Fixed', 'Excluded', 'awaitingFix']) AND (UnixHost.Packages.@NodeVulners.Status != null)) | \                 filter((UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND \                 UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR \                 (UnixHost.Packages.@NodeVulners.SeverityRating = 'High' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'H'))"         elif self.severity == "high":             query = f"select(@UnixHost, UnixHost.UnameNodeName, UnixHost.Packages.@NodeVulners.CVSS3Score, UnixHost.Packages.@NodeVulners.istrend, UnixHost.Packages.@NodeVulners.metrics.Exploitable, \                 UnixHost.Packages.Name, UnixHost.Packages.Version, UnixHost.Packages.@NodeVulners.KB, UnixHost.@Id, UnixHost.Packages.@NodeVulners, \                 UnixHost.Packages.@NodeVulners.Status, UnixHost.OsName, UnixHost.@Importance) | filter(UnixHost.Packages.@NodeVulners) | \                 filter((UnixHost.Packages.@NodeVulners.Status not in ['Fixed', 'Excluded', 'awaitingFix']) AND (UnixHost.Packages.@NodeVulners.Status != null)) | \                 filter((UnixHost.Packages.@NodeVulners.SeverityRating = 'High' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'High' AND \                 UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR \                 (UnixHost.Packages.@NodeVulners.SeverityRating = 'Medium' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'H'))"         elif self.severity == "medium":             query = f"select(@UnixHost, UnixHost.UnameNodeName, UnixHost.Packages.@NodeVulners.CVSS3Score, UnixHost.Packages.@NodeVulners.istrend, UnixHost.Packages.@NodeVulners.metrics.Exploitable, \                 UnixHost.Packages.Name, UnixHost.Packages.Version, UnixHost.Packages.@NodeVulners.KB, UnixHost.@Id, UnixHost.Packages.@NodeVulners, \                 UnixHost.Packages.@NodeVulners.Status, UnixHost.OsName, UnixHost.@Importance) | filter(UnixHost.Packages.@NodeVulners) | \                 filter((UnixHost.Packages.@NodeVulners.Status not in ['Fixed', 'Excluded', 'awaitingFix']) AND (UnixHost.Packages.@NodeVulners.Status != null)) | \                 sort(UnixHost.@Importance ASC) | filter((UnixHost.Packages.@NodeVulners.SeverityRating = 'High') OR \                 (UnixHost.Packages.@NodeVulners.SeverityRating = 'Medium' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = false  AND UnixHost.@Importance = 'ND') OR \                 (UnixHost.Packages.@NodeVulners.SeverityRating = 'Medium' AND UnixHost.Packages.@NodeVulners.istrend = true AND \                 UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'ND') OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'Low' AND \                 UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'H'))"         else:             print('Некорректное значение.')          csvResult = self.pushQuery(query)          # CSV result parsing and processing to JSON         jsonResult = {}         f = StringIO(csvResult)         reader = csv.reader(f, delimiter=';')          # Drop metadata line from CSV result         next(reader)          for row in reader:             hostName = row[0].split(" ")[0]             hostCVSS3Score = float(row[2].replace(',','.'))             if not jsonResult.__contains__(hostName):                 jsonResult[hostName] = {                     'Host': row[1],                     'HostCVSS3Score': hostCVSS3Score,                     'HostInfoUrl': f"{self.hostUrl}/#/assets/view/{row[8]}",                     'UpdateCommand': set(),                     'OS': row[11]                 }             if jsonResult[hostName]['HostCVSS3Score'] < hostCVSS3Score:                 jsonResult[hostName]['HostCVSS3Score'] = hostCVSS3Score              jsonResult[hostName]['UpdateCommand'].add(row[5])          # Some processing...         for hostName in jsonResult:             host = jsonResult[hostName]             rhel = ['CentOS', 'Oracle Linux']             deb = ['Debian', 'Ubuntu']                          if host['OS'] in deb:                 host['UpdateCommand'] = f"apt -y install --only-upgrade {' '.join(host['UpdateCommand'])}"             if host['OS'] in rhel:                 host['UpdateCommand'] = f"yum -y update {' '.join(host['UpdateCommand'])}"          return json.dumps(jsonResult)      def createQueryPayload(self, query):         return json.dumps({           "pdql": str(query),           "selectedGroupIds": [],           "additionalFilterParameters": {             "groupIds": [],             "assetIds": []           },           "includeNestedGroups": True,           "utcOffset": "+03:00"         })  if __name__ == "__main__":     client = MaxPatrolClient(oauth2Payload)     print('{"data":[' + client.getVulnedPackages() + ']}') 

Автор — @absoluterenatus Скрипт принимает на вход несколько аргументов:

  • "-u", "--user" — пользователь в MaxPatrol;

  • "-p", "--password" — пароль от пользователя в MaxPatrol;

  • "-c", "--client-secret» — ключ доступа в MaxPatrol;

  • "-H", "--host" — адрес MaxPatrol;

  • "-s", "--severity" — важность определения уязвимости диктует выбор необходимого запроса и формирование массива данных для LLD — низкоуровневого обнаружения.

Основа всего, это запросы, которые предоставляют нам все необходимые данные. Рассмотрим это на примере поиска критически уязвимых хостов.

Запрос
select(@UnixHost, UnixHost.UnameNodeName, UnixHost.Packages.@NodeVulners.CVSS3Score, UnixHost.Packages.@NodeVulners.istrend, UnixHost.Packages.@NodeVulners.metrics.Exploitable, UnixHost.Packages.Name, UnixHost.Packages.Version, UnixHost.Packages.@NodeVulners.KB, UnixHost.@Id, UnixHost.Packages.@NodeVulners, UnixHost.Packages.@NodeVulners.Status, UnixHost.OsName, UnixHost.@Importance) | filter(UnixHost.Packages.@NodeVulners) | filter((UnixHost.Packages.@NodeVulners.Status not in ['Fixed', 'Excluded', 'awaitingFix']) AND (UnixHost.Packages.@NodeVulners.Status != null)) | filter((UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'High' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'H'))

Где:

  • select(@UnixHost, UnixHost.UnameNodeName, UnixHost.Packages.@NodeVulners.CVSS3Score, UnixHost.Packages.@NodeVulners.istrend, UnixHost.Packages.@NodeVulners.metrics.Exploitable, UnixHost.Packages.Name, UnixHost.Packages.Version, UnixHost.Packages.@NodeVulners.KB, UnixHost.@Id, UnixHost.Packages.@NodeVulners, UnixHost.Packages.@NodeVulners.Status, UnixHost.OsName, UnixHost.@Importance) — выбирает нужные нам поля для формирования отчёта. Не все поля нужны, но так как этот запрос активно используется в работе МП, мы просто перенесли его в скрипт.

  • filter((UnixHost.Packages.@NodeVulners.Status not in ['Fixed', 'Excluded', 'awaitingFix']) AND (UnixHost.Packages.@NodeVulners.Status != null)) — фильтр исключает записи, которые были исправлены, удалены, ожидают исправления или имеют пустой статус.

  • filter((UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'Critical' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true) OR (UnixHost.Packages.@NodeVulners.SeverityRating = 'High' AND UnixHost.Packages.@NodeVulners.istrend = true AND UnixHost.Packages.@NodeVulners.metrics.Exploitable = true  AND UnixHost.@Importance = 'H')) — фильтр выбирает критические и высокие уязвимости, которые находятся в тренде и имеют доступные эксплойты.

Вывод запроса в MaxPatrol

Вывод запроса в MaxPatrol

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

  • 'Host' — имя сервера;

  • 'HostCVSS3Score' — рейтинг уязвимости;

  • 'HostInfoUrl' — ссылка на карточку сервера в MaxPatrol;

  • 'UpdateCommand' — команда для обновления уязвимых пакетов;

  • 'OS' — операционная система.

Пример массива данных для LLD:

LLD
{   "data": [     {       "host1": {         "Host": "host1",         "HostCVSS3Score": 7.2,         "HostInfoUrl": "https://localhost/#/assets/view/195780a2-bf00-0001-0000-0000000000f0",         "UpdateCommand": "apt -y install --only-upgrade linux-image-generic",         "OS": "Ubuntu"       },       "host2": {         "Host": "host2",         "HostCVSS3Score": 7.2,         "HostInfoUrl": "https://localhost/#/assets/view/1a292f70-5c80-0001-0000-000000000749",         "UpdateCommand": "yum -y update kernel-uek",         "OS": "Oracle Linux"       },       "host3": {         "Host": "host3",         "HostCVSS3Score": 7,         "HostInfoUrl": "https://localhost /#/assets/view/1a3b0f44-b900-0001-0000-0000000007e9",         "UpdateCommand": "yum -y update conmon",         "OS": "Oracle Linux"       },       "host4": {         "Host": "host4",         "HostCVSS3Score": 7,         "HostInfoUrl": "https://localhost/#/assets/view/1a55ccf0-0d80-0001-0000-000000000a30",         "UpdateCommand": "yum -y update nginx nginx-core nginx-filesystem",         "OS": "Oracle Linux"       },       "host5": {         "Host": "host5",         "HostCVSS3Score": 7,         "HostInfoUrl": "https://localhost/#/assets/view/1a55ccf0-8200-0001-0000-000000000a34",         "UpdateCommand": "yum -y update nginx-filesystem",         "OS": "Oracle Linux"       },       "host6": {         "Host": "host6",         "HostCVSS3Score": 7,         "HostInfoUrl": "https://localhost/#/assets/view/1a5f336e-0180-0001-0000-000000000b04",         "UpdateCommand": "yum -y update conmon",         "OS": "Oracle Linux"       }     }   ] }

Скрипт готов, теперь нужно создать шаблон для Zabbix.

Template Vulnerabilities by MaxPatrol
zabbix_export:   version: '6.0'   date: '2024-06-28T20:15:33Z'   groups:     - uuid: 8fb62aef15ca465398f1bc519daa3f5b       name: 'Templates Custom'   templates:     - uuid: 6aa142b202b24713bfb6f8d70c21c762       template: 'Template Vulnerabilities by MaxPatrol'       name: 'Template Vulnerabilities by MaxPatrol'       description: 'Мониторинг уязвимостей через MaxPatrol VM.'       groups:         - name: 'Templates Custom'       items:         - uuid: 0a554398196d4daebd8ffcf8b445bb06           name: 'Get critical vulnerabilities info'           type: EXTERNAL           key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","critical"]'           delay: 15m           history: '0'           trends: '0'           value_type: TEXT         - uuid: 20d5640a2782498092eab4daa40d941d           name: 'Get high vulnerabilities info'           type: EXTERNAL           key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","high"]'           delay: 15m           history: '0'           trends: '0'           value_type: TEXT       discovery_rules:         - uuid: bf6fc7594559409a8a550dc1c3164cbd           name: 'Get critical vulned hosts'           type: DEPENDENT           key: get.critical.vulned.hosts           delay: '0'           lifetime: 1d           item_prototypes:             - uuid: 3691c8e42ee340d6b0826cc8f8e9eeb7               name: '{#VULNED_HOST}'               type: DEPENDENT               key: 'critical.vulned.host.score[{#VULNED_HOST}]'               delay: '0'               value_type: FLOAT               description: |                 Карточка хоста: {#VULNED_INFO}                 Для закрытия уязвимости выполните команду: {#VULNED_CMD}                 Роль для закрытия уязвимостей: ссылка на роль автоматизации                 Регламент: ссылка на регламент                 После выполнения обновления запустите сканирование согласно инструкции: ссылка на инструкцию               preprocessing:                 - type: JSONPATH                   parameters:                     - '$..[?(@.Host==''{#VULNED_HOST}'')].HostCVSS3Score.first()'                   error_handler: CUSTOM_VALUE                   error_handler_params: '0'                 - type: DISCARD_UNCHANGED_HEARTBEAT                   parameters:                     - '21600'               master_item:                 key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","critical"]'               tags:                 - tag: CVSSScore                   value: '{#VULNED_SCORE}'                 - tag: Host                   value: '{#VULNED_HOST}'                 - tag: OS                   value: '{#VULNED_OS}'               trigger_prototypes:                 - uuid: eb474a294bc94785933092305e430768                   expression: 'last(/Template Vulnerabilities by MaxPatrol/critical.vulned.host.score[{#VULNED_HOST}])>0'                   name: 'Host {#VULNED_HOST} has critical vulnerabilities'                   url: '{#VULNED_INFO}'                   priority: DISASTER                   description: |                     Карточка хоста: {#VULNED_INFO}                     Для закрытия уязвимости выполните команду: {#VULNED_CMD}                     Роль для закрытия уязвимостей: ссылка на роль автоматизации                     Регламент: ссылка на регламент                     После выполнения обновления запустите сканирование согласно инструкции: ссылка на инструкцию                   manual_close: 'YES'                   tags:                     - tag: CVSSScore                       value: '{ITEM.LASTVALUE}'                     - tag: Host                       value: '{#VULNED_HOST}'                     - tag: OS                       value: '{#VULNED_OS}'                     - tag: templatename                       value: maxpatrol                     - tag: triggeritem                       value: '{#VULNED_HOST}'                     - tag: triggername                       value: vulnerabilities_critical           master_item:             key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","critical"]'           lld_macro_paths:             - lld_macro: '{#VULNED_CMD}'               path: $..UpdateCommand.first()             - lld_macro: '{#VULNED_HOST}'               path: $..Host.first()             - lld_macro: '{#VULNED_INFO}'               path: $..HostInfoUrl.first()             - lld_macro: '{#VULNED_OS}'               path: $..OS.first()             - lld_macro: '{#VULNED_SCORE}'               path: $..HostCVSS3Score.first()           preprocessing:             - type: JSONPATH               parameters:                 - '$.data.[0].*'               error_handler: DISCARD_VALUE         - uuid: 4256689274f0487da57c28a081b8d14f           name: 'Get high vulned hosts'           type: DEPENDENT           key: get.high.vulned.hosts           delay: '0'           lifetime: 1d           item_prototypes:             - uuid: 5f24eeb98b9c4fc986b62965c78eebe2               name: '{#VULNED_HOST}'               type: DEPENDENT               key: 'high.vulned.host.score[{#VULNED_HOST}]'               delay: '0'               value_type: FLOAT               description: |                 Карточка хоста: {#VULNED_INFO}                 Для закрытия уязвимости выполните команду: {#VULNED_CMD}                 Роль для закрытия уязвимостей: ссылка на роль автоматизации                 Регламент: ссылка на регламент                 После выполнения обновления запустите сканирование согласно инструкции: ссылка на инструкцию               preprocessing:                 - type: JSONPATH                   parameters:                     - '$..[?(@.Host==''{#VULNED_HOST}'')].HostCVSS3Score.first()'                   error_handler: CUSTOM_VALUE                   error_handler_params: '0'                 - type: DISCARD_UNCHANGED_HEARTBEAT                   parameters:                     - '21600'               master_item:                 key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","high"]'               tags:                 - tag: CVSSScore                   value: '{#VULNED_SCORE}'                 - tag: Host                   value: '{#VULNED_HOST}'                 - tag: OS                   value: '{#VULNED_OS}'               trigger_prototypes:                 - uuid: 34f580d6f13a47a6993fae59bbf43c58                   expression: 'last(/Template Vulnerabilities by MaxPatrol/high.vulned.host.score[{#VULNED_HOST}])>0'                   name: 'Host {#VULNED_HOST} has high vulnerabilities'                   url: '{#VULNED_INFO}'                   priority: HIGH                   description: |                     Карточка хоста: {#VULNED_INFO}                     Для закрытия уязвимости выполните команду: {#VULNED_CMD}                     Роль для закрытия уязвимостей: ссылка на роль автоматизации                     Регламент: ссылка на регламент                     После выполнения обновления запустите сканирование согласно инструкции: ссылка на инструкцию                   manual_close: 'YES'                   tags:                     - tag: CVSSScore                       value: '{ITEM.LASTVALUE}'                     - tag: Host                       value: '{#VULNED_HOST}'                     - tag: OS                       value: '{#VULNED_OS}'                     - tag: templatename                       value: maxpatrol                     - tag: triggeritem                       value: '{#VULNED_HOST}'                     - tag: triggername                       value: vulnerabilities_high           master_item:             key: 'maxpatrol_vulns.py["-u","{$MP.API.USER}","-p","{$MP.API.PASSWORD}","-c","{$MP.API.SECRET}","-H","{$MP.API.URL}","-s","high"]'           lld_macro_paths:             - lld_macro: '{#VULNED_CMD}'               path: $..UpdateCommand.first()             - lld_macro: '{#VULNED_HOST}'               path: $..Host.first()             - lld_macro: '{#VULNED_INFO}'               path: $..HostInfoUrl.first()             - lld_macro: '{#VULNED_OS}'               path: $..OS.first()             - lld_macro: '{#VULNED_SCORE}'               path: $..HostCVSS3Score.first()           preprocessing:             - type: JSONPATH               parameters:                 - '$.data.[0].*'               error_handler: DISCARD_VALUE 

Шаблон состоит из двух основных элементов, которые запускают скрипт с нужными параметрами (critical/high), и двух правил LLD. Эти правила используются для создания метрик с рейтингом, необходимой метаинформацией и триггерами.

Не буду подробно описывать каждый элемент данных и правила предобработки. Всё это уже описано в предыдущих статьях. Вместо этого, я опишу только базовую логику создания элементов и срабатывания триггеров:

  • Если в выполняемом запросе есть уязвимые серверы, то они будут включены в массив данных.

  • На основе массива данных создаются прототипы со специальной метрикой — рейтингом оценки уязвимости (CVSS).

  • Если уязвимость на сервере устранена, то он исчезает из списка, а показатель метрики устанавливается на 0.

  • Триггер активируется, когда условие больше 0.

  • Серверы, которые больше не имеют уязвимостей, исключаются из Zabbix в течение 24 часов после устранения проблемы.

Активные триггеры в Zabbix (при наличии уязвимостей)

Активные триггеры в Zabbix (при наличии уязвимостей)
Метрики в Zabbix (latest data)

Метрики в Zabbix (latest data)

Пример сообщения в чате:

? PROBLEM: Host host01 has high vulnerabilities Сервер: MaxPatrol Время срабатывания: 2029.05.19 03:21:11 (MSK) Владелец: @ital/@owner Команда: Кибербезопасность (https://corp.portal.ru/company/teams/666) Сервис: #maxpatrol (#audit) Важность триггера: High  ℹ️ Описание: ??? Карточка хоста: https://localhost/#/assets/view/1a7bdc2d-ea00-0001-0000-000000000e1c Для закрытия уязвимости выполните команду: yum -y update kernel-uek Роль для закрытия уязвимостей: ссылка на роль Регламент: ссылка на регламент После выполнения обновления запустите сканирование согласно инструкции: ссылка на инструкцию

Эпилог

Это решение значительно облегчило работу администраторам и команде безопасности, и улучшило наше взаимодействие между командами.

Для команды безопасности:

  • Теперь не нужно вручную составлять списки серверов и создавать задачи.

  • В чате появились уведомления, чтобы мы могли быстрее реагировать на сообщения.

Для администраторов:

  • Быстрая реакция и устранение уязвимостей.

  • Сразу же формируется команда и составляется список пакетов для обновления.

  • Появилась новая роль Ansible для устранения уязвимостей.

  • Появилась вся необходимая информация о сервере, включая инструкции, регламенты и другие полезные материалы.

Планы на будущее:

  • Оптимизировать код и запросы, удалить всё ненужное.

  • Интегрировать систему с Jira для автоматического создания задач по устранению уязвимостей.

  • Добавить возможность обновлять пакеты прямо из Zabbix (стоит ли это делать — вопрос спорный, но сама функция интересная).

А как вы реализовывали похожие задачи? Поделитесь своим опытом в комментариях.

На этом у меня всë, всем спасибо за уделенное время и удачи!


ссылка на оригинал статьи https://habr.com/ru/articles/805453/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *