awesome-everything EN
↑ Обратно к восхождению

Безопасность

OAuth в production: audience атаки, observability и реальные провалы

Суть Audience confusion атаки, хранение токенов по типу клиента, observability метрики для обнаружения компрометации и четыре реальных OAuth провала, стоивших миллионы.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 11 min

JWT-валидатор логирует WARN, когда aud claim не содержит ожидаемое значение, затем всё равно принимает токен — “aud содержит client_id вызывающего”. Токен был выпущен для другого приложения. Валидатор только что принял чужой токен — audience bypass, реальный CVE паттерн, замеченный в Microsoft Azure AD в ноябре 2024.

Audience атаки

Когда один authorization server выдаёт токены для нескольких resource server (API-A и API-B), валидатор, принимающий любой валидный aud, может быть эксплуатирован: атакующий, получивший токен для API-A, представляет его API-B.

Правильная проверка aud: содержит ли aud claim токена hard-coded идентификатор этого resource server? Не “содержит ли aud любой известный client_id?” — логическая инверсия, обходящая защиту.

RFC 8707 — Resource Indicators: Клиент указывает resource параметр в /token запросе, называя target resource server. IdP выдаёт токен только с этой audience. Если клиенту нужны токены для обоих API-A и API-B, он вызывает /token дважды с разными resource значениями. Неудобно, но корректно.

Token Exchange (RFC 8693): Resource server, которому нужно вызвать downstream сервис, может представить свой incoming токен и запросить новый токен, валидный у downstream, с суженной audience. Это “least privilege” для service-to-service hop.

Claim cnf (confirmation) сужает привязку дальше: DPoP устанавливает cnf.jkt в thumbprint публичного ключа. Токен с cnf можно использовать только с proof, подписанным соответствующим приватным ключом — audience binding плюс sender binding вместе.

aud check: правильно vs неправильно
НЕПРАВИЛЬНО — audience bypass
if token.aud.includes(request.client_id) { accept }
Принимает любой токен где aud содержит ЛЮБОЙ известный client_id, включая из другого приложения.
ПРАВИЛЬНО
const EXPECTED = “acme-api-public”;
if token.aud.includes(EXPECTED) { accept }
Hard-coded ожидаемая audience. Никакой request-derived логики. Регресс-тест: токен с aud=[“other-api”] должен быть отклонён.

Хранение токенов по типу клиента

Где живут токены — определяет поверхность компрометации:

Browser SPA:

  • Access-токен: только JavaScript память (closure или React state). Никогда localStorage — XSS-readable.
  • Refresh-токен: httpOnly; Secure; SameSite=Strict cookie — недоступен из JS.
  • При page reload: prompt=none silent /authorize round, используя refresh-токен cookie.

Native mobile app:

  • Access-токен: OS keychain (iOS Keychain, Android Keystore). Не process memory.
  • Refresh-токен: OS keychain, никогда в SharedPreferences или незащищённом storage.

Server-side приложение:

  • Access-токен: server session (in-memory или Redis, не клиент).
  • Refresh-токен: server session. Клиент никогда не видит ни одного токена.

Machine-to-machine (CI/CD, сервис):

  • Использовать client_credentials grant, не user-delegated токены.
  • Никогда не embed user access-токены в CI pipelines.

Production observability

Минимально-жизнеспособный OAuth observability dashboard:

МетрикаУсловие alert
refresh_replay_detected_totalЛюбое ненулевое значение — вероятная компрометация
id_token_validation_failure_total по reasonSpike на sig/kid → JWKS rotation issue
token_introspection_latency_p99Выше 200ms → IdP перегружен
jwks_cache_hit_ratioПадение ниже 90% → слишком короткий cache TTL
dpop_proof_failure_total по reasonSpike на iat_skew → clock drift в mobile клиентах
token_request_total по outcomeSpike на invalid_grant → атака или misconfigured клиент

refresh_replay_detected_total > 0 — самый actionable alert: означает что хотя бы один refresh-токен был использован двумя различными клиентами — кто-то украл токен.

Четыре реальных OAuth провала

Facebook, сентябрь 2018. Баг в фиче “View As” утёк 50 миллионов access-токенов. Атакующие могли имперсонировать любого из этих пользователей в любом Facebook OAuth клиенте (Spotify, Tinder, Instagram). Fix: инвалидация 90 миллионов токенов.

Slack, февраль 2017. Misconfigured OAuth redirect URI в third-party Slack app позволил атакующему фишингом украсть authorization code workspace owner. Атакующий эскалировал до full admin access. Fix: URI валидация, enforcement exact-match redirect URI на уровне IdP.

GitHub Enterprise, 2021. Баг в PKCE verification позволил downgrade атаки против старых клиентов. Исправлено в patch release.

Microsoft Azure AD, ноябрь 2024. Token-validation cache poisoning уязвимость позволила crafted токену обойти aud валидацию ~30 минут на cached negative response. Fix за несколько часов. Root cause: negative-response кеш не различал “aud не найден” от “aud явно отклонён”.

Паттерн: каждый инцидент вовлекал одну пропущенную или buggy обязательную проверку. Industry ответ на все четыре был одинаков — больше обязательных проверок, короче TTL, шире observability.

Викторина

Команда хочет делить один access-токен между двумя resource server (API-A и API-B) для удобства. Почему это опасно?

Викторина

Почему access-токены нельзя хранить в localStorage в browser SPA?

Расставь шаги по порядку

Поставь шаги диагностики spike id_token validation failures в порядке:

  1. 1 Проверить dimension failure_reason на id_token_validation_failure_total
  2. 2 Если reason 'sig' или 'kid-not-found' — подозревать JWKS key rotation
  3. 3 Проверить jwks_cache_hit_ratio — падение указывает на stale cache от rotation
  4. 4 Подтвердить, опубликовал ли IdP новый signing key недавно
  5. 5 Триггернуть немедленный JWKS refresh на всех инстансах
  6. 6 Уменьшить JWKS cache TTL до 5–10 минут и добавить on-cache-miss refresh как backstop
Вспомните перед уходом
  1. 01
    Объясни audience confusion атаку и правильную aud check, предотвращающую её.
  2. 02
    Почему refresh_replay_detected_total > 0 — сигнал компрометации, а не безвредная ошибка?
  3. 03
    Какой observability gap решает короткий access token TTL (5–15 мин)?
Итог

Валидация audience — hard-coded identity check, не membership check — aud токена должен содержать конкретный идентификатор этого resource server, никогда не выводимый из запроса. Хранение токенов следует threat model клиента: JS память для SPA, OS keychain для native, server session для server-side. Production observability должна раскрывать refresh_replay_detected_total (сигнал компрометации), JWKS cache hit ratio (готовность к ротации) и introspection latency (здоровье IdP). Четыре крупных OAuth инцидента — Facebook 2018, Slack 2017, GitHub Enterprise 2021, Azure AD 2024 — каждый следовал из одной пропущенной или buggy обязательной проверки. OAuth безопасность — проблема полного набора: каждая проверка должна проходить, каждый раз.

Связанные уроки
встречается в202
Продолжить восхождение ↑OAuth/OIDC: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.