У нас, разработчиков, часто есть токены доступа и другая конфиденциальная информация, проходящая через наши приложения. Токены доступа необходимы, чтобы мы могли использовать API от имени наших пользователей, и токены должны где-то храниться. В одностраничных приложениях есть соблазн хранить токены доступа прямо в браузере. Это удобно, поскольку позволяет легко перехватывать обращения API и добавлять токен в заголовок авторизации. Но вот проблема: основные поставщики удостоверений прямо предостерегают от хранения токенов доступа в браузере, как это делает OWASP и авторы спецификации OAuth 2.0 Best Current Practices.

Не рекомендуется хранить конфиденциальную информацию в локальном хранилище. - Шпаргалка по OWASP

Не храните токены в локальном хранилище. - Auth0: Где хранить токены

Вы в безопасности от CSRF, но вы открыли себя для гораздо большего вектора атаки… XSS. Okta: JWTs - отстой

Не храните [JWT] в локальном хранилище (или хранилище сеансов). LogRocket: Лучшие практики аутентификации JWT

Лучше не позволять коду JavaScript когда-либо видеть токен доступа. OAuth 2.0 для браузерных приложений: передовая практика

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

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

  1. Пожалуйста, прекратите использовать локальное хранилище
  2. Безопасно ли хранить jwt в localStorage с помощью reactjs?
  3. Почему JWT - отстойные токены сеанса

В первой статье есть несколько замечательных комментариев, которые обсуждают, допустимо ли хранение конфиденциальной информации в браузере. Я не хочу, чтобы раздел комментариев в этой статье превратился в дискуссию - я не собираюсь возражать! - поэтому, пожалуйста, перейдите к статье Рэндалла Деггеса (1), если вы хотите принять участие в обсуждении.

В любом случае, хотя я согласен с OWASP, Auth0, Okta и остальными, я думаю, что в статьях отсутствуют конкретные примеры векторов атак и моделей угроз. Они также не могут предоставить разработчикам альтернативный подход. В статьях четко говорится, что разработчикам нельзя хранить токены доступа в браузере, но они не дают особых советов о том, где хранить токены при использовании одностраничных приложений с бессессионными API.

В этой статье я приведу несколько причин, по которым токены доступа не должны попадать в браузер, и сделаю это, описав несколько примеров векторов атаки. Я также рассмотрю некоторые распространенные контраргументы (т. Е. Аргументы в пользу хранения токенов доступа в браузере). В следующей статье я дам конкретную стратегию использования авторизации на основе токенов с одностраничным приложением и бессессионным сервером API.

Проблемы с хранением токенов в браузере

Есть две проблемы с хранением токенов в браузере. Во-первых, токены должны быть переданы с сервера аутентификации в приложение, и эта передача может быть перехвачена различными способами. Возможные механизмы перехвата включают:

  1. Контроллер входящего трафика или балансировщик нагрузки с завершением TLS или мостом SSL, который находится перед сервером аутентификации. Администраторы на стороне сервера аутентификации, имеющие доступ к такому устройству, могут обнюхивать токены по сети.
  2. Законные пользователи корпоративной сети, которая отслеживает трафик HTTPS с помощью прокси-сервера и «доверенных» сертификатов на рабочих станциях домена. В этом сценарии корпоративные администраторы имеют доступ к сетевому трафику между одностраничным приложением и сервером аутентификации.
  3. Проверка истории браузера, если токены отправлены в URL-адресе, например при использовании неявного типа предоставления OAuth. В этом сценарии сетевые администраторы могут иметь доступ к истории браузера рабочих станций в своей сети. Возможны и другие векторы атак, такие как украденные мобильные устройства и общие рабочие станции.

Короче говоря, любой «человек посередине», имеющий доступ к сетевому трафику с открытым текстом, может получить доступ к токенам, а в некоторых случаях это может сделать любой, у кого есть доступ к истории браузера законного пользователя.

Вторая проблема заключается в том, что когда токен сохраняется в браузере, он обычно доступен для JavaScript. Это проблематично из-за межсайтового скриптинга (XSS). Если злоумышленник использует уязвимость XSS в SPA, он, вероятно, сможет получить доступ к токенам пользователей. Вот несколько распространенных способов использования XSS.

  1. Приложение выводит управляемый пользователем текст, который не очищен должным образом. Например, пользователь вводит тег <script> как часть своего общедоступного профиля, и этот сценарий выполняется, когда его просматривает другой пользователь.
  2. Программа загрузки изображений неправильно обрабатывает загруженные изображения. Например, пользователь загружает изображение аватара SVG, содержащее вредоносный код JavaScript. Когда этот аватар просматривается законными пользователями, запускается вредоносный сценарий.
  3. Сторонний сценарий в приложении загружается с помощью CDN, и этот сценарий является вредоносным. Хотя в этом случае может помочь целостность субресурсов, ее не всегда можно использовать (например, с динамической аналитикой, пикселями отслеживания и скриптами рекламных кампаний).

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

Утечка токенов может быть большой проблемой

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

Предположим на мгновение, что злоумышленник успешно запускает XSS-атаку на ваше приложение и получает доступ в качестве одного из ваших пользователей. Конечно, этот злоумышленник может затем использовать ваше приложение для доступа к API от имени скомпрометированного пользователя. Но если вы храните токены доступа в своем приложении и злоумышленник получает один, то, как минимум, злоумышленник может использовать токен в пределах своей области действия. Объем токена, безусловно, может превышать то, что используется вашим приложением.

В качестве примера представьте, что у вас есть приложение, которое читает пользовательские репозитории GitHub. Он никоим образом не записывает и не изменяет репозитории, а только читает их. Поэтому вашему приложению нужен токен доступа GitHub с областью repo. Разумеется, область действия токенов GitHub - это, а область repo предоставляет доступ как для чтения, так и для записи (невозможно получить токен с доступом только для чтения). Если злоумышленник получает доступ к вашему приложению, он может читать репозитории от имени скомпрометированного пользователя. Но если злоумышленник получает токен доступа, он также может писать в репозитории скомпрометированного пользователя. Это явно намного хуже.

И это еще не все. Учитывая токен, в зависимости от вашей системы могут быть возможны другие атаки типа аутентификации:

  1. Воспроизведите атаки против вашего приложения. Если вы используете OpenID Connect (OIDC) или OAuth, то, получив токен, злоумышленник может создать фиктивный ответ аутентификации, вставить его в адресную строку своего браузера и, возможно, установить личность вашего приложения как пользователя, отличного от саму себя. Параметр nonce в спецификации OIDC используется для смягчения этого вектора атаки, но обход проверки nonce в SPA тривиален.
  2. Воспроизвести атаки на сервер аутентификации, например, используя токен в качестве параметра id_token_hint. В зависимости от сервера аутентификации это может позволить злоумышленнику неограниченно продлевать токены от имени пользователя, тем самым делая спорным вопрос о коротком истечении срока действия токенов.
  3. Сохранение входа пользователя в систему на неопределенный срок путем злонамеренного использования тихой аутентификации. (Подробнее о тихой аутентификации ниже.)

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

Хранение токенов в памяти

Auth0 предполагает, что если вам абсолютно необходимо хранить токены в браузере - а они решительно направляют своих пользователей против этого - токены должны храниться исключительно в памяти. Это означает помещать токены в локальную переменную синглтона какого-либо вида и никоим образом не сохранять их. Хотя это, возможно, немного более безопасно, чем постоянные токены, обычно это не помогает с угрозами XSS.

Вот в чем дело: если вы храните токены доступа в своем браузере, вы, вероятно, отправляете их вместе со своими запросами API. Ваш SPA, вероятно, перехватывает HTTP-запросы XML (XHR / AJAX) и добавляет токены доступа ваших пользователей в заголовок авторизации или что-то в этом роде. Злоумышленник, успешно воспользовавшийся XSS-уязвимостью, может просто исправить XMLHttpRequest.prototype.setRequestHeader или другие методы по мере необходимости.

const origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(key, val) {
  console.log('Intercepted setRequestHeader.');
  console.log(`${key} = ${val}`);
  origSetRequestHeader.call(this, key, val);
};

Я также отмечу, что поставщики удостоверений, такие как Auth0, которые поддерживают неявный поток OpenID Connect или поток кода с PKCE (ключ подтверждения для обмена кодом), часто предоставляют так называемую тихую аутентификацию. Его можно использовать для повторной аутентификации и эффективного обновления токена доступа пользователя без перенаправления пользовательского агента (браузера) на сервер аутентификации. Спецификация OpenID Connect запрещает токены обновления для общедоступных клиентов (клиентов, которые не имеют серверной части и не могут безопасно хранить секрет клиента), но тихая аутентификация обеспечивает механизм для получения нового токена доступа без перенаправления. Связь между пользовательским агентом и сервером аутентификации осуществляется в скрытом фрейме iframe или во всплывающем окне, а токены отправляются с сервера аутентификации пользовательскому агенту с помощью API обмена сообщениями HTML5. С технической точки зрения это делается с помощью Режима ответа на веб-сообщения. Если вы используете веб-сообщения - а если вы используете клиент Auth0 SPA, то вы это делаете неявно, - тогда успешная XSS-атака может просто добавить прослушиватель событий веб-сообщений и перехватить ответы токена.

Суть в том, что токены, хранящиеся в браузере, обычно подвержены XSS-атакам, даже если эти токены хранятся исключительно в памяти. Опять же, лучшая практика безопасности - полностью исключить токены доступа из браузера.

Неявный поток OpenID Connect / OAuth: утечки токенов

Кстати, при использовании неявного потока OIDC токены отправляются непосредственно с сервера аутентификации пользовательскому агенту во фрагменте URL. (Взгляните на Ответ успешной аутентификации в спецификации.) Это означает, что успешный злоумышленник XSS может украсть токены, проанализировав фрагмент URL-адреса после успешной аутентификации пользователя. И, как упоминалось выше, это также означает, что токены хранятся в истории браузера. В настоящее время история часто синхронизируется между несколькими устройствами, тем самым увеличивая площадь утечки токенов.

На самом деле у злоумышленника есть несколько возможностей украсть токены при использовании неявного потока (см. RFC6819). Таким образом, это считается небезопасным, и его следует избегать в пользу кода или гибридного потока. Если вам абсолютно необходимы токены доступа в браузере, лучше всего использовать поток кода с PKCE. Официальный проект спецификации текущих передовых практик OAuth 2.0 доступен на ietf.org.

Где хранить токены

Надеюсь, что приведенное выше дает вам веские причины не допускать попадания токенов доступа в браузер. В следующей статье дается несколько конкретных советов о том, как безопасно хранить токены доступа при использовании одностраничных приложений, сохраняя при этом ваш сервер API бессессионным и масштабируемым. В нем также говорится о смягчении других векторов атак, таких как подделка межсайтовых запросов (CSRF), и соблюдается ранее упомянутая спецификация лучших практик.

Пожалуйста, напишите мне, если у вас есть какие-либо вопросы или комментарии по поводу статьи, хотя я еще раз хотел бы попросить вас воздержаться от обсуждения того, следует ли хранить конфиденциальную информацию в браузерах. По поводу статьи Рэндалла Деггеса уже ведутся большие дебаты.

Нужен разработчик?

"Связаться!" Мы небольшая софтверная компания в Фолсоме, Калифорния, и будем рады вам помочь.

использованная литература

  1. Памятка по OWASP
  2. Auth0: Где хранить токены
  3. Okta: JWT отстой
  4. LogRocket: передовые методы аутентификации JWT
  5. OAuth 2.0 для браузерных приложений: передовая практика
  6. Почему JWT - отстойные токены сеанса
  7. Режим ответа на веб-сообщения OAuth 2.0
  8. Прекратить использование локального хранилища
  9. Неявный ответ аутентификации OpenID Connect
  10. Безопасно ли хранить jwt в localStorage с помощью reactjs?
  11. Модель угроз OAuth 2.0 и соображения безопасности
  12. Неявное предоставление OAuth 2.0