Три стороны систем ИБ и неочевидные риски
При внедрении и сопровождении систем информационной безопасности, часто участвуют три стороны: вендор (разработчик), интегратор (технический подрядчик) и заказчик (организация-клиент). Вендор отвечает за разработку, обновления и вторую линию поддержки. Интегратор выполняет внедрение, настройку, доработки и оказывает первую линию поддержки. Заказчик работает с системой на практике, формирует требования и может передавать логи диагностики через интегратора в вендорскую поддержку. При этом ответственность за обезличивание или фильтрацию чувствительных данных в логах часто лежит на стороне заказчика. На приктике заказчик не всегда проверяет, а какие на самом деле данные в логах выгружаются.
Что может скрываться внутри диагностических архивов
Недавно, работая с системой MaxPatrol VM и формируя диагностический архив для обращения в техническую поддержку, я заметил неприятную особенность: информация, которая выгружается для анализа, содержит пароли в открытом виде. Пароли от учетных записей компонентов, которые являются частью системы. Эти компоненты изолированы в Docker-контейнерах, но доступ к ним может осуществляться из сети через открытые порты. Среди компонентов — PgAdmin для доступа к СУБД с высокой привелегией, Grafana, RabbitMQ.
Сами логи представляют собой набор текстовых файлов с конфигурациями и событиями от различных компонентов системы. Кроме того, при сборе диагностической информации происходит выгрузка переменных окружения из операционной системы Linux, где в открытом виде указаны пароли, используемые для взаимодействия между компонентами. Среди прочего, в данных могут попадаться пароли сервисных учетных записей, например, для подключения к SMTP или Proxy-серверу, которые относятся к инфраструктуре.

Для того что бы проверить наличие паролей в логах диагноситческого отчета можно зайти в каталог с логами и выполнить команду:
grep -irnE 'var' .
где var
- одно из следующих значений переменной:
Password=
Password:
Какие могут быть риски при передаче паролей
Незначительная утечка такой информации может стать началом серьезных проблем. Например, администратор может использовать один и тот же пароль для разных систем, что приведёт к компрометации других систем. Наличие паролей в открытом виде существенно упрощает сценарии повышения привилегий через MaxPatrol VM.
Сама по себе система MaxPatrol VM — это актив повышенного риска. С серверов системы осуществляются подключения в различные сегменты сети, а для выполнения сканирований используются учётные записи с повышенными правами. Компрометация такой платформы потенциально означает доступ ко всей инфраструктуре.
Про здравый подход к безопасности
Иногда достаточно поработать с разными системами вживую, чтобы понять простую вещь: без доступа к логам действительно сложно выявить суть проблемы. Эксперты в области ИБ, работавшие с иностранными решениями, как правило, знают это на собственном опыте и именно поэтому рассматривают логи как ключевой источник информации для диагностики.
Но с опытомприходит и другое понимание: в логах не должно быть того, что само по себе может стать уязвимостью. Открытые пароли, переменные окружения с ключами, технические учётки — всё это, оказавшись в архиве, а затем в чужих руках, превращается в реальный риск. И это уже не вопрос «хорошо или плохо». Это вопрос зрелого подхода к безопасности. Того самого, который формируется не в теории, а в практике там, где последствия всегда реальны.
В целом хранение и передача паролей в открытом виде противоречит базовым принципам безопасности и международным security best practices, на которых, в том числе, основаны и Российские нормативные требования в сфере ИБ. Ниже приведу некоторые ключевые источники, прямо указывающие на недопустимость хранения паролей в логах или конфигурационных файлах в открытом виде:
CWE-256: Plaintext Storage of a Password Зарегистрированная в списке MITRE Common Weakness Enumeration. Прямо указывает, что хранение паролей в незашифрованном виде - это нарушение архитектурной безопасности приложения.
OWASP: Password Plaintext Storage OWASP, где подчёркивается, что даже временное хранение пароля в логах может привести к его утечке.
OWASP Password Storage Cheat Sheet Практическое руководство по корректному обращению с паролями, включая рекомендации по хэшированию, защите в памяти и минимизации риска экспонирования.
Что делать в таком случае
В итоге я решил сам написать скрипт на Python, который рекурсивно обходит, выгруженный каталог с логами, находит строки с паролями и автоматически затирает их. На мой взгляд, это самое быстрое решение для очистки логов.
Исходный код скрипта выложил на GitHub

#!/usr/bin/python3
import os
import re
import argparse
#python3 sanitmpvmlogs.py --logs ./troubleshoot_dir
#Arguments
parser = argparse.ArgumentParser(description='Удаление паролей в логах диагностики Maxpatrol VM. Распакуйте архив'
' с логами, полученными от утилиты сбора логов в каталог'
' и задайте каталог скрипту.\n'
'Delete passwords in Maxpatrol VM diagnostic logs.')
parser.add_argument('-l', '--logs', required=True, help='Enter directory with logs')
args = parser.parse_args()
catalog = args.logs
pass_patterns = [r'(PgPassword=)\S*',
r'(PostgrePassword=)\S*'
r'(ConsulSecret=)\S*',
r'(EventStorageAuthPassword=)\S*',
r'(MetricsPassword=)\S*',
r'(RMQAgentPassword=)\S*',
r'(RMQPassword=)\S*',
r'(RMQSiemPassword=)\S*',
r'(RMQAdminPassword=)\S*',
r'(RMQSiemPassword=)\S*',
r'(SmtpPassword=)\S*',
r'(ClickHouseUserPassword=)\S*',
r'(GrafanaAdminPassword=)\S*',
r'(TelemetryInstanceAccessToken=)\S*',
r'(ProxyPassword=)\S*',
r'(--httpAuth.password=)\S*',
r'(METRICS_PASSWORD=)\S*',
r'(GF_SECURITY_ADMIN_PASSWORD=)\S*',
r'(LogManager_ClickhousePassword=)\S*',
r'(VictoriaMetrics_Password=)\S*',
r'(VictoriaMetricsSettings_BasicAuthPassword=)\S*',
r'(Database_VictoriaMetricsPassword=)\S*',
r'(FRONTEND__LOGSPACE__SECURITY__PASSWORD=)\S*',
r'(FRONTEND__CLICKHOUSE__SECURITY__PASSWORD=)\S*',
r'(FRONTEND__ELASTICSEARCH__SECURITY__PASSWORD=)\S*',
r'(RABBITMQ_ADMIN_PASS=)\S*',
r'(RABBITMQ_SIEM_PASS=)\S*',
r'(COLLECTOR_METRICS_PASSWORD=)\S*',
r'(RABBITMQ_AGENT_PASS=)\S*',
r'(PGADMIN_DEFAULT_PASSWORD=)\S*',
r'(COLLECTOR_POSTGRESQL_PASSWORD=)\S*',
r'(POSTGRES_PASSWORD=)\S*',
r'(CONTENT_POSTGRES_PASSWORD=)\S*',
r'(CONSUL_SECRET=)\S*',
r'(HMRabbitSettings_Password=)\S*',
r'(CONTENT_HM_RMQ_AUTH_PLAIN_PASSWORD=)\S*',
r'(RabbitMq_Password=)\S*',
r'(CSPF_HM_RMQ_AUTH_PLAIN_PASSWORD=)\S*',
r'(RABBITMQ_CORE_PASS=)\S*'
r'(FlusProxy_ProxyPassword=)\S*',
r'(Password=)\S*',
r'(Password:) \S*',
r'(Password\":) \S*'
]
replace = ''
stats = {}
#Count statistics
def count_stat(p):
if p in stats:
stats[p] += 1
else:
stats[p] = 1
def clean_log(f):
with open(f, 'r', encoding='utf-8', errors='ignore') as fr:
lines = fr.readlines()
with open(f, 'w', encoding='utf-8', newline='\n') as fw:
for line in lines:
for pattern in pass_patterns:
#Search
if re.search(pattern, line):
replace = re.search(pattern, line)
line = re.sub(pattern, replace.group(1), line, flags=re.IGNORECASE)
count_stat(replace.group(1).rstrip('='))
print(line)
#print(replace)
fw.write(line)
for current_dir, subdirs, files in os.walk(catalog):
#Current Iteration Directory
#print('Current Directory: ', current_dir)
#Directories
'''for dirname in subdirs:
print('\tSub Directory: ' + dirname)'''
#Files
for filename in files:
fullpath = os.path.join(current_dir, filename)
print('Parsing: ', fullpath)
clean_log(fullpath)
#Statistics
for i in stats:
print('Strings found: ', i, stats[i])