Здравствуйте! Меня зовут Игорь, и я руковожу несколькими направлениями в команде 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'))
— фильтр выбирает критические и высокие уязвимости, которые находятся в тренде и имеют доступные эксплойты.
Поля, необходимые для создания метрик и получения нужной информации:
-
'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 часов после устранения проблемы.
Пример сообщения в чате:
? 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/
Добавить комментарий