User enumeration
как атакующие узнают, кто у вас в системе (и как с этим жить)
Автор: Dungleton Security
Для кого: все, кто хоть раз получал письмо «восстановите пароль» и думал
«а я тут вообще при чём?»
Зачем атакующему перечислять пользователей
User enumeration - Это разведка.
Прежде чем подбирать пароль или слать фишинг, атакующему нужно понять простую вещь:
какие учётные записи вообще существуют.
Если логин реальный - с ним уже можно что-то делать:
- подобрать пароль (или проверить утёкший),
- сделать правдоподобный фишинг,
- использовать как якорь для дальнейших атак.
Если логин выдуманный — он бесполезен.
Поэтому первая задача —
отделить реальные аккаунты от мусора.
Мы в
Dungleton регулярно видим, как это делают на практике, чаще всего применяя к нам(
Если атакующий знает несколько реальных имён (а они почти всегда где-то есть — сайт, LinkedIn, конференции), дальше всё механически: берём список имён, прогоняем через форму регистрации, отмечаем «занято».
Даже если:
- одинаковый HTTP-код,
- одинаковый текст,
остаются:
- разные поля в JSON,
- разные stack trace’ы в debug-режиме,
- разные тайминги.
Отдельный привет логам и метрикам, которые пишутся только «если пользователь найден».
OSINT: вы сами всё рассказали
Очень часто формат логинов вообще не нужно угадывать.
Он уже есть:
- на странице «О команде»,
- в PDF-презентации,
- в примерах в документации,
- на скриншотах из интерфейсов,
- в докладах с конференций.
Пара имён + один пример логина = гипотеза по формату.
Дальше — перебор и проверка.
Даже если это «просто примеры» и «не прод» — атакующему всё равно.
Timing-атаки: когда ошибки одинаковые, а время — нет
Даже если вы сделали всё «правильно»:
- один текст,
- один код ответа,
можно спалиться по
времени обработки.
Типичный сценарий:
- несуществующий логин → быстрый отказ;
- существующий логин → поиск пользователя + проверка хеша пароля.
Если используется bcrypt / scrypt / argon2 — разница может быть десятки или сотни миллисекунд.
Этого достаточно:
- делается серия запросов,
- считается среднее время,
- логины с «длинным» ответом помечаются как валидные.
Что реально помогает
- При user not found всё равно выполнять хеширование (на заглушке).
- Следить, чтобы логирование и метрики не добавляли разницу.
- Добавлять jitter или фиксированную задержку там, где это допустимо.
- Не забывать, что API и UI — разные поверхности атаки.
Здесь классика жанра:
Правильно:
«Если аккаунт с таким email существует, мы отправили письмо»
На практике:
- «Пользователь не найден»,
- письмо отправляется только для существующих,
- разный тайминг ответа.
Регистрация и «логин занят»
Проверка занятости логина в реальном времени — почти всегда утечка.
Особенно если:
- нет капчи,
- нет лимитов,
- нет задержек.
Лучше:
- проверять занятость только при финальной отправке,
- ограничивать частоту,
- не говорить явно, почему регистрация не прошла.
Про шаблоны логинов и «случайные» имена
Форматы name.family, n.family, family.n, name.f удобны.
И именно поэтому опасны.
Как только они где-то засветились
вместе с реальными именами, атакующий получает:
- формат,
- кандидатов,
- способ проверки.
Даже если это было «просто для примера».
Итог
User enumeration — это не уязвимость.
Но позволяя её воспроизводить, мы
сделали атаку проще.
Чем меньше различий:
- в текстах,
- в кодах,
- во времени ответа,
и чем меньше реальных примеров и шаблонов в открытом доступе —
тем меньше полезных сигналов вы отдаёте наружу.
Мы в
Dungleton Security регулярно проверяем такие вещи на наших системах - и почти всегда что-то находится.
Пишите мне нам на email, если у вас есть вопросы, с Вами были Эмили Браун и Павел Чернов, до встречи на PHDays 2026!