JWT Key Confusion (RS256 to HS256)
JWT RS256/HS256 key confusion: the server exposes the public key and doesn't enforce the signing algorithm, allowing token forgery via algorithm switch.
hardgolangPro
Задача
# JWT Key Confusion: подмена асимметричного алгоритма симметричным
## Сценарий
Вы — аудитор безопасности **SecureShop**. Приложение использует JWT-токены для аутентификации с асимметричным алгоритмом подписи (RSA): сервер хранит приватный ключ и подписывает им токены, а публичный ключ открыто доступен через стандартные эндпоинты (его, например, потребляют клиенты для верификации on-device или для интеграции с внешними системами). По данным внутреннего аудита, в админ-панели хранится конфиденциальная метка, доступная только пользователям с ролью администратора; её требуется извлечь, имея только обычную учётную запись.
## Цель
1. Изучите, как именно сервер верифицирует JWT-токены: какие алгоритмы он принимает и как именно валидируется заголовок `alg`.
2. Проанализируйте структуру keyfunc-обработчика валидации — какой ключ возвращается для каких алгоритмов.
3. Сформируйте подделанный токен, не зная серверного приватного ключа, и получите доступ к админ-панели; извлеките флаг.
## Теория
Key Confusion (или Algorithm Confusion) — атака на JWT-валидацию, основанная на смешении классов криптографических алгоритмов. RSA-подписи (RS256, RS384, RS512) используют **асимметричную** криптографию: приватный ключ для подписания, публичный для верификации. HMAC-подписи (HS256, HS384, HS512) используют **симметричный** ключ: один и тот же секрет для подписания и верификации. Когда серверный keyfunc возвращает один и тот же ключ для разных классов алгоритмов (или возвращает «байты PEM-публичного ключа» как материал для верификации, не различая, на какой алгоритм этот материал реально подаётся), атакующий получает возможность взять открытый публичный ключ и использовать его как «секрет» для подписи токена HMAC.
**Уязвимый паттерн:**
```go
// Keyfunc не различает классы алгоритмов — байты публичного ключа
// при HS256 обрабатываются как HMAC-секрет
token, _ := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); ok {
return &rsaPrivateKey.PublicKey, nil
}
return []byte(rsaPublicKeyPEM), nil // <- путь HMAC: публичный ключ как секрет
})
```
Атакующий получает публичный ключ через стандартный эндпоинт сервиса, конструирует токен с произвольными claims (включая `role: admin`), указывает в заголовке симметричный алгоритм (HS256), и подписывает токен PEM-байтами публичного ключа как HMAC-секретом. Сервер при верификации видит `alg: HS256` → возвращает PEM-байты публичного ключа как материал → HMAC проверяется на тех же байтах → подпись «совпадает» → токен принимается с подменёнными claims.
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Учётные данные обычного пользователя | `demo` / `demo` |
| Эндпоинт логина | `POST /login` (выдаёт cookie `token` с RS256-JWT) |
| Эндпоинт публичного ключа | `GET /api/public-key` (или `.well-known/jwks.json`) |
| Привилегированный эндпоинт | `GET /admin` (требует `role: admin` в claims) |