Как стать автором
Обновить

Сюрприз в логах MaxPatrol VM — удаляем пароли перед отправкой в поддержку

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров2.9K

Три стороны систем ИБ и неочевидные риски

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

Что может скрываться внутри диагностических архивов

Недавно, работая с системой MaxPatrol VM и формируя диагностический архив для обращения в техническую поддержку, я заметил неприятную особенность: информация, которая выгружается для анализа, содержит пароли в открытом виде. Пароли от учетных записей компонентов, которые являются частью системы. Эти компоненты изолированы в Docker-контейнерах, но доступ к ним может осуществляться из сети через открытые порты. Среди компонентов — PgAdmin для доступа к СУБД с высокой привелегией, Grafana, RabbitMQ.

Сами логи представляют собой набор текстовых файлов с конфигурациями и событиями от различных компонентов системы. Кроме того, при сборе диагностической информации происходит выгрузка переменных окружения из операционной системы Linux, где в открытом виде указаны пароли, используемые для взаимодействия между компонентами. Среди прочего, в данных могут попадаться пароли сервисных учетных записей, например, для подключения к SMTP или Proxy-серверу, которые относятся к инфраструктуре.

Попарсил изрядно 16 гигов логов =)
Попарсил изрядно 16 гигов логов =)

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

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])
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Считаете ли вы, что ответственность за содержимое логов диагностики должна лежать на заказчике — конечном пользователе системы?
35% Да7
65% Нет13
Проголосовали 20 пользователей. Воздержались 3 пользователя.
Теги:
Хабы:
-1
Комментарии9

Публикации

Истории

Работа

Ближайшие события

19 марта – 28 апреля
Экспедиция «Рэйдикс»
Нижний НовгородЕкатеринбургНовосибирскВладивостокИжевскКазаньТюменьУфаИркутскЧелябинскСамараХабаровскКрасноярскОмск
22 апреля
VK Видео Meetup 2025
МоскваОнлайн
23 апреля
Meetup DevOps 43Tech
Санкт-ПетербургОнлайн
24 апреля
VK Go Meetup 2025
Санкт-ПетербургОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
14 мая
LinkMeetup
Москва
5 июня
Конференция TechRec AI&HR 2025
МоскваОнлайн
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область