Перейти к содержимому

Украли access-token: три уровня защиты в собственном openid-провайдере

Украли access-token: что делать дальше - три уровня защиты в собственном OpenID-провайдере

Bearer-токен устроен предельно просто: предъявить его может любой, у кого он оказался. Сервер видит корректную подпись, срок действия и набор разрешений, но обычно не способен понять, является ли отправитель настоящим владельцем токена или злоумышленником, который его скопировал.

Если access-token действует от 15 минут до часа, атакующий получает полноценное окно для работы. Он может обращаться к API из другой страны, с нового устройства и через другую сеть - для сервера такие запросы часто выглядят вполне легитимно. Сам факт кражи при этом может остаться незамеченным.

При создании собственного OAuth 2.1 / OpenID Connect-провайдера redb.Identity эту проблему пришлось решать комплексно. Защита строится не на одном механизме, а на трёх последовательных уровнях:

1. не допустить утечку токена;
2. сделать украденный токен малополезным;
3. обеспечить его быстрое аннулирование.

Откуда обычно крадут access-token

Наиболее очевидный сценарий - XSS. Если токен хранится в браузере, вредоносный JavaScript может получить к нему доступ. Но этим список угроз не ограничивается.

Опасность представляют:

- уязвимость в самом приложении;
- скомпрометированная npm-зависимость;
- расширение браузера с разрешением читать содержимое страниц;
- хранение токена в `localStorage`.

Особенно опасен последний вариант. Значение из `localStorage` доступно любому скрипту на том же origin, переживает перезагрузку и остаётся в браузере после закрытия вкладки. При успешной атаке злоумышленник забирает именно токен. Ему больше не нужны компьютер жертвы, её браузер, IP-адрес или активная сессия.

Первый уровень: токен не должен попадать в браузер

Многие административные панели identity-серверов исторически реализованы как SPA. Браузер запускает OAuth-клиент, проходит Authorization Code Flow с PKCE, получает access-token и хранит его в памяти приложения либо в браузерном хранилище.

Так устроены, например, популярные консоли крупных identity-платформ. Это не обязательно ошибка: для публичных клиентов и SPA такой подход долго считался нормальным. Однако у него есть серьёзный недостаток. Если в административной панели появляется XSS или вредоносная зависимость, атакующий получает не обычный пользовательский токен, а ключ к системе, которая управляет авторизацией всей инфраструктуры.

Как работает BFF

Более безопасная модель - Backend for Frontend. Между браузером и API появляется серверное приложение:

1. браузер инициирует вход;
2. сервер выполняет OIDC-обмен через back-channel;
3. access- и refresh-токены сохраняются на сервере;
4. браузеру выдаётся только сессионная cookie с флагами `HttpOnly`, `Secure` и подходящей политикой `SameSite`;
5. сервер самостоятельно обращается к API от имени пользователя.

JavaScript не получает access-token вообще. Это важнее любых соглашений и инструкций для разработчиков: секрет отсутствует в браузере технически.

В redb.Identity.Web административная панель и личный кабинет работают по похожей схеме на Blazor Server с cookie-аутентификацией. Токены помещаются в серверный билет аутентификации, а при обращении к Identity извлекаются серверным кодом из текущего контекста.

Промежуточные процессы также не передаются через `localStorage` или открытые query-параметры. MFA-челленджи, подтверждение согласия и состояние impersonation хранятся в отдельных защищённых HttpOnly-cookie, зашифрованных средствами Data Protection.

Blazor Server добавляет ещё один барьер: интерфейс и обработчики находятся на сервере, а клиент получает отображение и канал взаимодействия. Это сокращает объём чувствительной логики, исполняемой в браузере.

Ограничения BFF

BFF не устраняет все угрозы. Если скомпрометирован сам сервер или его окружение, злоумышленник сможет получить серверные сессионные данные и токены. Кроме того, cookie-аутентификация возвращает проблему CSRF.

Если браузер автоматически прикладывает cookie к запросу, вредоносный сайт может попытаться отправить запрос в приложение от имени пользователя. Поэтому нужны CSRF-токены, проверка `Origin` и `Referer`, корректные настройки `SameSite`, а для критических операций - повторное подтверждение личности.

BFF также не всегда удобен для мобильных приложений. Нативный клиент обычно должен самостоятельно проходить Authorization Code Flow с PKCE, хранить секреты в защищённом хранилище операционной системы и использовать дополнительные механизмы привязки токена.

Второй уровень: украденный токен должен потерять ценность

Даже если токен каким-то образом утёк, желательно не позволить использовать его бесконтрольно. Для этого применяют sender-constrained tokens - токены, связанные с конкретным криптографическим ключом клиента.

Один из практических вариантов - DPoP, или Demonstrating Proof of Possession. Клиент создаёт пару ключей и при каждом запросе подписывает специальное подтверждение владения закрытым ключом. В токене или сопутствующих данных фиксируется отпечаток открытого ключа.

Сервер проверяет сразу несколько условий:

- access-token действителен;
- подпись запроса корректна;
- запрос адресован нужному методу и URL;
- использован тот же ключ, к которому привязан токен;
- временная метка и идентификатор запроса не позволяют повторить старое подтверждение.

Украдённая строка access-token без закрытого ключа становится недостаточной для доступа. Атакующий может попытаться отправить её в API, но не сможет сформировать корректный DPoP-proof.

DPoP не заменяет защиту от XSS и не делает кражу безвредной во всех сценариях. Если злоумышленник выполняет код внутри самого приложения и получает возможность использовать закрытый ключ через скомпрометированный клиент, защита ослабевает. Но при типичной краже токена из логов, памяти или сетевого трафика привязка к ключу существенно снижает ущерб.

Третий уровень: быстрое отзывное действие

Наконец, система должна уметь быстро прекращать действие подозрительного токена. Простого ожидания окончания срока жизни недостаточно: даже короткое окно может позволить изменить настройки, выгрузить данные или создать нового администратора.

Для этого нужны:

- отзыв refresh-token;
- блокировка сессии;
- отзыв конкретного grant;
- принудительное завершение всех сессий пользователя;
- ротация refresh-token;
- проверка статуса токена для чувствительных операций;
- аудит действий и оповещения о необычной активности.

Короткоживущий access-token уменьшает ущерб, но не отменяет отзыв. Важно разделять эти понятия: срок действия отвечает на вопрос "когда токен перестанет работать сам", а отзыв - "как прекратить его действие прямо сейчас".

Полезно заранее определить процедуру реагирования. При подозрении на утечку нужно уметь найти связанные с токеном сессии, отозвать их, аннулировать refresh-token, проверить административные изменения и при необходимости сменить ключи.

Что делать с аномальной активностью

Сам по себе необычный IP не доказывает атаку: пользователь может путешествовать, применять VPN или менять сеть. Поэтому лучше оценивать совокупность признаков:

- резкая смена географии;
- параллельные запросы из удалённых регионов;
- необычный User-Agent;
- обращение к административным endpoint;
- всплеск числа запросов;
- операции, нехарактерные для конкретного пользователя.

Такие события должны попадать в журнал аудита. Для критичных действий можно требовать повторную MFA-проверку, даже если текущая сессия ещё действительна.

Если украли всю базу

Кража базы identity-сервера опаснее утечки одного access-token, но последствия зависят от архитектуры хранения данных.

Пароли должны храниться только в виде медленных адаптивных хэшей с индивидуальной солью. Подходящие алгоритмы - Argon2id, scrypt или bcrypt с параметрами, рассчитанными под конкретное оборудование. Простый быстрый SHA-256 для паролей неприемлем: при утечке базы его можно быстро перебирать на видеокартах.

Дополнительный pepper может храниться вне базы - например, в секретном хранилище или HSM. Тогда одного дампа недостаточно для проверки паролей. Однако pepper не отменяет необходимости качественного хэширования и не защищает уже выданные токены.

Ключи подписи и шифрования также не должны лежать в обычных таблицах. Цепочка доверия должна упираться в корневой ключ, который отсутствует в резервной копии базы и доступен только сервису с нужными правами.

При этом кража базы всё равно может раскрыть идентификаторы пользователей, метаданные клиентов, настройки redirect URI, сведения о сессиях и другие служебные данные. Поэтому необходимы минимальные права доступа, шифрование резервных копий, контроль выгрузок и регулярная проверка конфигурации.

Что важно закрепить на практике

Безопасность токенов - это не один флаг и не отдельная библиотека. Нужна последовательная модель:

- не хранить access-token в браузере без крайней необходимости;
- использовать BFF для веб-админок и личных кабинетов;
- защищать cookie от CSRF;
- применять PKCE для публичных клиентов;
- связывать токены с ключом через DPoP там, где это оправдано;
- ограничивать срок жизни access-token;
- ротировать и отзывать refresh-token;
- вести детальный аудит;
- хранить pepper и ключи отдельно от базы;
- регулярно проверять сценарий полной компрометации.

Главный вывод прост: украденный bearer-токен нельзя считать "почти бесполезным" по умолчанию. Пока он действителен, злоумышленник может выглядеть для сервера как законный клиент. Поэтому зрелый OpenID-провайдер должен одновременно предотвращать утечку, связывать токен с владельцем и иметь механизм немедленного отключения. Именно сочетание этих трёх уровней превращает отдельную кражу из катастрофы в контролируемый инцидент.

Прокрутить вверх