Перейти к содержимому

Как пароль попадает в журналы: неочевидные каналы утечки секретов

Как чужой пароль оказался в журнале приложения: неочевидные каналы утечки секретов

Я искал причину падения сервиса и просматривал журнал по тексту ошибки. В соседней записи, среди сообщений о неудачных попытках входа, в поле имени пользователя обнаружилась странная строка. Это было явно не имя: длинная последовательность символов со знаками пунктуации, похожая на пароль.

После этого я выгрузил из журнала все значения поля `username`, связанные с неуспешной авторизацией за последний месяц. Из выборки исключил реальные имена пользователей и оставил строки, которые были слишком длинными, содержали спецсимволы и не повторялись. Таких значений оказалось около сорока.

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

Пароль случайно попал в поле логина

Самый простой и одновременно самый распространённый сценарий связан с формой входа. В ней обычно есть два поля, расположенных одно за другим: имя пользователя и пароль. Ошибка менеджера паролей, некорректное автозаполнение или случайное нажатие `Tab` могут привести к тому, что секрет окажется в первом поле.

Сервер получает обычный запрос: имя пользователя - длинная строка со спецсимволами, пароль - другое значение. Пользователь с таким именем не найден, и приложение записывает в журнал сообщение вроде: "Неудачная попытка входа для пользователя ...". С точки зрения программы в этой записи нет пароля. Там якобы находится логин.

Именно поэтому правило "не записывать поле password" недостаточно. Защищать нужно не только поля, которые разработчик считает секретными, но и весь контекст, в котором потенциально может оказаться конфиденциальное значение.

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

GET-запросы и параметры в адресной строке

Другой распространённый путь утечки - передача секретов через URL. Формы авторизации всё реже используют метод `GET`, но параметры в адресной строке по-прежнему встречаются в ссылках для сброса пароля, подтверждения операций, временных токенах и внутренних служебных обработчиках.

Всё, что указано после знака вопроса в URL, обычно автоматически попадает в журнал веб-сервера. Если запрос выглядит так:

```text
/reset?token=секретное_значение
```

то этот токен окажется в access log. То же произойдёт с паролем или другим чувствительным параметром, если разработчик по ошибке передаст его через строку запроса.

Важно учитывать, что запись создаётся не в одном месте. Один и тот же URL могут сохранить балансировщик, CDN, обратный прокси, nginx, Apache, сервер приложения и система централизованного сбора журналов. В результате единственная ошибка способна породить несколько копий секрета в разных хранилищах.

Для конфиденциальных данных следует использовать тело `POST`-запроса, а токены - по возможности не помещать в URL вообще. Даже если журнал веб-сервера настроен безопасно, адрес может сохраниться в истории браузера, заголовке `Referer`, системах аналитики и инструментах мониторинга.

Исключения и трассировки

Третий канал - необработанные исключения. Некоторые библиотеки, фреймворки и средства отладки включают в трассировку значения локальных переменных, аргументы функций и содержимое объектов. Если пароль находится в переменной обработчика в момент ошибки, он может попасть в stack trace.

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

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

HTTP-клиенты и мониторинг производительности

HTTP-клиенты и прокси умеют записывать запросы целиком - включая заголовки, тело формы и значения `Authorization`. Такая функция полезна при диагностике, но крайне опасна в рабочей среде. Достаточно включить подробный уровень журналирования, и пароли начнут уходить в общий поток логов.

Похожая проблема возникает в системах мониторинга производительности. Инструменты трассировки могут сохранять параметры вызовов, чтобы показать, почему конкретный запрос выполнялся долго. Если методом `authenticate()` передаётся пароль, он может оказаться в телеметрии.

Особенно неприятно, когда данные отправляются во внешний сервис. В таком случае секрет покидает внутренний контур, а на него могут не распространяться привычные правила хранения и удаления журналов.

Почему ревью авторизации не всегда помогает

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

Нужен отдельный аудит всех каналов наблюдаемости:

- форматов access log;
- фильтров application log;
- обработчиков исключений;
- параметров трассировки;
- настроек HTTP-клиентов;
- конфигурации APM и систем сбора ошибок;
- правил хранения и доступа к журналам.

Как защитить приложение

Наиболее надёжный подход - маскировать значения на уровне центрального логгера. Фильтр должен применяться независимо от того, какой компонент сформировал запись. В нём стоит скрывать пароли, токены, cookies, ключи API, значения заголовка `Authorization`, секреты из URL и подозрительные значения полей логина.

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

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

Проверка на тестовых маркерах

Хорошая практика - регулярно отправлять в тестовую среду специально созданный маркер, например уникальную строку вида `SECRET_TEST_...`, и проверять все места, где может появиться запись: журналы приложения, прокси, систему ошибок, APM и хранилище аналитики.

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

Что делать, если пароль уже попал в журнал

Секрет следует считать скомпрометированным. Его нужно немедленно заменить, определить все системы, где он мог использоваться, и проверить журналы доступа. Нельзя ограничиваться удалением одной записи: копии могли попасть в резервные хранилища, реплики, архивы, системы агрегации и локальные файлы.

В мае 2018 года Twitter сообщил, что некоторые пароли записывались во внутренний журнал до хеширования, и рекомендовал пользователям сменить их. В марте 2019 года Facebook рассказал о хранении паролей сотен миллионов пользователей во внутренней инфраструктуре в открытом виде. Механизм был другим, но общий вывод одинаков: внутреннее хранилище не является безопасным местом для секретов.

Фраза "признаков злоупотребления не обнаружено" не доказывает, что копии никто не получил. Если к данным имел доступ хотя бы один человек или сервис, отсутствие следов невозможно подтвердить полностью. Поэтому наиболее честная реакция - считать секрет раскрытым, отозвать его, заменить ключи и сократить срок хранения журналов.

Безопасность логов - это не отдельная формальность, а часть управления секретами. Пароль может попасть туда из-за ошибки пользователя, неверного метода HTTP, исключения или диагностического инструмента. Защищать нужно не только авторизацию, но и весь путь запроса через инфраструктуру.

Прокрутить вверх