Timing Attack: Перечисление Пользователей
Обработчик логина имеет разное время ответа для существующих и несуществующих пользователей, что позволяет перечислять аккаунты.
hardgolangPro
Задача
# Timing Attack: перечисление пользователей через различия во времени ответа
## Сценарий
Вы аудируете интернет-магазин **SecureShop**. У приложения есть форма логина с корректным сообщением об ошибке при неверных данных: «Invalid credentials» — одинаковый текст для несуществующего пользователя и для существующего с неверным паролем. Команда уверена, что защита от user enumeration реализована — атакующий не может по сообщению понять, существует ли учётка. По данным внутреннего аудита, в админ-панели хранится конфиденциальная метка, доступная пользователю с привилегиями admin; её требуется получить.
## Цель
1. Изучите поведение эндпоинта логина для разных сценариев: несуществующий пользователь, существующий с неверным паролем, существующий с верным паролем. Сравните не только текст ответа, но и поведение по другим осям (время, заголовки, статус).
2. Найдите боковой канал утечки информации, через который можно различить «несуществующий пользователь» от «существующий с неверным паролем» — даже при идентичном текстовом ответе.
3. Используя этот канал, подтвердите наличие admin-учётки и получите доступ к админ-панели; извлеките флаг.
## Теория
User enumeration через **timing side-channel** — класс уязвимостей, при которых атакующий определяет существование учётной записи по различиям в **физическом времени обработки** запроса, даже если все наблюдаемые ответы (статус, заголовки, тело) идентичны. Корень проблемы — в том, что путь обработки запроса проходит через дорогостоящие операции (хеширование пароля, обращение к БД, проверка криптоподписи) **только когда пользователь существует**; если кода нет — обработка завершается мгновенно.
**Уязвимый паттерн:**
```go
// Дорогостоящая операция (bcrypt) выполняется только при найденном пользователе
err := db.QueryRow("SELECT password FROM users WHERE username=?", username).Scan(&hash)
if err != nil {
return // мгновенный возврат — не существует
}
bcrypt.CompareHashAndPassword(hash, password) // занимает ~60-100ms
```
bcrypt по дизайну вычислительно дорог — это нужно для замедления offline-перебора хешей. Но в timing-side-channel это становится явным сигналом: время ответа на «существующий с неверным паролем» отличается от «несуществующий» на порядки. Атакующий через серию замеров (curl + `--write-out '%{time_total}'`, Python с `time.time()`) с разными username получает чёткий сигнал, какие учётки реально есть в системе. Дальше работают другие техники (brute-force подобранных учёток, password-spray по полученному списку).
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Учётные данные | `demo` / `demo` |
| Эндпоинт логина | `POST /login` (timing-side-channel в обработке) |
| Целевой пользователь | `admin` (имя из публичных конвенций) |
| Привилегированный эндпоинт | `GET /admin` (флаг отображается на странице) |