Credential stuffing и захват аккаунтов: как обнаружить и защититься
Продвинутое4 минРедакция Innovialabs
Коротко
Credential stuffing — это массовая подстановка пар логин-пароль, утёкших с других сайтов; защищаются слоями: блокируют скомпрометированные пароли через k-anonymity, оценивают каждый вход по устройству и поведению, просят второй фактор только при высоком риске, фильтруют ботов и отслеживают аномалии входов.
Как работает credential stuffing
Люди повторяют пароли. Когда у одного сайта утекает база пользователей, злоумышленники берут пары email-пароль и автоматически подставляют их в другие сервисы: банки, магазины, приложения доставки, SaaS-продукты. Каждая сработавшая пара — это захват аккаунта (account takeover, ATO): украденные бонусы, сохранённые карты, персональные данные или плацдарм для мошенничества.
Почему это сложно остановить:
- запросы идут через резидентные прокси и тысячи IP, поэтому лимиты на IP почти не срабатывают;
- на каждый аккаунт приходится одна-две попытки, поэтому блокировки по аккаунту тоже не срабатывают;
- боты имитируют настоящие браузеры, решают простые проверки и меняют user agent;
- учётные данные у атакующего правильные — успешный вход выглядит полностью легитимно.
Rate limiting по-прежнему нужен, но это первый слой, а не вся защита.
Слой 1. Отклоняйте скомпрометированные пароли
Если пароль уже есть в публичных базах утечек, его обязательно попробуют. Проверяйте пароли при регистрации, смене пароля и входе — так рекомендует NIST SP 800-63B.
Сервис Pwned Passwords использует k-anonymity: вы хешируете пароль SHA-1, отправляете только первые 5 hex-символов, получаете все суффиксы с этим префиксом и сравниваете у себя. Ни пароль, ни полный хеш не покидают ваш сервер.
import hashlib
import requests
def is_breached(password: str) -> bool:
digest = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
prefix, suffix = digest[:5], digest[5:]
resp = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}", timeout=5)
resp.raise_for_status()
return any(line.split(":")[0] == suffix for line in resp.text.splitlines())
Если совпадение найдено при входе, пускайте пользователя только после дополнительной проверки и требуйте сменить пароль. Заранее решите, что делать при недоступности сервиса: обычно вход не блокируют, но событие логируют.
Слой 2. Оценивайте риск каждого входа
Собирайте сигналы и сводите их в оценку риска вместо одного правила «да/нет»:
- Устройство: знакомы ли это устройство и браузер для аккаунта? Сохранённый токен устройства или cookie — сильный положительный сигнал.
- Сеть: ASN дата-центра или хостинга, известный прокси или выходной узел Tor, страна, из которой пользователь никогда не входил.
- Поведение: время между загрузкой страницы и отправкой формы, паттерны ввода и движения курсора, прямой POST в API входа без загрузки страницы.
- История: «невозможное перемещение» между двумя входами, несколько аккаунтов с одного устройства, сброс пароля сразу после входа с нового устройства.
По возможности держите сигналы на сервере: всё, что присылает клиент, можно подделать.
Слой 3. Step-up аутентификация
Оценка риска определяет, что запросить:
| Риск | Действие |
|---|---|
| Низкий | Обычный вход |
| Средний | Одноразовый код или подтверждение по email |
| Высокий | Сильный второй фактор или блокировка с уведомлением пользователя |
Passkeys и коды из приложения хорошо защищают от credential stuffing: одного повторно использованного пароля уже недостаточно. SMS-коды лучше, чем ничего, но слабее против SIM swapping. Если предлагать MFA всем, а проверку запрашивать только при рискованных входах, трение остаётся низким.
Слой 4. Обнаруживайте и замедляйте ботов
- Поставьте эндпоинт входа за bot management на edge или WAF с правилами против credential stuffing.
- Показывайте челлендж только при подозрительных сигналах, а не на каждом входе.
- Возвращайте одинаковую ошибку и похожее время ответа при неверном пароле и несуществующем пользователе, чтобы атакующие не могли проверять свои списки.
- Ограничивайте сразу по нескольким измерениям: IP, подсеть, ASN, устройство, аккаунт и глобально.
- Защитите все точки входа: мобильный API, сброс пароля, старые эндпоинты и партнёрские входы часто забывают.
Слой 5. Мониторьте аномалии входов
У credential stuffing узнаваемый рисунок в метриках. Настройте алерты на:
- всплеск общего числа попыток входа или доли неудачных;
- рост доли ошибок «пользователь не найден» — в списке атакующего есть email, которых у вас никогда не было;
- попытки входа во множество аккаунтов с одного отпечатка устройства;
- рост успешных входов с новых устройств, за которыми следует смена email, телефона или реквизитов выплат.
Когда атака подтверждена: ужесточите пороги, завершите сессии затронутых аккаунтов, принудительно сбросьте пароли и уведомляйте пользователей о входах с новых устройств, чтобы они могли отреагировать сами.
Частые ошибки
- Ставка на один механизм, обычно CAPTCHA, решение которой боты могут отдать на аутсорс.
- Блокировка аккаунта после нескольких неудач — так атакующие легко блокируют реальных пользователей.
- Сообщение «этот email не зарегистрирован» на форме входа.
- Защищённый веб-вход при открытом мобильном API.