JWT: None Algorithm Attack
Сервер принимает JWT с алгоритмом none — подделайте токен без подписи и получите права администратора.
mediumphpPro
Задача
# JWT: обход аутентификации через приём небезопасного алгоритма подписи
## Сценарий
Вы — аудитор безопасности **SecureShop** (PHP + Apache + native JWT). Аутентификация построена не на серверных сессиях, а на JWT-токенах: после успешного входа сервер выдаёт клиенту подписанный токен, сохраняет его в cookie `token`, и каждый последующий запрос авторизуется именно по этому токену. Команда разработки уверена, что верификация настроена корректно — токены подписываются HMAC-секретом, который хранится только на сервере, а перед использованием полезной нагрузки сервер пересчитывает HMAC и сверяет с подписью.
По данным внутреннего аудита, в админ-панели хранится конфиденциальная метка (CTF-флаг), доступная только пользователям с ролью `admin`. Пароль администратора атакующему неизвестен; ваша задача — получить admin-доступ и извлечь флаг, имея только обычную учётную запись `demo` / `demo`.
## Цель
1. Залогиньтесь как `demo` / `demo` и изучите структуру токена в cookie `token` (заголовок, payload, подпись — три base64url-сегмента, разделённые точками).
2. Найдите способ создать токен с произвольными claims (включая `role: admin`) без знания серверного секрета — иначе говоря, обойти криптографическую проверку подписи.
3. Подмените cookie `token` на собранное значение, откройте `/admin` и извлеките CTF-флаг.
## Теория
JWT-токен (RFC 7519) состоит из трёх base64url-частей, разделённых точками: **header**, **payload**, **signature**. Header содержит поле `alg` — имя алгоритма подписи. Payload содержит claims — данные о пользователе. Signature доказывает, что header и payload не изменялись после выдачи токена.
Стандарт исторически включал значение `alg: "none"` — «токен без подписи». Этот режим был введён для использования в уже защищённых каналах (mTLS, локальный IPC), но в публичных HTTP-API стал источником массовых уязвимостей. Если сервер при верификации читает поле `alg` из заголовка и попадает в ветку, где для `none` подпись пропускается, проверка фактически отсутствует — атакующий может предъявить любой токен с любыми claims, не зная секрета.
В этой лабе верификатор реализован вручную на нативном PHP (`base64`, `hash_hmac`, `hash_equals`). Ошибка в нём — отдельная ветка кода, обрабатывающая `alg = "none"`: она просто возвращает payload без какой-либо проверки. Дополнительно функция получения текущего пользователя перезаписывает поле `role`, прочитанное из БД, значением из claims токена — поэтому подменив `role` в payload, атакующий сразу же получает административные права.
**Уязвимый PHP-паттерн:**
```php
$alg = strtolower($header['alg'] ?? '');
// VULNERABILITY: accepts "none" — no signature verification
if ($alg === 'none') {
return $payload;
}
// ... далее ветка с честной HMAC-проверкой для HS256
// VULNERABILITY: role overridden from JWT claims
$user['role'] = $payload['role'] ?? $user['role'];
```
## Таблица атак на JWT
| Класс атаки | Идея | Применима в этой лабе |
|-------------|------|----------------------|
| `alg: none` | Объявить в заголовке отсутствие подписи; сервер пропускает проверку | **Да** — основной вектор |
| HS256 ↔ RS256 (key confusion) | Подменить алгоритм с асимметричного на симметричный, использовать публичный ключ как HMAC-секрет | Нет — сервер использует HS256, асимметричной схемы нет |
| Brute-force HMAC-секрета | Подобрать слабый секрет офлайн (hashcat, jwt_tool) | Нет в рамках этой лабы (отдельная категория) |
| `kid` path traversal | Подсунуть в заголовке путь к контролируемому атакующим файлу как ключ | Нет — поле `kid` верификатор не использует |
| JWK header injection | Передать собственный публичный ключ прямо в заголовке токена | Нет — верификатор использует серверный секрет, не доверяет заголовку |
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Обычная учётная запись | `demo` / `demo` |
| Эндпоинт логина | `POST /login` (выдаёт cookie `token` с HS256-JWT) |
| Имя cookie | `token` |
| Кодировка частей токена | base64url без `=`-выравнивания |
| Привилегированный эндпоинт | `GET /admin` (требует `role: admin` в подменённом токене) |
| Где находится флаг | в HTML-ответе `/admin` в карточке Admin Flag, формат `flag{...}` |