Статья Атаки на OAuth 2.0: redirect URI manipulation, перехват токенов и authorization code interception на практике

Стальная стрелка-указатель с изгибом посередине лежит на чёрной ткани. Вдоль стрелки выгравирована надпись с адресом перенаправления, в тёмном фоне светятся янтарные огни.


На пентесте SaaS-платформы с SSO через Keycloak я обнаружил, что авторизационный сервер принимает redirect_uri с path traversal: https://app.client.com/callback/../../../evil.com - валидация проверяла только начало строки. Подставил свой URL, отправил crafted ссылку, authorization code пришёл на Burp Collaborator через 12 секунд после клика. Полный ATO, ноль алертов в WAF. Такие misconfig'и в OAuth 2.0 встречаются в каждом втором веб-приложении с SSO-интеграцией, и redirect URI по-прежнему остаётся самым недооценённым вектором при тестировании аутентификации.

Место атак на OAuth 2.0 в цепочке пентеста​

Уязвимости OAuth 2.0 и OpenID Connect по классификации MITRE ATT&CK попадают сразу в несколько тактик. Steal Application Access Token (T1528, Credential Access) - перехват токенов для доступа от имени жертвы. Exploit Public-Facing Application (T1190, Initial Access) - эксплуатация misconfig'ов авторизационного сервера как точки входа. Browser Session Hijacking (T1185, Collection) - перехват сессии через манипуляцию OAuth flow в браузере. По OWASP это A07:2021 - Identification and Authentication Failures и API2:2023 - Broken Authentication. Подробнее - в нашем материале про атаки на аутентификацию.

На практике OAuth-тестирование начинается в фазе recon. Индикаторы: кнопка «Войти через Google/GitHub/Microsoft», запросы к /authorize с параметрами client_id, redirect_uri, response_type. Если приложение использует SSO - OAuth/OIDC почти наверняка задействован.

Требования к окружению и recon​

Перед тестированием подготовьте инструментарий:
  • Burp Suite Pro с расширением AuthMatrix - Community Edition покроет базовый перехват, но Pro нужен для Intruder и автоматизации redirect_uri-перебора
  • Postman или curl для пошагового воспроизведения OAuth flow
  • Python 3.x с библиотекой requests для автоматизации: генерация state/PKCE-пар, массовая проверка redirect_uri
Recon начинается с endpoint discovery. Запрашиваем /.well-known/openid-configuration на authorization server - там перечислены authorization_endpoint, token_endpoint, response_types_supported, code_challenge_methods_supported. Если последнее поле отсутствует - PKCE(Proof Key for Code Exchange) не обязателен, и поверхность атаки сразу расширяется. Фиксируем redirect_uri из регистрации клиента (если доступно через dynamic client registration или документацию) - это базовая точка для redirect URI manipulation.

Redirect URI Manipulation: обход валидации на практике​

1780029640443.webp
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Процесс нормализации представляет собой превращение относительного пути в абсолютный с помощью встроенных функций языка.
Исследование ACM «OAuth 2.0 Redirect URI Validation Falls Short, Literally» показало, что path confusion и parameter pollution - рабочие векторы даже против серверов, которые декларируют strict matching.

Open redirect chain. Находите open redirect где-то в приложении - допустим, https://app.example.com/goto?url=https://evil.com. Если redirect_uri зарегистрирован как https://app.example.com/* (wildcard) или проверяется по prefix или сервер имеет слабую сатнитизацию - атакующий подставляет open redirect endpoint как redirect_uri. Авторизационный сервер считает URI валидным, но code уходит через цепочку редиректов на левый сервер. По данным Salt Labs, именно такой вектор был эксплуатирован в уязвимости Booking.com - redirect URI manipulation через недостаточную валидацию позволяла перехватить OAuth-сессию пользователя.

Subdomain takeover. Если redirect_uri содержит wildcard *.example.com, а CNAME для staging.example.com указывает на деактивированный облачный ресурс - атакующий захватывает subdomain и получает валидный redirect endpoint. Исследователи Descope описали аналогичный вектор с Google OAuth domain takeover, где expired domain позволял войти в аккаунт жертвы.

При таком раскладе, фишинг атака становится просто критически высокого уровня, поскольку пользователи видят официальный SSL-сертификат и знакомый домен.
Более того, если для поддомена настроены MX-записи, хакер может отправлять и принимать письма от лица компании.

Проверка в Burp Suite​

В Burp Proxy перехватываем запрос к authorization_endpoint. Параметр redirect_uri - наша цель. Отправляем в Repeater и тестируем мутации:
Код:
GET /authorize?response_type=code
  &client_id=legit_client_id
  &redirect_uri=https://app.example.com/callback/../../../evil.com
  &scope=openid+profile+email
  &state=random_value HTTP/1.1
Host: auth.example.com
Что проверять?
  • Удаление path (/callback/)
  • Добавление subpath (/callback/../../admin)
  • URL-encoded символы (%2e%2e%2f вместо ../)
  • Параметры в URI (/callback?next=https://evil.com)
  • Подстановку другого домена в path.
  • Каждую мутацию - в Repeater.
Ответ 302 с вашим URI в заголовке Location означает: валидация обойдена.

Для автоматизации: Burp Intruder с payload-списком из 20-30 мутаций redirect_uri, throttle 1 request/sec (чтобы не триггерить rate limiting на IdP).

Перехват Authorization Code​

Authorization code - одноразовый, живёт секунды. Но если перехватить его до обмена на token легитимным клиентом - атакующий получает доступ от имени жертвы. Помимо redirect URI manipulation есть менее очевидные каналы утечки.

Утечка через Referer Header​

Механизм, который русскоязычные источники почти не разбирают на уровне HTTP-запросов. После получения authorization code браузер перенаправляется на redirect_uri с code в query string: https://app.example.com/callback?code=AUTH_CODE&state=xxx. Если на странице callback загружаются внешние ресурсы - скрипт аналитики, шрифт с CDN, пиксель трекинга - браузер отправит полный URL включая ?code=AUTH_CODE в заголовке Referer при запросе к третьей стороне.

Согласно IETF Security BCP (раздел 4.2 «Credential Leakage via Referer Headers»), это документированная угроза с конкретной мерой защиты: установить Referrer-Policy: no-referrer на callback-странице.

Как проверять?
  1. Проходим полный OAuth flow через Burp.
  2. В HTTP History фильтруем запросы к third-party доменам, отправленные со страницы callback. Если в заголовке Referer присутствует ?code= - вектор подтверждён.
Разработчики регулярно забывают об этом: ставят PKCE, внедряют BFF, но оставляют Google Analytics JS на callback-странице. Я видел это на трёх независимых проектах - и каждый раз удивлялся, как тщательно выстроенная архитектура рушилась из-за одного скрипта.

Прерывание OAuth Flow через XSS​

Более сложная техника, подробно описанная в Habr-статье об authorization code injection. Суть: атакующий, имея XSS в том же origin, что и redirect_uri, открывает скрытый iframe с authorization request. Если у жертвы есть активная сессия на IdP - авторизация проходит silent (без взаимодействия). Code возвращается в iframe, и JavaScript атакующего читает его (same-origin policy позволяет доступ к содержимому).

Ключевой момент: HttpOnly cookie на session ID не защищают. Атакующий не крадёт существующую сессию - он инициирует новый OAuth flow и получает свежий code. Как описано в OAuth 2.0 for Browser-Based Applications: «атакующий получает свои собственные учётные данные», не компрометируя существующие.

Когда работает:
  • XSS в любом месте origin приложения (не обязательно на странице callback)
  • response_mode не form_post (form_post отправляет code в теле POST, недоступном клиентскому JS)
  • У жертвы есть активная сессия на IdP
Когда НЕ работает:
  • IdP требует повторного consent при каждом запросе (prompt=consent)
  • Используется form_post response mode
  • CSP запрещает iframe с доменом IdP
Применимость: внешний пентест веб-приложений с OAuth/OIDC. Требуется предварительная XSS в origin - это отдельный вектор, который нужно найти до эксплуатации OAuth flow.

OAuth Token Theft: implicit flow и 307 redirect​

Implicit flow ([URL='https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics']response_type=token[/URL]) возвращает access token прямо во fragment URL: https://app.example.com/callback#access_token=eyJ.... IETF Security BCP прямо рекомендует отказаться от implicit flow. Но на пентестах он встречается до сих пор - в legacy SPA, мобильных WebView и приложениях, собранных по устаревшим туториалам с 2017 года.

Если в authorization request вы видите response_type=token - это уязвимость в контексте современных рекомендаций. Фрагмент URI не отправляется серверу (не попадает в логи), но доступен через JavaScript. Для blue team это слепая зона: токен утёк, а в серверных логах тишина. Связка с XSS даёт прямой ATO без необходимости обмена code на token.

Отдельно стоит вспомнить инцидент с GitHub, Heroku и Travis CI, описанный исследователями Descope: атакующие похитили OAuth-токены из интеграций Heroku и Travis CI, использовали их для доступа к приватным GitHub-репозиториям и далее обнаружили AWS-credentials, что дало доступ к инфраструктуре npm. OAuth token theft - не маловероятная проблема, а путь к lateral movement через trust chain между сервисами.

307 Redirect Exploit. Менее известный вектор (IETF BCP, раздел 4.12). Если авторизационный сервер возвращает HTTP 307 вместо 302 после аутентификации, браузер пересылает тело POST-запроса (включая credentials пользователя) на redirect_uri клиента. Применимо к self-hosted IdP (Keycloak, кастомные реализации) - проверяйте HTTP status code в Burp после отправки формы логина на IdP. Разница между 302 и 307 - одна цифра, а последствия принципиально разные.

PKCE Bypass и CSRF через отсутствие state​

1780030662543.webp

PKCE Downgrade Attack​

PKCE (RFC 7636) связывает code_verifier с code_challenge - без verifier перехваченный code бесполезен. Но если авторизационный сервер не обязывает PKCE - атакующий отправляет authorization request без code_challenge. Сервер принимает запрос, code выдаётся без привязки к verifier. Это и есть PKCE downgrade, описанный в чек-листе Xakep и IETF BCP раздел 4.8.

Проверка: отправляем authorization request без параметров code_challenge и code_challenge_method. Если сервер возвращает 302 с code - downgrade возможен. Из /.well-known/openid-configuration поле code_challenge_methods_supported: если отсутствует - PKCE скорее всего опционален.

Тут есть нюанс, который часто упускают. PKCE для confidential clients (серверных приложений с client_secret) - не панацея. Как детально разобрано в Habr-статье: при XSS в origin клиента атакующий контролирует и challenge, и verifier, потому что он сам инициирует flow. PKCE защищает от перехвата чужого code третьей стороной, но не от создания нового flow из контекста XSS.

CSRF в OAuth через отсутствие state​

Параметр state - одноразовый токен, привязанный к сессии пользователя. Без него атакующий подставляет свой authorization code в callback-URL жертвы, и приложение привязывает аккаунт жертвы к аккаунту атакующего на IdP. Классическая CSRF-атака на OAuth, описанная в IETF BCP раздел 4.7.

Проверка: удаляем state из authorization request или подставляем произвольное значение. Если приложение принимает callback без валидации state - CSRF подтверждена. В OpenID Connect аналогичную роль выполняет nonce, но проверяйте оба параметра.

Выбор вектора: decision tree для пентестера​

УсловиеТехникаИнструмент
redirect_uri принимает нестандартные значенияRedirect URI manipulationBurp Repeater + Intruder
Wildcard в redirect_uri + open redirect в приложенииOpen redirect chainBurp, paramspider для поиска open redirect
XSS в origin приложения, response_mode не form_postПрерывание flow + code theft через iframeJS payload в XSS
response_type=token (implicit flow)Token extraction через fragmentXSS payload, Referer leakage
PKCE не обязателен на ASPKCE downgrade + code interceptionBurp Repeater (удалить code_challenge)
Внешние ресурсы на callback-страницеReferer header leakageBurp HTTP History, фильтр Referer
IdP отвечает 307 вместо 302307 redirect credential leakBurp, проверка status code
Параметр state отсутствует или не валидируетсяCSRF - привязка аккаунта атакующегоCrafted callback URL
Клиент работает с несколькими IdPMix-Up Attack - подмена issuerПодмена authorization server в ответе

Порядок тестирования: recon (/.well-known/openid-configuration) - redirect URI fuzzing - проверка PKCE enforcement - Referer leakage - state validation - XSS-зависимые векторы.

Что видит WAF при атаках на OAuth 2.0​

OPSEC при тестировании OAuth - отдельная история. Где ваши действия оставляют следы:

IdP-логи. Каждый authorization request логируется. Keycloak пишет все authorization events, Azure AD фиксирует consent grants. Массовый перебор redirect_uri создаёт аномалию. Рекомендация: throttle в Intruder 1 req/sec, не более 30 мутаций за сессию.

WAF. ModSecurity и cloud WAF (Cloudflare, AWS WAF) ловят URL-encoded path traversal (%2e%2e%2f) в query parameters. Чистые URL-мутации (подмена path, добавление параметров) обычно не триггерят правила - WAF видит валидный HTTP-запрос к легитимному домену.

Приложение. Большинство приложений не логируют значение redirect_uri из authorization response - это и делает redirect URI manipulation тихим вектором. Implicit flow ещё тише: фрагмент URI (#access_token=...) не доходит до сервера вообще, в логах пусто.

Слепая зона blue team. Основная проблема - корреляция между authorization event на IdP и token usage на resource server. Без SIEM-правила, связывающего эти события, token theft через redirect manipulation остаётся невидимым.

За два с лишним года тестирования OAuth-интеграций в продуктовых компаниях я наблюдаю один и тот же паттерн: команды внедряют OAuth по документации провайдера (Auth0, Keycloak, Azure AD), но не проверяют поведение при нестандартном input. Документация описывает happy path. Задача пентестера - проверить unhappy path: что будет, если redirect_uri содержит ../, если code_challenge отсутствует, если response_mode подменён на fragment вместо form_post.

Самый недооценённый вектор из всех описанных - Referer leakage. Разработчики внедряют PKCE, строят BFF-архитектуру, ставят HttpOnly на cookies - и при этом оставляют analytics-скрипт на callback-странице, через который authorization code утекает в заголовке Referer. Ни один WAF не среагировал, потому что Referer - штатный механизм HTTP, а не аномалия.

PKCE подаётся как решение всех проблем OAuth-безопасности. В реальности PKCE закрывает ровно один сценарий: перехват чужого authorization code. Он не защищает от XSS в origin клиента, где атакующий инициирует собственный flow и контролирует оба компонента - и challenge, и verifier. BFF тоже не серебряная пуля: session ID, выданный серверной частью после code exchange, перехватывается тем же JavaScript из iframe - HttpOnly cookie не помеха, потому что атакующий читает Set-Cookie из заголовка ответа на свой же fetch-запрос. На HackerLab.pro в web-категории есть задачи, где OAuth redirect bypass нужно довести до рабочего ATO - на живом стенде каждый шаг ощущается иначе, чем в writeup.
 
Последнее редактирование модератором:
Посмотреть вложение 83516

На пентесте SaaS-платформы с SSO через Keycloak я обнаружил, что авторизационный сервер принимает redirect_uri с path traversal: https://app.client.com/callback/../../../evil.com - валидация проверяла только начало строки. Подставил свой URL, отправил crafted ссылку, authorization code пришёл на Burp Collaborator через 12 секунд после клика. Полный ATO, ноль алертов в WAF. Такие misconfig'и в OAuth 2.0 встречаются в каждом втором веб-приложении с SSO-интеграцией, и redirect URI по-прежнему остаётся самым недооценённым вектором при тестировании аутентификации.

Место атак на OAuth 2.0 в цепочке пентеста​

Уязвимости OAuth 2.0 и OpenID Connect по классификации MITRE ATT&CK попадают сразу в несколько тактик. Steal Application Access Token (T1528, Credential Access) - перехват токенов для доступа от имени жертвы. Exploit Public-Facing Application (T1190, Initial Access) - эксплуатация misconfig'ов авторизационного сервера как точки входа. Browser Session Hijacking (T1185, Collection) - перехват сессии через манипуляцию OAuth flow в браузере. По OWASP это A07:2021 - Identification and Authentication Failures и API2:2023 - Broken Authentication. Подробнее - в нашем материале про атаки на аутентификацию.

На практике OAuth-тестирование начинается в фазе recon. Индикаторы: кнопка «Войти через Google/GitHub/Microsoft», запросы к /authorize с параметрами client_id, redirect_uri, response_type. Если приложение использует SSO - OAuth/OIDC почти наверняка задействован.

Требования к окружению и recon​

Перед тестированием подготовьте инструментарий:
  • Burp Suite Pro с расширением AuthMatrix - Community Edition покроет базовый перехват, но Pro нужен для Intruder и автоматизации redirect_uri-перебора
  • Postman или curl для пошагового воспроизведения OAuth flow
  • Python 3.x с библиотекой requests для автоматизации: генерация state/PKCE-пар, массовая проверка redirect_uri
Recon начинается с endpoint discovery. Запрашиваем /.well-known/openid-configuration на authorization server - там перечислены authorization_endpoint, token_endpoint, response_types_supported, code_challenge_methods_supported. Если последнее поле отсутствует - PKCE(Proof Key for Code Exchange) не обязателен, и поверхность атаки сразу расширяется. Фиксируем redirect_uri из регистрации клиента (если доступно через dynamic client registration или документацию) - это базовая точка для redirect URI manipulation.

Redirect URI Manipulation: обход валидации на практике​

Посмотреть вложение 83546
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Процесс нормализации представляет собой превращение относительного пути в абсолютный с помощью встроенных функций языка.
Исследование ACM «OAuth 2.0 Redirect URI Validation Falls Short, Literally» показало, что path confusion и parameter pollution - рабочие векторы даже против серверов, которые декларируют strict matching.

Open redirect chain. Находите open redirect где-то в приложении - допустим, https://app.example.com/goto?url=https://evil.com. Если redirect_uri зарегистрирован как https://app.example.com/* (wildcard) или проверяется по prefix или сервер имеет слабую сатнитизацию - атакующий подставляет open redirect endpoint как redirect_uri. Авторизационный сервер считает URI валидным, но code уходит через цепочку редиректов на левый сервер. По данным Salt Labs, именно такой вектор был эксплуатирован в уязвимости Booking.com - redirect URI manipulation через недостаточную валидацию позволяла перехватить OAuth-сессию пользователя.

Subdomain takeover. Если redirect_uri содержит wildcard *.example.com, а CNAME для staging.example.com указывает на деактивированный облачный ресурс - атакующий захватывает subdomain и получает валидный redirect endpoint. Исследователи Descope описали аналогичный вектор с Google OAuth domain takeover, где expired domain позволял войти в аккаунт жертвы.

При таком раскладе, фишинг атака становится просто критически высокого уровня, поскольку пользователи видят официальный SSL-сертификат и знакомый домен.
Более того, если для поддомена настроены MX-записи, хакер может отправлять и принимать письма от лица компании.

Проверка в Burp Suite​

В Burp Proxy перехватываем запрос к authorization_endpoint. Параметр redirect_uri - наша цель. Отправляем в Repeater и тестируем мутации:
Код:
GET /authorize?response_type=code
  &client_id=legit_client_id
  &redirect_uri=https://app.example.com/callback/../../../evil.com
  &scope=openid+profile+email
  &state=random_value HTTP/1.1
Host: auth.example.com
Что проверять?
  • Удаление path (/callback/)
  • Добавление subpath (/callback/../../admin)
  • URL-encoded символы (%2e%2e%2f вместо ../)
  • Параметры в URI (/callback?next=https://evil.com)
  • Подстановку другого домена в path.
  • Каждую мутацию - в Repeater.
Ответ 302 с вашим URI в заголовке Location означает: валидация обойдена.

Для автоматизации: Burp Intruder с payload-списком из 20-30 мутаций redirect_uri, throttle 1 request/sec (чтобы не триггерить rate limiting на IdP).

Перехват Authorization Code​

Authorization code - одноразовый, живёт секунды. Но если перехватить его до обмена на token легитимным клиентом - атакующий получает доступ от имени жертвы. Помимо redirect URI manipulation есть менее очевидные каналы утечки.

Утечка через Referer Header​

Механизм, который русскоязычные источники почти не разбирают на уровне HTTP-запросов. После получения authorization code браузер перенаправляется на redirect_uri с code в query string: https://app.example.com/callback?code=AUTH_CODE&state=xxx. Если на странице callback загружаются внешние ресурсы - скрипт аналитики, шрифт с CDN, пиксель трекинга - браузер отправит полный URL включая ?code=AUTH_CODE в заголовке Referer при запросе к третьей стороне.

Согласно IETF Security BCP (раздел 4.2 «Credential Leakage via Referer Headers»), это документированная угроза с конкретной мерой защиты: установить Referrer-Policy: no-referrer на callback-странице.

Как проверять?
  1. Проходим полный OAuth flow через Burp.
  2. В HTTP History фильтруем запросы к third-party доменам, отправленные со страницы callback. Если в заголовке Referer присутствует ?code= - вектор подтверждён.
Разработчики регулярно забывают об этом: ставят PKCE, внедряют BFF, но оставляют Google Analytics JS на callback-странице. Я видел это на трёх независимых проектах - и каждый раз удивлялся, как тщательно выстроенная архитектура рушилась из-за одного скрипта.

Прерывание OAuth Flow через XSS​

Более сложная техника, подробно описанная в Habr-статье об authorization code injection. Суть: атакующий, имея XSS в том же origin, что и redirect_uri, открывает скрытый iframe с authorization request. Если у жертвы есть активная сессия на IdP - авторизация проходит silent (без взаимодействия). Code возвращается в iframe, и JavaScript атакующего читает его (same-origin policy позволяет доступ к содержимому).

Ключевой момент: HttpOnly cookie на session ID не защищают. Атакующий не крадёт существующую сессию - он инициирует новый OAuth flow и получает свежий code. Как описано в OAuth 2.0 for Browser-Based Applications: «атакующий получает свои собственные учётные данные», не компрометируя существующие.

Когда работает:
  • XSS в любом месте origin приложения (не обязательно на странице callback)
  • response_mode не form_post (form_post отправляет code в теле POST, недоступном клиентскому JS)
  • У жертвы есть активная сессия на IdP
Когда НЕ работает:
  • IdP требует повторного consent при каждом запросе (prompt=consent)
  • Используется form_post response mode
  • CSP запрещает iframe с доменом IdP
Применимость: внешний пентест веб-приложений с OAuth/OIDC. Требуется предварительная XSS в origin - это отдельный вектор, который нужно найти до эксплуатации OAuth flow.

OAuth Token Theft: implicit flow и 307 redirect​

Implicit flow ([URL='https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics']response_type=token[/URL]) возвращает access token прямо во fragment URL: https://app.example.com/callback#access_token=eyJ.... IETF Security BCP прямо рекомендует отказаться от implicit flow. Но на пентестах он встречается до сих пор - в legacy SPA, мобильных WebView и приложениях, собранных по устаревшим туториалам с 2017 года.

Если в authorization request вы видите response_type=token - это уязвимость в контексте современных рекомендаций. Фрагмент URI не отправляется серверу (не попадает в логи), но доступен через JavaScript. Для blue team это слепая зона: токен утёк, а в серверных логах тишина. Связка с XSS даёт прямой ATO без необходимости обмена code на token.

Отдельно стоит вспомнить инцидент с GitHub, Heroku и Travis CI, описанный исследователями Descope: атакующие похитили OAuth-токены из интеграций Heroku и Travis CI, использовали их для доступа к приватным GitHub-репозиториям и далее обнаружили AWS-credentials, что дало доступ к инфраструктуре npm. OAuth token theft - не маловероятная проблема, а путь к lateral movement через trust chain между сервисами.

307 Redirect Exploit. Менее известный вектор (IETF BCP, раздел 4.12). Если авторизационный сервер возвращает HTTP 307 вместо 302 после аутентификации, браузер пересылает тело POST-запроса (включая credentials пользователя) на redirect_uri клиента. Применимо к self-hosted IdP (Keycloak, кастомные реализации) - проверяйте HTTP status code в Burp после отправки формы логина на IdP. Разница между 302 и 307 - одна цифра, а последствия принципиально разные.

PKCE Bypass и CSRF через отсутствие state​

Посмотреть вложение 83547

PKCE Downgrade Attack​

PKCE (RFC 7636) связывает code_verifier с code_challenge - без verifier перехваченный code бесполезен. Но если авторизационный сервер не обязывает PKCE - атакующий отправляет authorization request без code_challenge. Сервер принимает запрос, code выдаётся без привязки к verifier. Это и есть PKCE downgrade, описанный в чек-листе Xakep и IETF BCP раздел 4.8.

Проверка: отправляем authorization request без параметров code_challenge и code_challenge_method. Если сервер возвращает 302 с code - downgrade возможен. Из /.well-known/openid-configuration поле code_challenge_methods_supported: если отсутствует - PKCE скорее всего опционален.

Тут есть нюанс, который часто упускают. PKCE для confidential clients (серверных приложений с client_secret) - не панацея. Как детально разобрано в Habr-статье: при XSS в origin клиента атакующий контролирует и challenge, и verifier, потому что он сам инициирует flow. PKCE защищает от перехвата чужого code третьей стороной, но не от создания нового flow из контекста XSS.

CSRF в OAuth через отсутствие state​

Параметр state - одноразовый токен, привязанный к сессии пользователя. Без него атакующий подставляет свой authorization code в callback-URL жертвы, и приложение привязывает аккаунт жертвы к аккаунту атакующего на IdP. Классическая CSRF-атака на OAuth, описанная в IETF BCP раздел 4.7.

Проверка: удаляем state из authorization request или подставляем произвольное значение. Если приложение принимает callback без валидации state - CSRF подтверждена. В OpenID Connect аналогичную роль выполняет nonce, но проверяйте оба параметра.

Выбор вектора: decision tree для пентестера​

УсловиеТехникаИнструмент
redirect_uri принимает нестандартные значенияRedirect URI manipulationBurp Repeater + Intruder
Wildcard в redirect_uri + open redirect в приложенииOpen redirect chainBurp, paramspider для поиска open redirect
XSS в origin приложения, response_mode не form_postПрерывание flow + code theft через iframeJS payload в XSS
response_type=token (implicit flow)Token extraction через fragmentXSS payload, Referer leakage
PKCE не обязателен на ASPKCE downgrade + code interceptionBurp Repeater (удалить code_challenge)
Внешние ресурсы на callback-страницеReferer header leakageBurp HTTP History, фильтр Referer
IdP отвечает 307 вместо 302307 redirect credential leakBurp, проверка status code
Параметр state отсутствует или не валидируетсяCSRF - привязка аккаунта атакующегоCrafted callback URL
Клиент работает с несколькими IdPMix-Up Attack - подмена issuerПодмена authorization server в ответе

Порядок тестирования: recon (/.well-known/openid-configuration) - redirect URI fuzzing - проверка PKCE enforcement - Referer leakage - state validation - XSS-зависимые векторы.

Что видит WAF при атаках на OAuth 2.0​

OPSEC при тестировании OAuth - отдельная история. Где ваши действия оставляют следы:

IdP-логи. Каждый authorization request логируется. Keycloak пишет все authorization events, Azure AD фиксирует consent grants. Массовый перебор redirect_uri создаёт аномалию. Рекомендация: throttle в Intruder 1 req/sec, не более 30 мутаций за сессию.

WAF. ModSecurity и cloud WAF (Cloudflare, AWS WAF) ловят URL-encoded path traversal (%2e%2e%2f) в query parameters. Чистые URL-мутации (подмена path, добавление параметров) обычно не триггерят правила - WAF видит валидный HTTP-запрос к легитимному домену.

Приложение. Большинство приложений не логируют значение redirect_uri из authorization response - это и делает redirect URI manipulation тихим вектором. Implicit flow ещё тише: фрагмент URI (#access_token=...) не доходит до сервера вообще, в логах пусто.

Слепая зона blue team. Основная проблема - корреляция между authorization event на IdP и token usage на resource server. Без SIEM-правила, связывающего эти события, token theft через redirect manipulation остаётся невидимым.

За два с лишним года тестирования OAuth-интеграций в продуктовых компаниях я наблюдаю один и тот же паттерн: команды внедряют OAuth по документации провайдера (Auth0, Keycloak, Azure AD), но не проверяют поведение при нестандартном input. Документация описывает happy path. Задача пентестера - проверить unhappy path: что будет, если redirect_uri содержит ../, если code_challenge отсутствует, если response_mode подменён на fragment вместо form_post.

Самый недооценённый вектор из всех описанных - Referer leakage. Разработчики внедряют PKCE, строят BFF-архитектуру, ставят HttpOnly на cookies - и при этом оставляют analytics-скрипт на callback-странице, через который authorization code утекает в заголовке Referer. Ни один WAF не среагировал, потому что Referer - штатный механизм HTTP, а не аномалия.

PKCE подаётся как решение всех проблем OAuth-безопасности. В реальности PKCE закрывает ровно один сценарий: перехват чужого authorization code. Он не защищает от XSS в origin клиента, где атакующий инициирует собственный flow и контролирует оба компонента - и challenge, и verifier. BFF тоже не серебряная пуля: session ID, выданный серверной частью после code exchange, перехватывается тем же JavaScript из iframe - HttpOnly cookie не помеха, потому что атакующий читает Set-Cookie из заголовка ответа на свой же fetch-запрос. На HackerLab.pro в web-категории есть задачи, где OAuth redirect bypass нужно довести до рабочего ATO - на живом стенде каждый шаг ощущается иначе, чем в writeup.

После решения таких сложных задач я обычно открываю Мелстрой Гейм чтобы просто переключить внимание и отдохнуть.
Хороший разбор. Я столкнулся с похожим на одном аудите и у меня возник технический вопрос по поводу поведения самого Keycloak.

Вы тестировали этот обход путей на дефолтных настройках или там в панели админа в поле разрешенных адресов была прописана обычная звездочка? Просто в относительно свежих версиях они закрутили гайки со строгим соответствием строк и мне интересно, получилось ли обойти именно стандартную валидацию.
 
Последнее редактирование:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab