На пентесте 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
/.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: обход валидации на практике
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом 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.
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-странице.Как проверять?
- Проходим полный OAuth flow через Burp.
- В HTTP History фильтруем запросы к third-party доменам, отправленные со страницы callback. Если в заголовке
Refererприсутствует?code=- вектор подтверждён.
Прерывание 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_postresponse mode - CSP запрещает iframe с доменом IdP
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
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 manipulation | Burp Repeater + Intruder |
| Wildcard в redirect_uri + open redirect в приложении | Open redirect chain | Burp, paramspider для поиска open redirect |
| XSS в origin приложения, response_mode не form_post | Прерывание flow + code theft через iframe | JS payload в XSS |
| response_type=token (implicit flow) | Token extraction через fragment | XSS payload, Referer leakage |
| PKCE не обязателен на AS | PKCE downgrade + code interception | Burp Repeater (удалить code_challenge) |
| Внешние ресурсы на callback-странице | Referer header leakage | Burp HTTP History, фильтр Referer |
| IdP отвечает 307 вместо 302 | 307 redirect credential leak | Burp, проверка status code |
| Параметр state отсутствует или не валидируется | CSRF - привязка аккаунта атакующего | Crafted callback URL |
| Клиент работает с несколькими IdP | Mix-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.
Последнее редактирование модератором: