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