Предлагаем вашему вниманию перевод статьи из блога Eran Hammer — создателя фреймворка hapi.js. На этот раз речь пойдет об обеспечении безопасности идентификаторов сессий.



На Github прозвучал вопрос о том, зачем в Node.js-фреймворке Express к идентификационной cookie сессии добавляется хэш-суффикс? Отличный вопрос.

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

Брутфорс


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

Пароли — это не единственные учетные данные, использующиеся для веб-аутентификации. В наиболее распространенных реализациях используется страница логина, после успешного ввода данных на которой клиенту присваивается cookie-идентификатор сессии. Он служит «токеном предъявителя» — любой, кто «предъявит» этот токен, рассматривается системой в качестве прошедшего аутентификацию пользователя. Использование cookie-идентификатора избавляет от необходимости ввода логина и пароля на каждой странице. Однако теперь, этот id сессии является единственным ключом аутентификации, и любой, кто получит к нему доступ, сможет попасть в систему. В конце концов, cookie — это не более, чем строка символов.

Атаки подборка идентификаторов сессии — это разновидность брутфорс-атак. Вместо того, чтобы пытаться угадать пароль, атакующий подбирает идентификатор сессии. Злоумышленник генерирует идентификаторы сессии и отправляет с их помощью запросы в надежде на то, что какой-то из вариантов совпадет с реальным идентификатором активной сессии. Например, если в веб-приложении идентификаторы генерируются последовательно, то киберпреступник может просто использовать идентификатор собственной сессии и на его основе уже подбирать действующий id (предлагая системе «близлежащие» значения).

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

Нецелесообразность


Прежде всего необходимо сделать так, чтобы идентификаторы сессиий были достаточно длинными и выдавались не последовательно. Тут как с паролями — чем длиннее идентификатор, тем сложнее подобрать «валидный» с помощью перебора. Критически важно еще и генерировать такие id не с помощью предсказуемого алгоритма (например, со счетчиком), поскольку если такая логика присутствует, то атакующему уже даже не надо угадывать идентификаторы — он просто будет их генерировать по алгоритму. Использование для создания достаточно длинных id криптографически безопасного генератора случайных чисел — наилучший вариант. Что значит «достаточно длинных»? Это зависит от природы конкретной системы. Размер должен приводить к нецелесообразности усилий по подбору идентификатора сессии.

Другой способ защититься от угадывания сессионых id заключается в построении целостности токена с помощью добавления хэша или подписи для cookie сессии. В Express промежуточный софт, отвечающий за работу с сессиями, делает это вычисляя хэш для комбинации сессионного id и некого дополнения («секрета»). Поскольку для вычисления хэша в таком случае нужно знать секрет, то злоумышленник не сможет сгенерировать «валидный» идентификатор сессии без необходимости угадывания секрета (в противном случае ему просто предется перебирать хэши). Как и в случае сильных случайных id сессий, размер хэша должен соответствовать требованиям безопасности конкретного приложения. Не забываем, что сессионная cookie — это всего лишь строка символов, которые можно и подобрать.

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

Уровни защиты


Если в нашей системе генерируются сильные случайные идентификаторы сессий, нужен ли нам все равно хэш? Безусловно!

Ключевым принципом безопасности является наслоение. Еще его называют принципом «не класть все яйца в одну корзину». Если полагаться только на один источник безопасности, то в случае, если его удастся взломать, никакой безопасности не останется вообще. К примеру, что будет, если кто-то найдет ошибку в используемом генераторе случайных чисел? Что если затем им удастся взломать эту часть системы, и модифицировать ее? История знает множество примеров успешных атак, организаторы которых проделали именно это — генерировали случайные числа, которые на поверку оказывались совсем не такими случайными (мы писали о подобных уязвимостиях: раз и два).

Сочетание сильных случайных идентификаторов сессий с использованием хэширования для обеспечения целостности защищает от проблем в работе генератора случайных числе. Также этот метод защищает от ошибок, допущенных при разработке софта (например, использование неверной функции генератора — почти в каждой системе есть более и менее защищенные методы). Все так или иначе пишут плохой код вне зависимости от отлаженности процессов и опыта разработчиков. Это часть профессии. Вот почему важно строить разные уровни безопасности. Рва недостаточно, за ним нужна еще и стена, а на ней, вероятно, стоит разместить стражников.

Если вы думаете, что использование неверной функции генератора случайных числе или баг в пакете OpenSSL — это все, что нужно предусмотреть, подумайте о понятии monkey patch. Если кто-то на каком-либо из шагов развертывания приложения сделает что-то с глобальными случайными сущностями (например, для тестирования, логирования и т.п.) и сломает их (случайно или намеренно), то защита, опирающаяся только на случайность, просто перестает существовать.

Оповещение о проблемах


Важное отличие в подборе паролей и идентификаторов сессий заключается в том, что пароли ассоциированы с учетной записью (например, именем пользователя). Пара имя пользователя-пароль облегчает отслеживание брутфорс атак — увидеть, что кто-то много раз неверно ввел пароль для конкретной учетной записи, довольно легко. Однако, когда дело касается идентификаторов сессий, все не так просто — у сессий есть время действия и они не имеют дополнительного контекста, вроде имени пользователя. Это значит, что идентификатор может быть «невалидным» и в случае ее истечения, и при проведении атаки. Но без дополнительных данных (например, IP-адреса), понять, что на самом деле происходит, довольно трудно.

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

Гигиена безопасности


Аутентификационные данные должны иметь срок действия, поэтому у идентификатора сессии должен быть конечный срок жизни (его конкретная длина зависит от особенностей системы). У сookie есть срок истечения, однако не существует способа проверить его соблюдение. Злоумышленник может установить любое значение параметра экспирации cookie, и сервер никак не сможет об этом узнать. Хорошей практикой здесь является использование временной метки для любых аутентификационных данных — можно просто добавлять суффикс временной метки к случайно сгенерированному id сессии. Однако, чтобы в полной мере доверять таким меткам, нужно убедиться в том, что ее никто не изменял. Для этого и нужны подпись или хэш.

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

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

Аварийная кнопка


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

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

Общее предназначение


Важная функция промежуточного сессионного софта фреймворка Express — это поддержка user-generated идентификаторов сессий. Она позволяет разработчикам развертывать программное обеспечение в существующей среде, где сессионные идентификаторы генерируются некой существующей сущностью, которая может быть реализована на совсем другой платформе. Если не добавлять хэш к сгенерированному пользователем идентификатору сессии, то за построение безопасной системы будет отвечать не эксперт (разработчик конкретного модуля), а пользователь (новый подход к безопасности). Использование хэша — куда более верный подход, чем применение внутреннего генератора идентификаторов сессий.

Нужны ли крокодилы


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

Другой распространенный способ — создание cookie с общей информацией о пользователей (имя, недавно просмотренные элементы и т.п.) в добавок к сессии, впоследствии к хэшу добавляется что-либо из этой cookie — так создается связь между активным состоянием пользователя и аутентификацией. «Имя пользователя» опять возвращается в игру.

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

Заключение


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

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

Комментарии (10)


  1. maximw
    10.07.2015 11:29
    +2

    Очень спорная статья.

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

    Или предложение строить токен на основе id (или как-то иначе, но не случайно) и подписывать его хешированием с «перцем». Использование «перца», не сказать что сильно плохая, но точно не хорошая практика. Для некоторых систем не предполагающих надежной защиты или сильно экономящей ресурсы (валидация неслучайного токена без БД), возможно, это сгодится.

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


    1. NatalyM
      10.07.2015 11:40

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

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


    1. corvette
      10.07.2015 13:01

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


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


      1. maximw
        10.07.2015 14:32

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

        Про проблемы связанные с атаками по времени — нет ничего. А разделение хеша на две части решает вопрос сочетания хранения токенов в БД и использования поиска в БД, который разумеется не предназначен для защиты от атак по времени с алгоритмом сравнения, который соотв. предназначен.


  1. ArthurKushman
    10.07.2015 12:41

    Возможно я и ошибаюсь, а может что-то прочел не так детально как полагалось бы, но описанная в статье атака скорее называется не brute-force, а hijacking (или session hijacking) — ограбить сессию, дословно. Одна из наиболее распространненных атак 21 века.

    Brute-force же скорее, в простом понимании, является нагрузкой на сервер (как web, так и sql-server) перебором «сложных» и «проблемных» запросов. Например, можно подобрать, открытые в сети, запросы на карту, где собирается множество объектов и дергать их в циклах с разных тачек.


    1. vintage
      10.07.2015 15:54

      Нет, вы путаете brute force и DDOS.


  1. BeLove
    10.07.2015 15:56

    Жаль у автора в оригинале комментарии закрыты.


  1. SBKarr
    10.07.2015 16:06
    +1

    А у нас в системе используется два токена, первый в cookies, второй в строке запроса. Стоит упомянуть, что сайты работают как одностраничные морды, подключённые к API. Оба токена — SHA-512 хеши с солью, основанные на отпечатках браузера, с элементом случайности. Без обоих валидных ключей сервер даже пытаться запрашивать базу не будет. C HTTPS двойную систему можно обойти, оставив только один из хешей, тот, что в cookies. В основном, это сделано для упрощения жизни системам в стиле API-to-API.
    Сессия существует только 12 минут (почему-то так сложилось), с возможностью продления. При продлении можно выдавать новые токены, но это вызывает проблему с синхронизацией, если открыты несколько вкладок. Для долговременной авторизации (запоминания пароля), генерируется отдельный временный ключ, который наглухо привязан к пользователю (в отличии от сессии, которая просто является ключом к каким-то данным в базе), который тоже привязан к браузеру. Таких ключей может быть несколько.
    Меня всегда удивляло, почему подписи браузеров не используют для защиты сессий, это ведь очевидное решение. Можно делать сильный отпечаток, который может стать неверным при изменении настроек, или слабый, которому и обновление браузера — не помеха. Зависит от того, что защищаем, ключи от банка или любимые фоточки котиков.


    1. Chikey
      10.07.2015 23:25
      +2

      Обычно все эти «двух токенные» костыли имеют еще больше изъянов чем проверенные временем схемы. Почему не используют подписи? Потому что ее легко подделать, единственное чему более менее можно доверять это ip адрес. Ну и если кто то нашел XSS у него нет задачи украсть сессию, они просто делают что им нужно в том же контексте.


  1. VitaZheltyakov
    10.07.2015 16:36
    -3

    Это называется простым словом — ПАРАНОЙЯ.

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