PHP: Хранение сессий в защищённых куках

    На некоторой стадии развития веб-проекта возникает одна из следующих ситуаций:

    • backend перестаёт помещаться на одном сервере и требуется хранилище сессий, общее для всех backend-серверов
    • по различным причинам перестаёт устраивать скорость работы встроенных файловых сессий

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

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

    Описание механизма


    Эта идея не нова и реализована во множестве фрэймворков и библиотек для различных языков программирования. Вот пара примеров:


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

    Большинство имеющихся реализаций работают следующим образом: записывают в какую-то куку строку, содержащую время истечения сессии, данные сессии и HMAC-подпись времени истечения и данных. При запросе клиента кука читается соответствующим обработчиком, затем проверяется подпись и сравнивается текущее время с временем истечения сессии. Если всё совпадает, обработчик возвращает данные сессии в приложение.

    Однако, шифрование куки в распространённых реализациях этого механизма отсутствует.

    Сравнение с классическим подходом


    В итоге, хранение сессий в куках имеет следующие достоинства:

    • Возрастает производительность веб-приложения, так как небольшая криптографическая операция дешевле сеанса сетевого обмена или доступа к диску для извлечения данных сессии.
    • Возрастает надёжность веб-приложения, так как оно не зависит от внешнего KV-хранилища. Даже если хранилище сессий обеспечено средствами отказоустойчивости, это не наделяет его абсолютной стабильностью: переключение требует времени, а часть проблем (такие как ухудшение сетевой связности между регионами) и вовсе неискоренимы. Зачастую же сессии и вовсе хранятся на единственном сервере, являющимся единой точкой отказа всего веб-приложения.
    • Экономия ресурсов. Не нужно больше хранить сессии, а значит от этого выиграют и владельцы маленьких сайтов, у которых сократится дисковая активность, и освободят несколько серверов владельцы крупных веб-проектов.

    Имеются и недостатки, куда же без них:

    • Возрастает объём данных, передаваемый клиентом
    • Имеется ограничение на размер данных в сессии, связанное с ограничениями на размер кук. Обычно это чуть меньше 4 КБ кодированных данных.
    • Клиент может откатить состояние сессии на любое выданное и подписанное ранее значение, криптоподпись которого ещё действительна в текущий момент времени.

    Реализации для PHP


    Когда я попытался отыскать что-то похожее для PHP, я с удивлением обнаружил, что не существует ни одной библиотеки, которая дотягивает до минимума требований:

    • Безопасность: отсутствие ошибок при использовании криптографии
    • Актуальная кодовая база: поддержка современных версий PHP, отсутствие deprecated-расширений в зависимостях (таких как mcrypt)
    • Наличие тестов: сессии — это один из фундаментальных механизмов, и в основе реального приложения нельзя использовать незрелый код

    Кроме этого считаю вовсе не лишним:

    • Возможность шифрования: открытое хранилище сессии на клиенте, читаемое клиентом, не всем подходит.
    • Максимально компактное представление данных — ради минимизации оверхеда и запаса ёмкости сессии
    • Встраиваемость через SessionHandlerInterface

    Реализации, которые я рассмотрел:
    Репозиторий Комментарий
    github.com/Coercive/Cookie Фактически не библиотека для работы с сессиями вовсе. Ставит шифрованную куку, не подписывая её.
    github.com/stevencorona/SessionHandlerCookie Ближе всего к требованиям, но всё же имеет значительные недостатки:
    • Потенциально уязвима к атакам по времени из-за прямого сравнения хэша с образцом
    • Нет шифрования
    • Нет тестов
    • Неэкономная упаковка куки
    • Время истечения куки не хранится со значением и не охвачено подписью. Это значит. что клиент, единожды получив данные в сессии, может воспроизводить их бесконечно.
    • Мелкие баги: read() после write() в рамках одного выполнения скрипта показывает не то, что записано и пр.

    github.com/mapkyca/Encrypted-Client-Side-Sessions

    Также я смотрел реализацию хранения сессий в куках в фрэймворке Slim версии 2.x, но там нет ни подписи, ни шифрования. О чём авторы сразу и предупреждают.

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

    После всех поисков я решил реализовать такую библиотеку самостоятельно.

    Собственная реализация


    Packagist: packagist.org/packages/snawoot/php-storageless-sessions
    Github: github.com/Snawoot/php-storageless-sessions
    Установка из composer: composer require snawoot/php-storageless-sessions

    Ключевые особенности:

    • Обязательное шифрование. Алгоритм и режим — любой симметричный шифр на выбор, доступный в OpenSSL. По умолчанию: AES-256-CTR.
    • HMAC-подпись куки любым хэш-алгоритмом на выбор из ассортимента криптографического расширения Hash. Он же используется для генерации производных ключей шифрования. По умолчанию: SHA-256.
    • Реализованы контрмеры против атак по времени
    • Помимо основного набора данных и времени истечения, подписью охвачен и ID сессии, что оставляет простор для связывания данных сессии с внешними данными.
    • Реализация представлена в виде класса, совместимого с SessionHandlerInterface, а значит её можно прозрачно использовать практически с любыми PHP-приложениями.
    • Минимальный оверхед хранения, привносимый шифрованием и подписью.

    Пара слов о выборе режима шифрования. При использовании блочных режимов шифрования (ECB, CBC) длина шифротекста незначительно возрастает. Это связано с тем, что длина исходного сообщения должна быть кратна размеру блока. Из-за обязательного паддинга прирост длины составляет от одного байта до размера блока шифра. То есть для AES — от 1 до 16 байт. При использовании потоковых режимов шифрования (OFB, CFB, CTR, ...) исходное сообщение не пропускается через блочный шифр, вместо этого блочный шифр используется для образования гамма-последовательности, и тогда длина шифротекста точно соответствует длине исходного сообщения, что лучше подходит для описываемой задачи.

    Примеры использования


    Небольшой скрипт, иллюстрирующий работу с этим хэндлером:

    <?php
    
    require_once("vendor/autoload.php");
    
    header('Content-Type: text/plain');
    
    $secret = '********************';
    
    $handler = new VladislavYarmak\StoragelessSession\CryptoCookieSessionHandler($secret);
    session_set_save_handler($handler, true);
    session_start();
    
    if ($_GET) {
        foreach ($_GET as $key => $value)
            $_SESSION[$key] = $value;
        echo "Updated session:";
    } else
        echo "Current session data:\n";
    
    var_dump($_SESSION);
    

    Пронаблюдать его работу, задавая разные значения сессии в строке запроса, можно по адресу: https://vm-0.com/sess.php.

    Пример интеграции в Symfony:

    framework:
        session:
            handler_id:  session.handler.cookie
    
    services:
        session.handler.cookie:
            class:     VladislavYarmak\StoragelessSession\CryptoCookieSessionHandler
            public:    true
            arguments:    ['reallylongsecretplease']
    


    В качестве реального демо я подключил этот хэндлер сессий к первому пришедшему на ум веб-приложению, которое использует сессии. Им оказалось DokuWiki: wiki.vm-0.com. На сайте работает регистрация и логин, а работу сессий можно наблюдать в куках.

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

    Подробнее
    Реклама
    Комментарии 107
    • –6
      Хоспаде… первое апреля же закончилось?
      • –8
        Мне тут кажется имеется множество уязвимостей.

        Например злоумышленник (З) может сам зарегистрироваться на сайте и будет примерно представлять содержимое шифрованной куки предположительно он может подобрать ключ к своей куки. Далее имея ключ и текст он может вычислить ключ для подписи.

        Бороться этим можно только достаточно часто менять ключи. Что приведет к коллизиям у клиента кука подписана одним ключом, а на сервере он уже сменен. значит надо постоянно слать новые куки, что прям будут напрягать интернет. + Мы не уходим от узкого горлышка нам нужно хранить ключи для каждого пользователя.

        Ну как то так.
        • +5

          На момент написания этого комментария алгоритм AES достаточно защищен от такой атаки.

        • +1

          Я понимаю, конечно, что много всего держать в сессии плохо, но как работать с ограничением 4КБ на 1 куку?
          Нет ли смысла в данном случае предусмотреть в том числе сохранение данных в SessionStorage?

          • +1
            Нет ли смысла в данном случае предусмотреть в том числе сохранение данных в SessionStorage?
            Нет, автор статьи сделал серверное решение, оно абсолютно прозрачно для клиента. Использование интерфейса Storage предполагает какую-то дополнительную логику на стороне клиента, а это из другой оперы.
          • +7

            То, что вы придумали обычно реализуют через JWT, для чего есть множество библиотек, в том числе на PHP.
            А ещё есть статьи где популярно объясняется почему реализация сессий на клиенте это плохая идея в общем и с использованием JWT в частности:
            https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid
            http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/


            Ваша реализация, судя по тому что я прочел по диагонали, идеологически похожа, так что вторая статья (и её follow-up) весьма применима и для вашего случая.

            • 0

              Отмечу, что перечисленные недостатки относятся исключительно к использованию JWT в качестве хранилища сессионных данных.

              • +1

                Вы правда читали статью по второй ссылке?


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

                • +3

                  Да, читал. В обоих статьях большая часть — бред и паника на пустом месте.

                  • +1

                    Тогда будьте добры, уточните что во второй статье относится исключительно к JWT и не релевантно данной статье.

                    • 0

                      Данной статье оно как раз очень даже релевантно. Оно не релевантно для использования JWT в тех задачах, для которых JWT создавался.

                      • 0
                        Читаем вторую статью. «The drawbacks:
                        • They take up more space
                        • You cannot invalidate individual JWT tokens
                        • Data goes stale
                        • Implementations are less battle-tested or non-existent»

                        Каждый пункт как-то отражен в данной статье, автор про это задумывался. Так что корректно было бы обсуждать каждый момент по существу, а не просто кидать ссылку.
                        • 0

                          Ссылку я кинул для того, чтобы не повторять то, что кто-то уже структурировал в виде статьи. Задумываться над этим мало, нужно иметь решение к вопросам. Но исходя из их сущности решения скорее всего быть не может в общем случае. Если считаете иначе — давайте обсуждать.


                          Опять таки, чтобы не повторяться, рекомендую прочесть саркастический ответ на часто встречаемые контр-аргументы (он упомянут в шапке той статьи, если пропустили): http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/

                          • +4
                            В статьях, на которые вы ссылались, какие-то люди пытаются попиариться на относительно модном JWT. А реальность такова, что есть pros и cons у JWT-like и server-side сессий. У обоих есть фунадаментальные достоинства и недостатки и есть дефекты реализаций/спецификаций.
                            Надо стремиться использовать наиболее подходящее решение в каждом случае и исправлять дефекты, но заявлять «JWT sucks», как авторы тех статей — неправильно.
                            • 0

                              Голословно — не правильно. А если аргументировано, указывая на фундаментальные ошибки и заблуждения, то вполне себе можно и нужно.

                    • +1
                      JWT или другой способ шифрованного хранения сессионных данных


                      В JWT, кстати, данные хранятся в открытом виде.
                      • +1

                        Не обязательно

                        • 0
                          Обязательно, согласно спецификации https://tools.ietf.org/html/rfc7519
                          • +1

                            Вы Abstract читали (первый параграф)?:


                            JSON Web Token (JWT) is a compact, URL-safe means of representing
                            claims to be transferred between two parties. The claims in a JWT
                            are encoded as a JSON object that is used as the payload of a JSON
                            Web Signature (JWS) structure or as the plaintext of a JSON Web
                            Encryption (JWE) structure, enabling the claims to be digitally
                            signed or integrity protected with a Message Authentication Code
                            (MAC) and/or encrypted.

                            Весьма однозначно сказано что содержимое вполне себе может быть зашифрованным.

                            • 0
                              Так, как на хабре минусовать самого себя? :-D

                              Я всегда думал, что JWE это самостоятельный тип токена, отличный от JWT, пардон.
                            • 0
                              The contents of the JOSE Header describe the cryptographic operations applied to the JWT Claims Set. If the JOSE Header is for a JWS, the JWT is represented as a JWS and the claims are digitally signed or MACed, with the JWT Claims Set being the JWS Payload. If the JOSE Header is for a JWE, the JWT is represented as a JWE and the claims are encrypted, with the JWT Claims Set being the plaintext encrypted by the JWE. A JWT may be enclosed in another JWE or JWS structure to create a Nested JWT, enabling nested signing and encryption to be performed.
                              https://tools.ietf.org/html/rfc7519#page-6
                    • +2
                      Первая статья — типа «Давайте не будем использовать HTTP — вон в нём сколько дыр уже нашли». Какой может быть ответ? Нет, спасибо, мы дырки-то позатыкаем, но использовать не бросим.
                    • +6
                      Возможно безопасно и надёжно хранить данные сессии в браузерной куке у самого пользователя, если заверить данные сессии криптографической подписью.

                      … только до тех пор, пока "данные сессии" — маленькие. В противном случае вы получаете гигантский оверхед на каждом запросе.

                      • 0

                        Как решается вопрос принудительной инвалидации сессии?


                        А вообще, по-моему, данный подход не является сессией в обычном смысле слова в веб-разработке. Сессия — хранение каких-то данных на стороне сервера такое, что результаты запросов клиента зависят от истории предыдущих запросов. Тут же каждый запрос получается самодостаточным, что-то вроде WebForms.

                        • 0

                          А WebForms-то тут при чем?..

                          • +1
                            Думаю, что это отсылка к ViewState, где используется механизм 1:1 — генерируется состояние, шифруется, подписывается, кладётся в форму.
                            • 0

                              Да, но ViewState работало в дополнение к сессиям, а не вместо них...

                            • 0

                              Может запамятовал, но вроде они отправляли с фронта на сервер всё состояние сессии, ну и получали его от сервера.

                              • 0

                                Они отправляли на сервер все состояние страницы. Сессия работала отдельно.

                                • –1

                                  Они отправляли состояние страницы, а не сессии. Если у пользователя открыты сразу две вкладки — эти понятия принципиально различаются.


                                  Кроме того, ViewState не сохраняется при переходах между страницами.

                                  • 0

                                    Может. Я особо не разбирался, как фротендер с ними только немного работал больше 10 лет назад.

                              • +1

                                А он никак не решается. Разве что костылем в виде хранилища отозванных сессий.

                                • –2

                                  В обычных сессиях решается удалением файла/записи сессии.

                              • 0
                                Добрый день!

                                Как и указано в недостатках,
                                Клиент может откатить состояние сессии на любое выданное и подписанное ранее значение, криптоподпись которого ещё действительна в текущий момент времени.
                                Однако здесь важно заметить: если, к примеру, стоит практическая задача разлогинить юзера, то инвалидация одной сессии сама по себе ничего не даёт. Нужно прервать все сессии. И здесь у классического подхода к хранению сессий тоже возникают проблемы, так как в таком случае нужно найти и удалить все сессии, которые принадлежат пользователю. То есть это значит нужно хранить и поддерживать соответствие пользователя списку сессий. Дальше — больше: если используются sticky-сессии, то тогда один сервер должен пошариться по множеству других, чтобы разлогинить юзера через удаление сессий.

                                Удаление сессий в любом случае непрактичный подход. Вместо этого в сессию записывают факт логина вместе со временем логина. А в профиле пользователя держат время последнего логаута. Если время логина сессии раньше времени последнего логаута — считать её неактивной. При таком подходе для логаута нужно всего лишь обновить одно значение в записи о пользователе.
                                • –2
                                  А в профиле пользователя держат время последнего логаута.

                                  И как, бишь, вы это время между разными серверами будете синхронизировать в случае sticky sessions?


                                  (и как вы будете решать задачу "разлогинь меня в этой сессии, не трогая остальные"?)

                                  • 0
                                    И как, бишь, вы это время между разными серверами будете синхронизировать в случае sticky sessions?
                                    Время логаута не нужно синхронизировать, оно в профиле пользователя
                                    (и как вы будете решать задачу «разлогинь меня в этой сессии, не трогая остальные»?)
                                    Если нужно предоставить пользователю возможность разлогинить любую сессию, на которую он покажет пальчиком, то и при том и том подходе нужно хранить список сессий. Если только ту, в которой пользователь прямо сейчас сидит, то у классического подхода тут преимущество. Обязательно пользуйтесь классическим подходом, если Ваша бизнес-задача — разлогинивать текущие сессии пользователей.
                                    • –1
                                      Время логаута не нужно синхронизировать, оно в профиле пользователя

                                      … который хранится где?


                                      Если нужно предоставить пользователю возможность разлогинить любую сессию, на которую он покажет пальчиком, то и при том и том подходе нужно хранить список сессий.

                                      После чего совершенно не понятно, зачем гонять данные сессии на клиента.

                                      • –1
                                        … который хранится где?
                                        Который хранится в базе, а не в сессии.
                                        После чего совершенно не понятно, зачем гонять данные сессии на клиента.
                                        В таком случае заведите ишью в репозиториях джанги и рельсов, потребуйте немедленно удалить непонятные бэкенды для сессий.
                                        • 0
                                          Который хранится в базе, а не в сессии.

                                          … которая становится той же самой единой точкой отказа. В чем разница-то?

                                          • 0
                                            База это база, а хранилище сессий — это хранилище сессий. Предложенный хандлер устраняет SPOF в виде хранилища сессий.
                                            • 0

                                              Так что мешает хранить сессии в той же БД, что и профили? Как была одна точка отказа, так и осталась.

                                              • 0
                                                Вы когда-либо хранили сессии в БД?
                                                • 0

                                                  Да.

                                                  • 0
                                                    Отлично. Интересно узнать ответы на два вопроса:
                                                    1. Как вы удаляли старые сессии?
                                                    2. Каков ваш план масштабирования пропускной способности базы на запись при росте числа сессий?
                                                    Напоминаю при этом, что подход с хранением сессий в куках сам по себе не полагается на базу, если приложение не использует базу вовсе, то и для хранения сессий она не нужна. Если вдруг стоит требование инвалидировать сессии, то решение возможно чтениями из базы, что, согласитесь, совсем другое дело.
                                                    • 0
                                                      Как вы удаляли старые сессии?

                                                      Регулярной задачей.


                                                      Каков ваш план масштабирования пропускной способности базы на запись при росте числа сессий?

                                                      Шардинг.


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

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

                              • 0
                                Чем вам Memcached не угодила?
                                • 0

                                  Автор статьи спросил бы у вас: Допустим, memcache упал/втупил/стал недоступен. У вас или лежит сайт, или все незалогинены.


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

                                  • –1
                                    Ведь в Memcached тоже есть реплики, кластер? А Redis ещё и имеет ряд недостатков для сессии, на пример session locking https://github.com/phpredis/phpredis/issues/37
                                    Я не использовал Redis в highload пректе и не могу лично от себя ничего сказать…
                                    • 0

                                      Да, в memcache прикрутили что-то для репликации и можно использовать несколько серверов memcached в качестве кластера.
                                      Но данные при перезагрузке ноды вы все равно потеряете.
                                      В highload redis отлично используется для хранения сессий, к нему обращаются php, python, go.


                                      По вашей ссылке это проблема не redis, а конкретного php extension.
                                      Там в обсуждении люди описали, как сделать свой handler с session locking.
                                      Я не программист, но лично мне кажется, что session locking нужно делать в приложении, а не внутри extension, так как это специфичная фича — каждый может видеть её реализацию по разному.

                                • –5
                                  Удивлён что ещё никто не запостил картинку с троллейбусом из буханки хлеба. Механизм cookie предназначен не для этого, с таким же приколом можно хранить сессию в localStorage. Только зачем? И кстати, вот пятой точкой чувствую что возможны баги при параллельных запросах. В такие моменты у пользователя будет возникать несколько копий сессии, каждая из которых может обработаться по-своему.
                                  • –4
                                    Минусов ляпнули — и сбежали. Хоть бы обосновали что-ли…
                                    • +1
                                      На самом деле на ваш вопрос «зачем» есть ответ в самом начале статьи:

                                      На некоторой стадии развития веб-проекта возникает одна из следующих ситуаций:

                                      backend перестаёт помещаться на одном сервере и требуется хранилище сессий, общее для всех backend-серверов
                                      по различным причинам перестаёт устраивать скорость работы встроенных файловых сессий

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


                                      Я вот никогда раньше и не задумывался, что можно так сессии хранить. Теперь обдумываю…
                                  • +1

                                    Замечу, что большие куки — это плохо. Несколько раз у разных подсистем (не php) ловили падения при большом объеме кук. Ловить такие ошибки на проде — всегда очень занимательная задача.

                                    • +1
                                      Так и не понял ради чего всё это сделано? Если хотите работать с куками, то пожалуйста, зачем тогда вообще дергать сессию (а уж шифровать или нет это уже зависит от нужд). Кроме того не обязательно дергать диск или сетевые протоколы для сессий, можно хранение и в памяти сделать (если так критично к скорости чтения сессий). Кроме того, если у вас SSD, то у скорость чтения с диска довольно большая.
                                      • +1

                                        Для того, чтобы перенести хранение сессий на сторону клиента без изменения механизма работы с ними.


                                        В памяти хранить сложно, если у вас несколько инстансов апп-сервера — нужен механизм репликации (причём мастер-мастер) памяти между серверами.

                                        • 0
                                          in memory nosql base
                                          И все таки проще и логичнее просто перейти на работу с куками без сессий. С учетом того, что во многих фрейморках почти всега используются классы обертки для работы с сессиями. Просто в них использовать данные из куков. Но как пример работы через сессий через куки — довольно хороший)
                                          • –1

                                            Начало поста:


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

                                            Однако, есть альтернатива этому подходу...

                                            Не факт, что проще и логичней, не очень хорошо и в классы-обёртки сессий фреймворков лезть, и переписывать код, если $_SESSION используется.

                                      • –1

                                        Синхронизация между устройствам? Вот вы зашли на сайт с рабочего компа, затем в транспорте с планшета (еще и с 3G), что будет?

                                        • +1

                                          Ну, это болезнь любых сессий вообще, а не только данной реализации.

                                          • 0
                                            Но если мы храним сессию и так у себя — то нам легко раздать её на все клиенты и таким образом засинхронить. А если использовать вот это извращение с куками — тогда надо у одного устройства сессию взять, у себя её сохранить, и на другое устройство отдать. Если такой синхрон будет происходить постоянно — то вся идея ломается на корню, ибо мы опять храним сессии у себя.
                                            • +7

                                              Если раздать ее на все клиенты и таким образом засинхронить — это будет уже не сессия, а профиль пользователя.

                                        • –3
                                          Спасибо за статью идея интересная но есть пару проблемных моментов.

                                          1. HMAC — это не электронная подпись. Поскольку строится на базе симметричного ключа и соответственно может быть подделана одной стороной без возможности это опровергнуть другой стороной. Классическая ЭП на ассиметричной криптографии этого не допускает, поскольку ЭП строится с помощью закрытого ключа который есть только у подписывающей стороны.

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

                                          Пример. Вася подключается в Web-клиенту Банка. К нему грузятся скрипты, содержащие ключ шифрования.
                                          Вася отключается, но ключ у него есть. Вася знает что у Зины на счету много денег. Он от имени Зины строит сессию (ему даже авторизовываться не надо) и совершает перевод.

                                          3. Вы скорее всего скажите, что сессия будет защищена TLS. Но раз так, тогда смысла в доп. шифровании нет.

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

                                            Вы пост-то вообще читали? Сторона тут только одна — сервер, который сначала генерирует куку, а потом ее же читает.

                                            • –3
                                              Что вы называете сессией?

                                              Если только некий id, тогда непонятно причем тут ограничения на 4кб в куку, если же в сессии присутствует состояние полное состояние клиента, то для того чтобы его нехранить на сервере его надо шифровать и оно должно шифроваться на клиенте.

                                              В статье схемы взаимодействия не хватает.
                                              • +1

                                                Веб-сессия пользователя, оно же сеанс — это понятие, объеденяющее активность пользователя по взаимодействию с веб-ресурсом с момента первого обращения к ресурсу и до прекращения взаимодействия ресурсом.


                                                Обычно под сессией понимают термин "состояние сеанса", означающий блок нетипизированных данных, хранимых где-нибудь в течении всего сеанса, и к которым имеет доступ только серверный код.


                                                Клиентскому же коду доступ к сессии не нужен — у него есть localStorage для тех же целей.

                                          • –2

                                            Я, например, генерировал id сессии хешем от юзерагента, ip, даты и секретного ключа.
                                            И не сохранял в куках, а просто генерировал при каждом запросе.

                                            • 0

                                              Даты чего?

                                              • 0

                                                Даты создания сессии

                                                • –1

                                                  Я не понимаю. Где вы храните дату создания сессии, если id сессии генерируется на ее основе?

                                                  • –3

                                                    Дату берем сегодняшнюю.

                                                    • +2
                                                      а что будет в 23:59? Клиент разлогонится. Очень мило, можно еще сервер выключать. со словами пора спать
                                                      • +1

                                                        Отлично. Теперь любые два пользователя, которые сидят с одного IP и одного браузера в один день имеют общую сессию!


                                                        Вы что за сайты-то делаете, если ваша целевая аудитория, по всей видимости, бездетные незамужние люди, не сидящие за NATом?..

                                              • 0

                                                Браузер обновился — поменялся хэш. Надо брать не весь юзер агент, а из него платформу + ip + time zone,

                                                • 0

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

                                              • –3
                                                • 0
                                                  Добрый день!

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

                                                  Во-первых, IV это не публичный ключ, и хранить его отдельно от сообщения нет смысла.
                                                  Во-вторых, нет подписи куки, и я писал в статье, почему она должна быть.
                                                  • 0
                                                    Дело хозяйское.

                                                    1. Имеет, инвалидация же.
                                                    2. Шифрование же.
                                                    • 0
                                                      Вы свято верите, что кука в браузере протухнет по истечении времени и тогда данные инвалидируются. Однако, клиент сам управляет куками, они ему подконтрольны полностью. Вот в том числе поэтому нужно время экспайра и его подпись. Но не только поэтому, как я уже говорил.
                                                      • 0
                                                        Почитайте статью и гляньте код хотя бы прежде чем делать утверждения. Вектор используется для инвалидации без центрального хранилища.
                                                        • 0
                                                          Я почитал ваш код и утверждаю: куки подконтрольны клиенту, какие он проставит, такие и будут. Если клиент решит их хранить вечно — в вашем случае сессия будет длиться вечно.
                                                          • 0
                                                            Идентификатор — да, данные за ним — нет. Они не передаются клиенту, как и у файловой сессии, у которой есть собственный GC. Я, например, использую xcache с ненулевым временем жизни и вектор для ключа. Промах приводит к регенерации, никаких «вечно».
                                                        • 0
                                                          нет, если httpOnly
                                                          • 0
                                                            можно, МИТМом, локальным прокси например, если очень надо
                                                            • 0
                                                              как ві секьюрную куку митмом прочитаете?
                                                              • 0
                                                                Вышеуказанным методом. Например, org.littleshoot.proxy… Или что Вы хотели спросить?
                                                                • –1
                                                                  session.cookie_secure=True
                                                                  session.cookie_httponly=True
                                                                  


                                                                  ?
                                                                  • 0

                                                                    И что? Клиент-то ее получил и прочитал, значит, и обратно послать может. Все эти флажки "безопасности" — они для добронамеренных клиентов.

                                                                    • 0
                                                                      1. Куки с флагом HttpOnly не видны браузерному коду
                                                                      2. Куки с флагом Secure пересылается только через HTTPS (HTTP с использованием SSL — Secure Socket Level)

                                                                      Итак, каким образом вы организуете mitm — атаку, чтобы браузер юзера не выдал вашего вмешательства?
                                                                      • 0
                                                                        Куки с флагом HttpOnly не видны браузерному коду

                                                                        Это, простите, как? А отправляет их кто?


                                                                        Куки с флагом Secure пересылается только через HTTPS (HTTP с использованием SSL — Secure Socket Level)

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

                                                                        • 0
                                                                          Это, простите, как? А отправляет их кто?

                                                                          Это значит что их не видит js.
                                                                          Сам браузер их конечно шлет с каждым запросом, но js не может их не то что поменять, но даже считать.

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


                                                                          Да, всегда есть идиоты вводящие данные карты не пойми на каких сайтах. Но вменяемый человек обратит внимание, что чтото страничка его банкинга говорит о несекьюрном конекте и предлогает продолжить на свой страх и риск.
                                                                          • 0

                                                                            Так вот, клиенты браузерами не ограничиваются. Поэтому если у нас есть способ куку перехватить (а если его нет, то все обсуждения, в общем-то, на пустом месте), то мы всегда можем ее отправить и сделать подмену сессии.

                                                                            • 0
                                                                              1. Куки для API?
                                                                              2. Даже если куки для API, то они защищены ssl, а значит расшифровать перехваченный трафик вы всеравно не можете
                                                                              • 0
                                                                                Куки для API?

                                                                                Нет, для подконтрольного агента.


                                                                                Даже если куки для API, то они защищены ssl, а значит расшифровать перехваченный трафик вы всеравно не можете

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

                                                                                • 0

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

                                                                                  • 0

                                                                                    Для этого надо пользователя инвалидировать целиком.

                                                                                    • 0

                                                                                      Пользователя инвалидировать не проблема, проблема инвалидировать все его открытые сессии, если принято решение на каждый запрос не проверять не запрещён ли с момента последнего запроса вход пользователю.

                                                                                      • 0

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


                                                                                        Опять-таки, есть разница между инвалидацией куки и инвалидацией сессии.

                                                  • 0
                                                    Основная и ключевая проблема на мой взгляд — инвалидация данных сессии.
                                                    Да, единая точка отказа и бутылочное горлышко в виде редиса/мемкшеа есть, но уже существует множество решений ждя больших объемов данных
                                                    • 0
                                                      Автор пишет, что у него были проблемы с поиском текущих реализаций данного механизма на PHP.

                                                      Справедливости ради, стоит отметить, что в Laravel один из драйверов сессий, которые идут с фреймворком по умолчанию, предназначен именно для хранения данных сессии в куках.

                                                      https://laravel.com/docs/master/session
                                                      • 0
                                                        Посмотрел реализацию в ларавеле:
                                                        1. Громоздкая сериализация: ключи в жсон прям текстом пишутся, от всех полей в base64 паддинг ("===") остаётся, могли бы трансляцию в urlsafe base64 сделать, подпись прям в строковом виде. Можно было бы снизить расходы этой части раза в полтора точно.
                                                        2. Экспайр не охвачен подписью, то есть сессии — всегда вечные.

                                                        В остальном всё очень хорошо сделано.
                                                      • –1
                                                        С академической точки зрения интересно. Но если подумать, что такое сессия? Это некий ключ к состоянию. Хранить состояние на клиенте? Ну да, можно, если это простая корзина. Корзину даже шифровать не надо. user_id хранить даже в зашифрованном виде я бы не рискнул.

                                                        Состояние может быть очень сложным. Дополнительные сложности в отладке ради экономии 1 запроса, но платить за это жирнющей кукой — сомнительная выгода.
                                                        • 0

                                                          Сессия — не ключ к состоянию, а само состояние. Хранение состояния сессии на клиенте — ключ к созданию стейтлесс серверов, например в целях балансировки нагрузки и отказоустойчивости.

                                                          • 0
                                                            Хранение состояния сессии на клиенте — ключ к созданию стейтлесс серверов, например в целях балансировки нагрузки и отказоустойчивости.

                                                            … один из возможных.

                                                            • 0

                                                              Я не писал "единственный"

                                                        • 0
                                                          Тут ещё в личку вопрос поступила просьба рассмотреть вот этот модуль: https://github.com/psr7-sessions/storageless

                                                          На момент написания статьи я уже был с ним знаком, но не включил его в обзор по той причине, что он является не хандлером сессий, а middleware для сообщений PSR-7 (по сути аналог фильтра в UNIX-конвейере). То есть подходит не кому угодно, а тем, у кого всё приложение выстроено как обработчик сообщений PSR-7.

                                                          Если по этому критерию он подходит, то вот особенности данной реализации. Не вчитывался слишком вдумчиво, так как не работал с PSR-7, но обнаружил:
                                                          • Нет шифрования.
                                                          • Используется формат вебтокена JWT. Вместе с этим в качестве сериализатора всегда используется не один из встроенных сериализаторов сессий PHP, а всегда json_encode. Строго говоря, он может сериализовать не всё то же самое, что может сериализовать каждый из внутренних сериализаторов сессий PHP, нужно это учитывать. Кроме того, сама по себе кука получается размером ещё больше по сравнению со всеми остальными реализациями — оверхед максимальный.
                                                          • Исходя из использования JWT, помимо HMAC-подписи доступна ещё RSA-подпись. Это, наверное, плюс, но я не вижу юзкейса: подписывает и проверяет одна и та же сторона, она же имеет оба ключа. В таком случае намного оптимальнее отказаться от асимметричной криптографии для подписи в пользу HMAC — и по соображениям простоты, и по соображениям скорости.


                                                          Про всё это (плюс про то, что я писал в статье) и так в документации написано.

                                                          Одно место я так и не понял: https://github.com/psr7-sessions/storageless/blob/master/src/Storageless/Http/SessionMiddleware.php#L305-L311 — не знаю, означает ли это захардкоженный лимит времени жизни куки (и сессии) или нет.

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