Давид Мзареулян (david_m) wrote,
Давид Мзареулян
david_m

Category:

Правильная постоянная авторизация

Сейчас весь интернет увлечённо обсуждает, как правильно хранить пароли. Дело хорошее:) Но я недавно заинтересовался похожей темой и обнаружил, что по ней куда меньше материалов. Тема эта — постоянная авторизация или, проще говоря, функция «Запомнить меня» при входе на сайт.

Всё нижеописанное основано на двух статьях, популярных в англонете, но почему-то в рунете никак не освещённых — это “Persistent Login Cookie Best Practice” (2004) и “Improved Persistent Login Cookie Best Practice” (2006).

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

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

Правильный способ — хранить в куке только длинную случайную строку-токен. Эта строка выдаётся юзеру при авторизации и одновременно записывается в базу сайта вместе с идентификатором юзера. Таким образом, по пришедшему токену сайт может определить, какому юзеру этот токен был выдан, в то же время сам по себе по себе токен не содержит никакой информации кроме «шума».

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

Ещё один момент. Все говорят, что хранить в базе пароли без хэширования плохо, но почему-то почти никто не говорит того же самого про авторизационные токены. Между тем, с точки зрения безопасности токены практически эквивалентны паролям. Украв (незаметно) базу токенов, злоумышленник сможет войти на сайт под видом любого из пользователей. Поэтому токены и любые подобные им данные необходимо хэшировать при помещении в базу. Хорошая новость состоит в том, что тут, вероятно, не обязательно использовать «тяжёлые» алгоритмы типа bcrypt — токены сами по себе являются длинными случайными строками, и их хэши невозможно взломать брутфорсом, даже если хэш-функция быстрая. Поэтому достаточно использовать любую хэш-функцию, достаточно защищённую от коллизий, например, sha256.

Ну и не лишним будет напомнить, что авторизационные куки должны быть httpOnly — для защиты от XSS.

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

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

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

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

В статье “Improved Persistent Login Cookie Best Practice” предложен красивый способ борьбы с этой проблемой. Дадим юзеру не один, а два токена, в разных куках. Первый работает так как было описано выше, а вот второй мы менять не будем, а наоборот, запишем его в куку с максимальным временем жизни и будем продлевать её при каждом визите. Назовём этот постоянный токен «серией» — потому что, как будет видно, он определяет серию «коротких» токенов. Итак, у юзера есть токен (сменный) и серия (долгоживущая). В базе они тоже будут храниться вместе: Token, Series → UserID.

UPD 16/06/2012 Замечу, что серия тоже в первый раз выдаётся при авторизации юзера, соответственно, один юзер может иметь несколько серий — по числу устройств/браузеров, с которых он заходит на сайт.


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

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

UPD 16/06/2012 При этом серия также удаляется из базы и при следующей авторизации выпускается заново. Иначе злой хакер со своей валидной серией и невалидным токеном сможет постоянно сбрасывать авторизацию у юзера стой же серией.


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

Однако тут есть одна проблема. Предположим, юзер отправляет одновременно несколько запросов к сайту (это может быть и несколько окон с одним сайтом, и ajax, и какие-то динамические ресурсы на странице). Первый обработанный запрос вызовет перевыпуск токена, а вот следующий запрос (напомню, он послан ещё со старым токеном!) сайт сочтёт запросом с украденной кукой и сбросит все авторизации серии. То же самое случится, если у юзера медленный или ненадёжный канал, и послав запрос на сайт он по какой-то причине не получит ответа с новой кукой. Нажав на «релоад», юзер пошлёт запрос со старым токеном, который сайт уже успел инвалидировать — и опять получит сброс авторизации. Поскольку существует модуль Друпала, реализующий эту схему, многие пользователи наблюдали такое поведение на практике (http://drupal.org/node/327263).

Для борьбы с этой проблемой следует считать токены валидными какое-то время после их перевыпуска. Например, если на сервере используется memcached, то при смене токена можно сохранять старый (вместе с серией и userId) в memcache на какое-то малое время (вероятно, единицы минут). Тогда при проверке куки мы сначала смотрим в memcache, и если там есть нужная комбинация — считаем токен ещё валидным. Если нет — то смотрим в базе и далее действуем штатным образом. При этом нужно снова перевыпускать токен на случай если юзер не получил перевыпущенный в прошлый раз.

Мне кажется, это хороший метод, всяко более разумный, чем простое хранение токена в куке с 10-летним expires. А какие методы постоянной авторизации знаете/используете вы?
Tags: cookies, persistent login, webdev, авторизация
Subscribe

  • Анимация APNG с помощью canvas

    APNG — это такое расширение формата PNG, позволяющее делать анимацию. APNG лучше анимированного GIF-а примерно тем же, чем PNG лучше GIF-а простого —…

  • (no subject)

    Три года назад я в своём ЖЖ задавался вопросом, как избавить сайт от необходимости знания паролей пользователей. Хотелось не хранить на сервере (а…

  • Как получить число ссылок на страницу в разных соцсетях

    Число ссылок/упоминаний/лайков — это то, что показывается рядом с кнопочками “Tweet!”, “Like!”, “Buzz!” и пр. Иногда хочется их получать независимо…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 26 comments

  • Анимация APNG с помощью canvas

    APNG — это такое расширение формата PNG, позволяющее делать анимацию. APNG лучше анимированного GIF-а примерно тем же, чем PNG лучше GIF-а простого —…

  • (no subject)

    Три года назад я в своём ЖЖ задавался вопросом, как избавить сайт от необходимости знания паролей пользователей. Хотелось не хранить на сервере (а…

  • Как получить число ссылок на страницу в разных соцсетях

    Число ссылок/упоминаний/лайков — это то, что показывается рядом с кнопочками “Tweet!”, “Like!”, “Buzz!” и пр. Иногда хочется их получать независимо…