Вопрос или проблема
Я разрабатываю приложение на JavaScript, которое будет работать в веб-браузере, а также как псевдо-нативное мобильное приложение с помощью Apache Cordova. Оно взаимодействует через API с отдельным серверным сервисом.
В приложении требуется, чтобы пользователя запрашивали какую-то идентифицирующую информацию при каждом запуске, однако для удобства использования это не обязательно должны быть его полные имя пользователя и пароль. Ввод пароля, особенно на мобильных устройствах, будет слишком громоздким, чтобы это было приемлемо. Поэтому мы рассматриваем процедуру единовременной авторизации, при которой вводятся полные учетные данные, за которой следует этап “настройки”, на котором пользователь создаёт PIN для будущего доступа. В будущем это можно будет расширить, добавив возможность использования отпечатков пальцев или распознавания лиц для “разблокировки” приложения на поддерживаемых устройствах.
Мы также надеемся избежать использования cookies. Это позволило бы избежать проблем с CSRF, а также поддержка cookies в приложениях Cordova, похоже, либо отсутствует, либо ненадежна.
Мои первоначальные мысли по поводу реализации:
- Пользователь вводит действительную комбинацию имя пользователя/пароль на конечной точке
login/
API и получает в ответ “ID токен”. - Во время этапа “настройки” выбранный пользователем PIN используется для шифрования ID токена.
- Зашифрованный ID токен хранится в
LocalStorage
. - Создается вторичный запрос к конечной точке
authorise/
API с включением открытого ID токена. Если токен проходит проверку, приложению выдается второй токен. - Этот второй токен используется во всех последующих запросах к API, чтобы доказать, что пользователь является доверенным, имеет относительно короткий срок действия и не хранится каким-либо постоянным образом приложением.
- При возвращении в приложение позже пользователю нужно будет лишь ввести свой выбранный PIN.
- Он может быть использован для расшифровки сохраненного ID токена, который затем используется в запросе
authorise/
для генерации и возврата нового токена сеанса с коротким сроком действия.
В интернете много статей, предостерегающих против использования LocalStorage
для чего-либо чувствительного из-за его уязвимости в случае XSS-атак. Угроза в том, что токен в LocalStorage
может быть украден, в то время как тот же самый токен в httpOnly cookie не сможет. Стоит отметить, что в обоих случаях злонамеренный скрипт, работающий в приложении, может успешно инициировать мошеннические запросы к бэкенд API.
Я считаю, что угроза кражи ID токена с помощью XSS уменьшена тем, что он зашифрован под PIN пользователя, и значения как расшифрованного токена, так и самого PIN не хранятся и не используются за пределами запроса authorise/
.
Токен сеанса также подвержен риску кражи через XSS. Он хранится только в памяти, но, конечно, все еще доступен для JavaScript и, следовательно, для злонамеренного скрипта. Эти токены будут иметь короткие сроки действия, чтобы уменьшить эту угрозу. Не говоря уже о том, что мы приложим все усилия, чтобы защититься от XSS в первую очередь.
Я думаю, что изложенный выше подход звучит как безопасный способ реализации наших требований, но я не эксперт в области безопасности. Я упускаю какие-либо очевидные слабости? Это действительно звучит безопасно?
Хранение информации в localStorage вполне безопасно, но:
- Он не имеет срока действия. Данные в localStorage остаются там навсегда, что увеличивает период, в течение которого их можно утечь или получить. Для токенов сеанса это обычно не важно, поскольку вы можете истечь их на стороне сервера.
- Значения не шифруются на диске. Chrome шифрует значения cookie, но не шифрует значения localStorage. В Firefox разницы нет.
Во время этапа “настройки” выбранный пользователем PIN используется для шифрования ID токена.
Если PIN числовой и короткий, зашифрованный токен будет легко подвержен брутфорсу. Таким образом, это не защищает доступ, когда кто-то имеет зашифрованный токен, но не имеет PIN. Вы можете несколько смягчить это, используя медленную функцию вывода ключа.
Угроза в том, что токен в LocalStorage может быть украден, тогда как тот же самый токен в httpOnly cookie — нет.
Согласен с вами, что как только у злоумышленника есть XSS, ему больше не нужен ваш токен. Тем не менее, вы можете получить лучшее из обоих миров, используя как токен в localStorage, так и токен в HttpOnly cookie. Если каждый запрос требует обоих, это защищает одновременно от CSRF и украденных токенов сеанса через XSS.
но, конечно, все еще доступен для JavaScript и, следовательно, для злонамеренного скрипта
Я не уверен, что это правда. Приложение может иметь переменную с ограниченной областью, которая доступна, но не легко читается другим JavaScript.
но также как псевдо-нативное мобильное приложение
На мобильных устройствах вам определенно следует попытаться хранить свой токен в хранилище ключей устройства. Это самое безопасное решение и защищено операционной системой и аппаратным обеспечением телефона.
Ответ или решение
Хранение зашифрованных токенов в LocalStorage
При разработке веб-приложения на JavaScript, которое также будет работать как псевдонастильное мобильное приложение через Apache Cordova, необходимо учитывать вопросы безопасности, особенно в контексте хранения идентификационной информации и токенов. Вы хотите избежать использования cookies по причинам, связанным с CSRF, и выбрали LocalStorage для хранения зашифрованного токена. Давайте рассмотрим предложенный вами подход и его возможные уязвимости.
Общая структура вашего подхода
- Пользователь отправляет валидные данные для входа (логин и пароль) на API и получает «ID токен».
- На этапе «настройки» выбранный пользователем PIN используется для шифрования ID токена.
- Зашифрованный ID токен хранится в LocalStorage.
- Делается вторичный запрос к API на
authorise/
, включая незашифрованный ID токен. Если токен валиден, приложение получает второй токен. - Этот второй токен используется для всех последующих запросов к API, его срок действия относительно короткий, и он не хранится постоянно.
- При повторном запуске приложения пользователь вводит PIN для расшифровки хранения ID токена, который затем используется для нового запроса к API для получения новой сессии.
Уязвимости и предложения по улучшению
-
Уязвимость LocalStorage к XSS: Как вы отметили, LocalStorage подвержен атакам XSS. Если вредоносный скрипт выполнится на вашей странице, он может получить доступ к данным LocalStorage. Зашифровка токена под PIN mitigates работоспособность, но если PIN короткий и простой, его можно легко перебрать. Рекомендуется использовать медленные функции производной (например, Argon2) для повышения сложности декодирования.
-
Отсутствие автоматической истечения срока действия: Данные в LocalStorage не имеют срока действия. Чтобы повысить безопасность, можно установить на сервере срок действия для токена сессии и заставить пользователя повторно вводить свои учетные данные после завершения периода активности.
-
Оптимизация хранения токенов: Рассмотрите возможность использования как LocalStorage, так и HttpOnly cookie. Это позволит уменьшить риски как от XSS, так и от CSRF атак. Если на сервере будут проверяться оба токена, это созданет дополнительные слои безопасности.
-
Необходимость в строгих модулях безопасности: Так как ваше приложение будет доступно и на мобильных устройствах, хранение токенов в защищенном хранилище операционной системы (например, Keychain для iOS и Keystore для Android) является гораздо более надежным решением. Таким образом, чувствительная информация будет защищена на уровне ОС.
-
Кодовая безопасность: Уделяйте особое внимание защите вашего JavaScript-кода от возможных XSS атак. Это включает в себя использование Content Security Policy (CSP), регулярное обновление зависимостей и применения практик безопасного программирования.
Заключение
Вы предложили обоснованное решение для хранения токенов, но безопасность требует многоуровнего подхода. Обязательно учитывайте уязвимости, связанные с XSS и теми данными, которые хранятся, и используйте лучшие практики для защиты информации. Хранение токенов в защищенных частях приложения и на уровне устройства вместе с мерами предостережения от XSS поможет снизить риски и увеличить безопасность вашего приложения.