Руководство

Credential stuffing: почему вход выглядит нормально и всё равно течёт

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

Что это за атака и почему это не брутфорс

Credential stuffing не подбирает пароли. Он их воспроизводит. Берутся пары «логин + пароль» из утечки постороннего сервиса, и каждая пара пробуется один раз на вашем входе — в расчёте на то, что заметная доля людей переиспользует учётные данные.

Одно это отличие ломает почти всё, что у вас развёрнуто. Брутфорс шумный: много попыток по одной записи, легко ограничить по частоте, легко заблокировать. Stuffing — это одна попытка на запись по тысячам записей, растянутая на часы и с ротацией адресов.

Доля успеха низкая — обычно называют от десятой доли процента до нескольких процентов, — и её совершенно достаточно. Против списка в миллион пар десятая доля процента — это тысяча работающих аккаунтов.

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

Как это выглядит в логах

Причина, по которой это неделями остаётся незамеченным, в том, что ни один отдельный сигнал не выглядит тревожно. Паттерн существует только в агрегате.

  • Доля отказов, а не их число. Абсолютное число неудачных входов может почти не измениться, а вот отношение отказов к успехам сдвигается — потому что попытки размазаны по записям, которых в основном не существует или к которым пароль не подошёл.
  • Одна попытка на запись. Рейт-лимит по учётной записи не срабатывает никогда. Лимит по адресу срабатывает, только если атакующий забыл про ротацию.
  • Распределение адресов. Попытки идут из пулов резидентных прокси, то есть с адресов, которые выглядят как обычные клиенты, а не как дата-центр.
  • Слишком ровный тайминг. Человеческий трафик входа следует суточному ритму ваших клиентов. Прогон stuffing ровный всю ночь.
  • Тихий всплеск успехов с незнакомых адресов. Вот это и есть главное, и вот на это никто не настраивает оповещения, потому что успешный вход — не ошибка.

Почему обычные меры её не видят

Каждая из них стоит того, чтобы быть, и ни одна не останавливает эту атаку сама по себе.

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

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

Капча — это издержка на попытку, которую атакующий уже заложил. Сервисы разгадывания тарифицируют их за тысячу, а при доле успеха 0,1% экономика по-прежнему сходится.

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

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

Что действительно её ловит

Поскольку в отдельном запросе атака невидима, детект должен работать на уровне сессии и адреса.

Сигналы уровня сессии. Прогон stuffing автоматизирует форму: поля заполняются без тайминга набора, фокус перемещается не так, как это делает человеческая рука, отправка приходит быстрее, чем человек мог бы правдоподобно всё заполнить. Это тот же поведенческий уровень, который читает любой невидимый детект, и он виден с первой же попытки.

Транспортные отпечатки. Инструменту нужно установить TLS-соединение, и его стек виден. Отпечаток JA3/JA3N, не совпадающий с браузером, которым сессия себя называет, — сильный сигнал, и починить его правкой заголовка нельзя.

Репутация поверх попыток. Одна попытка на запись не даёт ничего в разрезе записи, но один и тот же адрес, делающий по одной попытке к четырёмстам разным записям, однозначен. Этот агрегат существует, только если что-то оценивает адрес, а не запрос.

Межклиентская репутация. Списки для stuffing прогоняют по многим сайтам в рамках одной кампании. Адрес, который вчера прогонял список по чужому входу, стоит остановить до того, как он возьмётся за ваш, — а это возможно, только если знание общее.

Рабочая схема

По убыванию влияния каждого шага на результат.

Внедрите MFA и добивайтесь подключения. Ничто другое в этом списке не ломает атаку целиком. Сделайте её умолчанием для новых аккаунтов и предлагайте существующим после успешного входа с нового адреса.

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

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

Блокируйте на шлюзе. Решение, применённое в JavaScript страницы, — это решение, которое инструмент атакующего никогда не выполнит.

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

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

Где здесь Karma

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

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

Это не замена MFA, и страница не утверждает обратного. MFA — та мера, которая обесценивает воспроизведённый пароль; всё изложенное здесь про то, как остановить трафик, который проверяет, какие пароли ещё работают.

FAQ

Что такое credential stuffing?
Воспроизведение пар «логин + пароль», украденных при утечке другого сервиса, на вашем входе — в расчёте на переиспользование паролей. Это не подбор: каждая пара пробуется один раз по тысячам записей, поэтому лимиты по учётной записи и политики блокировки не срабатывают, а успешные входы технически корректны.
Почему рейт-лимит не останавливает credential stuffing?
Потому что он привязан не к тому. Лимиты по учётной записи не срабатывают при одной попытке на запись, а лимиты по адресу обходятся ротацией через пул резидентных прокси — ширпотребную услугу. Паттерн становится видимым, только когда что-то оценивает адрес поверх попыток, а не считает запросы.
Как обнаружить credential stuffing в логах?
Смотрите на отношение, а не на число: рост отказов относительно успехов, одна попытка на запись по множеству записей, ровный тайминг всю ночь и адреса из резидентных пулов. Самое важное оповещение — об успешных входах с адресов без истории: отказы это шум, успехи это утечка.
Останавливает ли MFA credential stuffing?
Да, на подключённых аккаунтах: воспроизведённый пароль без второго фактора ничего не стоит, что делает MFA самой действенной мерой. Она ничего не даёт на записях, где не включена, а на потребительском продукте это обычно большинство, так что её место рядом с детектом на уровне сессии, а не вместо него.