Почему не стоит использовать LocalStorage

Привет, Хабр! Представляю вашему вниманию перевод статьи "Please Stop Using Local Storage" автора Randall Degges.


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


Введение


image


Итак, localStorage — новая особенность HTML5, позволяющая хранить любую информацию в пользовательском браузере благодаря JavaScript. Это старый добрый JS-объект, в который можно добавлять и удалять пары ключ/значение. Давайте посмотрим на пример небольшого кода:


// Два варианта добавления данных
localStorage.userName = "Петя";
localStorage.setItem("favoriteColor", "чёрный");

// После добавления в localStorage, они будут там
// до тех пор, пока их явно не удалить
alert(`${localStorage.userName} предпочитает ${localStorage.favoriteColor} цвет.`);

// А теперь удалим данные из хранилища
localStorage.removeItem("userName");
localStorage.removeItem("favoriteColor");

Запустив этот код на тестовой HTML-странице, мы увидим в alert-окне фразу "Петя предпочитает чёрный цвет". Если же зайти в инструменты разработчика, предварительно закомментировав строки с удалением данных, то можно убедиться, что оба значения сохранились в локальном хранилище вашего браузера.


image


Теперь вас может заинтересовать следующий вопрос: есть ли способ использовать локальное хранилище так, чтобы сохраненные данные автоматически удалялись? К счастью, разработчики HTML5 позаботились об этом, добавив глобальный объект sessionStorage, работающий точно так же, как и localStorage, за исключением одного: все хранящиеся в нем данные удаляются, когда пользователь закрывает вкладку браузера.


Преимущества


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


Во-первых, это чистый JavaScript! Одна из неприятных вещей, касающихся cookies (которые, по сути, являются единственной реальной альтернативой локальному хранилищу) заключается в том, что они должны быть созданы сервером. Ужас, ведь работа с веб-серверами скучна и трудоёмка. Если вы создаете статичный сайт (например, SPA), то использование localStorage позволит ему работать без какого-либо бекенда. Это довольно мощная концепция и одна из основных причин, по которым такая практика популярна среди разработчиков.


Ещё одно достоинство заключается в том, что localStorage располагает как минимум 5 Мб для хранения данных (этот размер поддерживается всеми основными веб-браузерами), что на порядок больше, чем у cookie-файлов (~ 4 Кб). Это дает весомое преимущество, если есть необходимость кэшировать относительно большой объём данных приложения в браузере для последующего использования.


Недостатки


У localStorage очень простое API, многие разработчики даже не представляют, насколько оно простое. Рассмотрим подробнее:


  • Может содержать только строки, что делает его совершенно бесполезным, если речь идёт хоть о чем-то сложнее строк. Конечно, можно переводить все типы данных в строки, но это безобразное решение.
  • Оно синхронно. Это означает, что каждая операция, связанная с хранилищем, будет выполняться последовательно. Для сложных приложений это критично, поскольку может замедлить скорость его работы.
  • Его не могут использовать web workers. То есть, если вы создаете приложение, использующее преимущества фоновой обработки для производительности, расширение для Chrome или прочие подобные вещи, то локальным хранилищем воспользоваться, увы, не выйдет.
  • Ограничение размера хранимых данных (как выше было уже сказано, примерно 5 Мб). Это достаточно маленький лимит для приложений, которые должны хранить большой объём данных или нуждаются в возможности работы без подключения к интернету.
  • Любой JavaScript-код на странице имеет доступ к хранилищу, поскольку никакой защиты, увы, не предусмотрено. Об этом, самом главном недостатке, мы поговорим чуть позже.

Выходит, что localStorage является хорошим инструментом только при соблюдении ряда условий.


Безопасность


Дело вот в чем: большинство минусов локального хранилища незначительны. Но вопрос безопасности — решающий фактор, поэтому поговорим о нем более подробно.
Итак, localStorage НЕБЕЗОПАСЕН! Совсем! Каждый, кто использует его для хранения конфиденциальных данных, поступает неправильно.



Давайте разберемся, что понимается под конфиденциальными данными:
— Идентификаторы пользователей
— Идентификаторы сессий
— JWT (JSON Web Token)
— Персональная информация
— Информация о кредитной карте
— API-ключи
— Любая другая информация, которую вы бы не стали публиковать публично

LocalStorage разрабатывался не как безопасный механизм хранения данных в браузере, а как простое хранилище ключей и значений с целью облегчения создания небольших сайтов/веб-приложений. И все. 

Что, по вашему, самое опасное в мире? Верно! JavaScript.

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


На самом деле проблема заключается в межсайтовом скриптинге (XSS). Не хочу грузить вас подробным объяснением этой уязвимости, поэтому постараюсь объяснить вкратце:
 если хакер сможет запустить JavaScript-код на вашем сайте, то он запросто вытащит всю информацию из localStorage и отправит её на свой сервер, тем самым заполучив, например, данные о пользовательской сессии.
Вы можете возразить: "Да ну? Мой сайт безопасен. Никто не сможет запустить какой-либо скрипт на моем сайте".

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



Наверняка ваш сайт содержит скрипты, которые загружаются с других серверов. Cамыми распространенными вариантами являются ссылки на:
— Bootstrap
— jQuery

— Vue, React, Angular и прочие
— Google Analytics



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


<script src="https://awesomejslibrary.com/minified.js"></script>

Предположим, что awesomejslibrary.com подвергся атаке, и minifed.js скрипт был так же изменен. В этом случае появляется риск того, что скрипт соберет все данные из localStorage и отправит их на специально созданный для хранения украденной информации API. Выходит, что хакеры украли данные пользователя, при этом ни он, ни вы (как разработчик), не узнаете об этом. Плохой вариант.




Мы все частенько задумываемся над тем, что все js-скрипты нужно размещать локально у себя на сервере, однако на практике такое происходит редко. Во многих компаниях маркетологи могут напрямую вносить изменения на сайт через WYSIWYG-редакторы и прочие инструменты. Отсюда возникает вопрос: вы правда уверены, что нигде на вашем сайте не используется сторонний JS? Я отвечу за вас: нет. 

Поэтому, чтобы снизить риск утечки пользовательской информации, не храните конфиденциальные данные в localStorage.


Про токены


Хоть мне и кажется, что я достаточно убедительно объяснил, почему не стоит хранить конфиденциальные данные в локальном хранилище, отдельно стоит разъяснить ситуацию с JSON Web Token (JWT). 

Многие разработчики, которые хранят JWT в localStorage, не понимают, что это, по сути, то же самое, что и имя пользователя / пароль.


Если хакеры скопируют эти токены, то смогут отправлять запросы на ваш сервер, и вы об этом никогда не узнаете. Поэтому обращайтесь с ними так же, как с данными кредитной карты или паролем, а именно — не храните в localStorage, вопреки тысячам туториалов, видео на Youtube и даже курсам программирования в университетах. Это неправильно! Если кто-то советует вам хранить токены в локальном хранлище для аутентификации, покажите им эту статью.


Альтернативы


Итак, после того, как мы убедились, что localStorage — далеко не идеальное решение для хранения информации, время познакомиться с альтернативными вариантами.




Конфиденциальные данные


Для хранения таких данных единственным верным решением является сессия на стороне сервера. Алгоритм следующий:


  • Когда пользователь логинится на вашем сайте, нужно создать уникальный индентификатор сессии и сохранить в криптографически зашифрованный куки-файл. Если вы используйте какой-либо веб-фреймворк, просто загуглите “how to create a user session using cookies” и следуйте этому руководству.
  • Убедитесь, что у cookie-библиотеки, которую использует ваш фреймворк, в настройках включено “httpOnly”. Это сделает невозможным просмотр куки-файлов браузером, что необходимо для их безопасного использования на стороне сервера. Советую прочитать статью Джеффа Этвуда для получения дополнительной информации.
  • Кроме того нужно убедиться, что в настройках указано SameSite = strict (чтобы предотвратить CSRF-атаки), а также secure = true (чтобы гарантировать передачу куки только через зашифрованное соединение)
  • При запросе пользователем сайта, используйте его сессионный идентификатор (извлеченный из куки-файла) для получения информации об аккаунте. После чего можно свободно отправлять пользователю любые связанные с этой учетной записью конфиденциальные данные без повторной проверки идентификатора сессии (разумеется, если первая проверка была пройдена)

Эта простая и, что самое главное, безопасная модель. И, разумеется, с помощью нее можно масштабировать проект любого уровня.


Данные, отличные от строк


Если вам необходимо хранить информацию, которая не является конфиденциальной и которая представляет собой что-то сложнее строк, то лучшее решение для этого — IndexedDB. Это транзакционная система БД с низкоуровневым API, являющаяся хорошим вариантом для хранения различных данных (включая файлы/blobs) прямиком в браузере. Более подробную информацию можно получить из гайда от Google.


Оффлайн-данные


Для обеспечения работы приложения без подключения к интернету наилучшим решением будет связка IndexedDB с Cache API (является частью Service Workers), благодаря которой возможно кэширование всех необходимых для корректной работы ресурсов.

 Отличный туториал по использованию от Google — здесь.




Вывод


Надеюсь, теперь вы понимаете (понимаете ведь?), почему далеко не всегда стоит использовать localStorage.

 Если вам нужно хранить данные, которые являются публичными, не используются в высокопроизводительных приложениях, точно не займут более 5 Мб и состоят только из строк, то локальное хранилище станет хорошим инструментом для ваших целей.
Во всех остальных случая — не используйте локальное хранилище! Используйте альтернативные решения.


И пожалуйста, я вас просто умоляю, не храните информацию о сессии (вроде JSON Web Token) в localStorage. Это сделает ваш сайт уязвимым для многочисленных атак, которые навредят пользователям.


P.S. Для тех, кто задался вопросом, почему я не упомянул Content Sequiriy Policy (CSP) как способ защиты от XSS.
 Причина проста: это не поможет в ситуации, которую я описал. Даже если вы используете CSP для проверки всех сторонних доменов, откуда подключаете JavaScript, это не поможет, если сайт из белого списка будет взломан.


P.P.S. Subresource integrity, к сожалению, тоже не является решением проблемы. Для большинства маркетинговых инструментов, рекламных сетей и т.д. (которые являются наиболее распространёнными сторонними скриптами) subresource integrity почти никогда не используется, поскольку поставщики этих скриптов частенько их меняют для расширения функционала и прочих вещей.


Перевод статьи Please stop using Local Storage.
Опубликовано с разрешения автора.

Поделиться публикацией

Комментарии 42

    +12
    >localStorage располагает как минимум 5мб для хранения данных (этот размер присущ всем основным веб-браузерам), что на порядок больше, чем у cookie-файлов (~ 4кб).

    Вообще-то на 3 порядка
      +32
      cookies должны быть созданы сервером.

      Они могут быть созданы и клиентом.


      Недостатки

      Не работает в Сафари в порно режиме.


      Конечно, можно переводить все типы данных в строки, но это безобразное решение.

      Сериализация данных — вполне нормальное решение.


      если хакер сможет запустить JavaScript-код на вашем сайте, то

      Он сможет получить любую доступную пользователю приватную информацию, независимо от того используете ли вы localStorage или нет. И CSP конечно же не спасёт, ибо есть куча способов переслать себе данные через сам же атакуемый сайт.


      Наверняка ваш сайт содержит скрипты, которые загружаются с других серверов.

      Весьма необоснованное предположение. Те, кто пекутся о безопасности пользовательских данных и стабильности работы никакие сторонние скрипты не подключают.


      Никто не сможет запустить какой-либо скрипт на моем сайте". И вот тут загвоздка. В теории вы абсолютно правы, однако на деле этого фактически невозможно достичь.

      Можно. Если вы не знаете как — это не значит, что это невозможно.

        +9
        Браво! При прочтении хотелось прокоментировать именно эти же «тезисы» именно также.
          0
          Т.е. никто не печется о безопасности данных и стабильности работы. Потому что скрипты даже на входе (и внутри) ЛК почти всех веб клиент-банков, как минимум GA. Начиная от Сбербанка (и да, они считают, что это нормально).
            0

            Поэтому надо использовать браузерные плагины вроде uMatrix и печься о безопасности своих данных самостоятельно.

            +1
            Те, кто пекутся о безопасности пользовательских данных и стабильности работы никакие сторонние скрипты не подключают.
            Все дружно передаем привет Сбербанку.
              0

              автор не говорит, что "Никто не сможет запустить какой-либо скрипт на моем сайте". он как раз приводит это утверждение и говорит, что оно неверное.

              0

              Я где-то читал, что localStorage используют также для кеширования ресурсов. Получается вообще ничего хранить нельзя. Всё могут изменить и прописать зловред. Что там хранить тогда, CSS и шрифты?

                0

                Для CSS и шрифтов используйте сервис-воркер. В LS нельзя хранить вообще что-то представляющее ценность.

                0

                Согласен в том, что сид лучше хранить в печеньках с httpOnly. Остальное выглядит прохладно, если вы грузите чужой js это автоматом значит что уже ничего не спасёт, поменять форму логина, никто те запрещал

                  0
                  HttpOnly-куки подвержены CSRF-атаке, а SameSite=strict поддерживается, мягко говоря, не всеми браузерами.
                    0
                    Согласен. CSRF, очень не приятная вещь. Получается нужно использовать и токен в LS и одновременно Печенку с HttpOnly. Первое защитит от CSRF, второе от кражи через JS.
                  0

                  Сегодня Web-стек не может предоставить инструментов надлежащей доставки авторизованного контента. Список актуальных атак:


                  • подмена или модификация HTTP-траффика, в некоторых случаях и HTTPS.
                  • взлом сервера отдающего веб-интерфейс.
                  • подмена DNS.
                  • воровство аккаунта сервис провайдера для подмены трафика.
                    +3
                    Очень странное утверждение по поводу небезопасности localStorage. Можно подумать что IndexedDB и Cache API не подвержены XSS атакам. Можно было сказать проще — если у вас на сайте XSS — ваш сайт небезопасен. И никакие пляски с бубном вокруг cookie и IndexedDB его не спасут.
                      +3
                      Если у Вас XSS, то никакие данные не будут в безопасности. Хоть в localStorage они лежат, хоть по куке отдаются с сервера. Так что не стоит запрещать молотки, если кто-то постоянно себе по пальцам попадает.
                        0

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

                        +2
                        если хакер сможет запустить JavaScript-код на вашем сайте

                        То localStorage это наименьшая из ваших проблем с точки зрения безопасности, ибо код сможет вделать почти всё то, что и пользователь.

                          –2
                          У меня вопрос — зачем вообще хранить конфиденциальные данные в LocalStorage(да и на клиенте вообще кроме некоторых и то зашифрованных )?
                            –2
                            В том-то и дело, что так делать не нужно. Но я сам частенько натыкался на гайды, где те же JWT советуют хранить в localStorage
                              +1
                              В комментариях уже обсудили, что за некоторыми исключениями, нет разницы где хранить токены, в куках или в localStorage.
                              +1

                              Чтобы тот же JWT сохранялся между перезагрузками страницы, а не предлагал пользователю логиниться каждый раз как он F5 нажмёт.

                                –4

                                Разве куки будут сбрасываться при перезагрузке страницы?

                                  0

                                  Во-первых, как уже было сказано выше, Cookie — ~4kB т.е. 4k char. В среднем, я не использовал JWT токены длинее 2k символов, но это не показатель, и ваш JWT может быть и в 8kB.


                                  Во-вторых, cookie постоянно гоняются по сети, даже если вы этого не хотите (fetch и XMLHttpRequest не в счет, там по умолчанию false).

                              +6

                              Честно признаюсь, я не так чтобы очень разбираюсь в безопасности, но некоторые тезисы вызывают сомнения.


                              Может содержать только строки, что делает его совершенно бесполезным, если речь идёт хоть о чем-то сложнее строк. Конечно, можно переводить все типы данных в строки, но это безобразное решение.

                              «Протокол HTTP может передавать только строки, что делает его совершенно бесполезным, если речь идет хоть о чем-то сложнее строк.» Ок, HTTP, может быть, не самая прекрасная вещь на свете, но ведь вполне рабочая.


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

                              Хотелось бы пример, что такое можно делать с localStorage, чтобы оно тормозило, записать туда 5-меговый блоб? «Тормозит потому что не асинхронное» это не аргумент, должны быть какие-то цифры или хотя бы ссылки.


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

                              Писать и читать из расширения Chrome в localStorage вполне возможно, content scripts имеют доступ к хранилищу страницы. Другое дело что для хранения настроек самого расширения лучше взять chrome.storage.sync (а когда этого API не было, все всё хранили в localStorage background_page).
                              Но как это связано с веб-воркерами? И почему нельзя передать данные из хранилища в web worker, а он там пусть их в фоне обрабатывает?


                              Убедитесь, что у cookie-библиотеки, которую использует ваш фреймворк, в настройках включено “httpOnly”.

                              Разумно. Но при чем тут клиентский JS?

                                +6
                                Присоединюсь ко всеобщему недоумению от статьи.
                                Недостатки
                                Оно синхронно. Это означает, что каждая операция, связанная с хранилищем, будет выполняться последовательно. Для сложных приложений это критично, поскольку может замедлить скорость его работы.
                                АПИ не вызывает сетевых запросов, скорей всего не вызывается даже чтение с диска при каждом обращении. Звучит так, что человек хочет асинхронное обращение к полям объекта.
                                Одна из неприятных вещей, касающихся cookies (которые, по сути, являются единственной реальной альтернативой локальному хранилищу) заключается в том, что они должны быть созданы сервером. Ужас, ведь работа с веб-серверами скучна и трудоёмка.
                                Очень эмоционально и абсолютно неверно, как и почти вся статья.
                                  +2
                                  localStorage располагает как минимум 5 Мб для хранения данных (этот размер поддерживается всеми основными веб-браузерами), что на порядок больше, чем у cookie-файлов (~ 4 Кб)

                                  Занудство: на три порядка больше. Но я восхищаюсь темпами развития технологии, при которых прирост в 10 раз вообще ничего не значит, а переход от килобайтов к мегабайтам — это переход «на порядок». Предлагаю придумать новое русское выражение, которое бы передавало смысл фразы «на порядок больше» при сравнении килобайтов с мегабайтами.
                                    0

                                    На 1024-ичный порядок больше?

                                      +1

                                      на беспорядок больше ;)

                                        0
                                        килопорядок.
                                        0
                                        Http only куку чужой код не сможет угнать. Да, сможет ей воспользоватьсч на месте, но это будет атака по площадям, а не целевая
                                          0
                                          Хранение данных сессии в HttpOnly cookie имеет (кажется) только одно преимущество — сессию нельзя украсть средствами JS. Но можно использовать примерно такой трюк — давать каждому токену id, и выдавая токен ставить HMAC от этого id в HttpOnly cookie. Таким образом токен без этого HMAC в cookie будет бесполезен. А сам JWT при этом будет доступен только JS коду загруженному со своего домена, а значит выполнять роль CSRF-токена, если запросы делает только JavaScript.

                                          При такой схеме мы всё так же можем узнать пользователя без запроса к базе (что полезно в условиях микросервисной архитектуры). Также можно реализовать отзывание токена за счёт цепочки токенов и малого срока действия (несколько минут).

                                          С другой стороны это довольно сложная система, и, возможно, проще просто завести отдельный сервис аутентификации.
                                            0
                                            Проще весь токен в куку засунуть если невозможность угнать через JS так важна.
                                              0
                                              В таком случае будет нужен дополнительный CSRF-токен, плюс использование токена для вебсокетов\других доменов намного сложнее без доступа для JS.
                                                0

                                                Не факт, что токен поместится в куку :)

                                              0
                                              Если хакер сможет запустить JavaScript-код на вашем сайте, то он запросто вытащит всю информацию из localStorage
                                              Если хакер сможет запустить JavaScript-код на моём, он итак сможет вытащить всё что угодно да и вообще сделать всё что угодно. Server-only кука не спасёт — хакер просто пошлёт запрос к главной странице (куки будут успешно отсылаться, т. к. origin совпадает), возьмёт нужные хэши и далее будет посылать запросы к методам — уже и вместе с хэшами, и вместе с куками. При этом самих кук у него не будет, но они ему и не очень-то нужны.

                                              Конечно же, это только один из вариантов, можно придумать ещё 100500 вещей, что можно сделать с javascript. В общем обычные куки не выглядят сильно безопаснее даже с режимом http-only.

                                              Впрочем, какая-то разница всё-таки есть — если можно угнать именно саму сессию, то что с ней делать можно подумать потом, а с http-only куками надо сделать делать всё сразу, и для этого нужно знать структуру сайта уже до проведения атаки.
                                                –1
                                                Правильнее было бы назвать статью — «Как не стоит использовать LocalStorage» или «Для чего не стоит использовать LocalStorage»
                                                  –3
                                                  Оно синхронно. Это означает, что каждая операция, связанная с хранилищем, будет выполняться последовательно.

                                                  А что, обернуть код [хотя бы] в promise уже не судьба?
                                                    +2
                                                    А что даст оборачивание в promise синхронного кода?
                                                    +1
                                                    поскольку поставщики этих скриптов частенько их меняют для расширения функционала и прочих вещей

                                                    Все адекватные поставщики скриптов указывают версию скрипта. И если я поставил версию 2.3.456 с указанием Integrity, ссылка на неё должна оставаться неизменной. Тут больше вопрос использования адекватных библиотек.
                                                      0
                                                      Интэгрити поддерживается ещё не всеми браузерами, к сожалению.
                                                      0

                                                      Насколько я понял, localStorage нужно использовать, и отказаться от него не панацея. Панацея будет если защититься от XSS и CSRF атак..?

                                                        0
                                                        Разумеется, отказываться от его использования не стоит. Конфиденциальные данные можно хранить в куках для своего спокойствия, но лучше, разумеется, постараться защититься от всевозможных атаках, в частности XSS и CSRF.

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                      Самое читаемое