Токен авторизации на примере JSON WEB Token

https://proglib.io/p/jwt-for-dummies/
Доброго времени суток, дорогой читатель. В данной статье я постараюсь рассказать об одном из самых популярных (на сегодняшний день) способов авторизации в различных клиент-серверных приложениях — токен авторизации. А рассматривать мы его будем на примере самой популярной реализации — JSON Web Token или JWT.
Введение
Начнем с того, что важно уметь различать следующие два понятия: аутентификации и авторизации. Именно с помощью этих терминов почти все клиент-серверные приложения основывают разделение прав доступа в своих сервисах.
Очень часто к процессу аутентификации примешивают и процесс индентификации — процесс, позволяющий определить что за пользователь в данный момент хочет пользоваться нашим приложением, например, путем ввода логина. Далее нам, как разработчикам и как ответственным людям хочется убедиться, что данный пользователь действительно тот за кого он себя выдает — и поэтому следует процесс аутентификации, когда пользователь подтверждает, что он тот самый %user_name%, например, путем ввода пароля.
Казалось бы, все что необходимо выполнить для безопасности нашего приложения мы сделали. Но нет, еще одним очень важным шагом в любом клиент-серверном приложении является разграничение прав, разрешение или запрет тех или иных действий данному конкретному аутентифицированному пользователю — процесс авторизации.
Еще раз кратко закрепим: сначала идет идентификация и аутентификация, процессы когда мы определяем и удостоверяемся, что за пользователь в данный момент использует наше приложение, а далее идет авторизация — процесс принятия решения о разрешенных данному пользователю действиях.
Еще одно небольшое введение
Прежде чем начать говорить о самом токене авторизации следует упомянуть для каких целей вообще его решили использовать. Поскольку мы знаем, что почти весь интернет так или иначе построен на протоколе HTTP(или его старшем брате HTTPS) и что он не отслеживает состояние, то есть при каждом запросе HTTP ничего не знает, что происходило до этого, он лишь передает запросы, то возникает следующая проблема: если аутентификация нашего пользователя происходит с помощью логина и пароля, то при любом следующем запросе наше приложение не будет знать все тот же ли этот человек, и поэтому придётся каждый раз заново логиниться. Решением данной проблемы является как раз наш токен, а конкретно его самая популярная реализация — JSON Web Tokens (JWT). Также помимо решения вопросов с аутентификацией токен решает и другую не менее важную проблему авторизации (разграничение разрешенных данному пользователю действий), о том каким образом мы узнаем ниже, когда начнем разбирать структуру токена.
Формальное определение
Приступим наконец к работе самого токена. Как я сказал ранее в качестве токенов наиболее часто рассматривают JSON Web Tokens (JWT) и хотя реализации бывают разные, но токены JWT превратились в некий стандарт, именно поэтому будем рассматривать именно на его примере.
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON.
Фактически это просто строка символов (закодированная и подписанная определенными алгоритмами) с некоторой структурой, содержащая полезные данные пользователя, например ID, имя, уровень доступа и так далее. И эта строчка передается клиентом приложению при каждом запросе, когда есть необходимость идентифицировать и понять кто прислал этот запрос.
Принцип работы
Рассмотрим принцип работы клиент серверных приложений, работающих с помощью JWT. Первым делом пользователь проходит аутентификацию, конечно же если не делал этого ранее и в этом есть необходимость, а именно, например, вводит свой логин и пароль. Далее приложение выдаст ему 2 токена: access token и refresh token (для чего нужен второй мы обсудим ниже, сейчас речь идет именно об access token). Пользователь тем или иным способом сохраняет его себе, например, в локальном хранилище или в хранилище сессий. Затем, когда пользователь делает запрос к API приложения он добавляет полученный ранее access token. И наконец наше приложение, получив данный запрос с токеном, проверяет что данный токен действительный (об этой проверке, опять же, ниже), вычитывает полезные данные, которые помогут идентифицировать пользователя и проверить, что он имеет право на запрашиваемые ресурсы. Таким нехитрым образом происходит основная логика работы с JSON Web Tokens.

https://habr.com/ru/post/336082/
Структура токена
Пришло время обсудить структуру токена и тем самым лучше разобраться в его работе. Первое что следует отметить, что JWT токен состоит из трех частей, разделенных через точку:
Полезные данные (playload)

funnytorimage.pw
Рассмотрим каждую часть по подробнее.
Заголовок
Это первая часть токена. Она служит прежде всего для хранения информации о токене, которая должна рассказать о том, как нам прочитать дальнейшие данные, передаваемые JWT. Заголовок представлен в виде JSON объекта, закодированного в Base64-URL Например:
Если раскодировать данную строку получим:
Заголовок содержит два главных поля: alg и typ. Поле typ служит для информации о типе токена, но как я уже упоминал ранее, что JWT превратился в некий стандарт, то это поле перестало нести особый смысл и служит скорее для целей будущего, если вдруг появится улучшенная версия алгоритма JWT(2.0), которая заменит JWT. Поле alg задает алгоритм шифрования. Обязательный для поддержки всеми реализациями является алгоритм HMAC с использованием SHA-256, или же, как он обозначен в заголовке, HS256. Для работы с этим алгоритмом нужен один секретный ключ, конкретный механизм работы рассмотрим ниже. Для справки можно также отметить, что существует и асимметричный алгоритм, который можно использовать в JWT, например, RS256. Для работы с ним требуется два ключа — открытый и закрытый. Но в данной статье рассмотрим работу с одним закрытым ключом.
Полезные данные
Перейдем наконец к полезным данным. Опять же — это JSON объект, который для удобства и безопасности передачи представляется строкой, закодированной в base64. Наглядный пример полезных данных (playload) токена может быть представлен следующей строкой:
Что в JSON формате представляет собой:
Именно здесь хранится вся полезная информация. Для данной части нет обязательных полей, из наиболее часто встречаемых можно отметить следующие:
iss — используется для указания приложения, из которого отправляется токен.
user_id — для идентификации пользователя в нашем приложении, кому принадлежит токен.
Одной из самых важных характеристик любого токена является время его жизни, которое может быть задано полем exp. По нему происходит проверка, актуален ли токен еще (что происходит, когда токен перестает быть актуальным можно узнать ниже). Как я уже упоминал, токен может помочь с проблемой авторизации, именно в полезных данных мы можем добавить свои поля, которые будут отражать возможности взаимодействия пользователя с нашим приложением. Например, мы можем добавить поле is_admin или же is_preferUser, где можем указать имеет ли пользователь права на те или иные действия, и при каждом новом запросе с легкостью проверять, не противоречат ли запрашиваемые действия с разрешенными. Ну а что же делать, если попробовать изменить токен и указать, например, что мы являемся администраторами, хотя таковыми никогда не были. Здесь мы плавно можем перейти к третьей и заключительной части нашего JWT.
Подпись
На данный момент мы поняли, что пока токен никак не защищен и не зашифрован, и любой может изменить его и тем самым нарушается вообще весь смысл аутентификации. Эту проблему призвана решить последняя часть токена — а именно сигнатура (подпись). Происходит следующее: наше приложение при прохождении пользователем процедуры подтверждения, что он тот за кого себя выдает, генерирует этот самый токен, определяет поля, которые нужны, записывает туда данные, которые характеризуют данного пользователя, а дальше с помощью заранее выбранного алгоритма (который отмечается в заголовке в поле alg токена), например HMAC-SHA256, и с помощью своего приватного ключа (или некой секретной фразы, которая находится только на серверах приложения) все данные токена подписываются. И затем сформированная подпись добавляется, также в формате base64, в конец токена. Таким образом наш итоговый токен представляет собой закодированную и подписанную строку. И далее при каждом новом запросе к API нашего приложения, сервер с помощью своего секретного ключа сможет проверить эту подпись и тем самым убедиться, что токен не был изменен. Эта проверка представляет собой похожую на подпись операцию, а именно, получив токен при новом запросе, он вынимает заголовок и полезные данные, затем подписывает их своим секретным ключом, и затем идет просто сравнение двух получившихся строк. Таким нехитрым способом, если не скомпроментировать секретный ключ, мы всегда можем знать, что перед нами все еще наш %user_name% с четко отведенными ему правами.
Время жизни токена и Refresh Token
Теперь плавно перейдем к следующему вопросу — времени жизни токена, и сопутствующей этой теме refresh token. Мы помним, что одно из важнейших свойств токена — это время его жизни. И оно совсем недолговечное, а именно 10-30 минут. Может возникнуть вопрос: а зачем такое короткое время жизни, ведь тогда придется каждый раз заново создавать новый токен, а это лишняя нагрузка на приложения. А ответ достаточно очевидный, который включает в себя и одновременно ответ на вопрос: а что делать если токен был перехвачен. Действительно, если токен был перехвачен, то это большая беда, так как злоумышленник получает доступ к приложению от имени нашего %user_name%, но так как access token является короткоживущим, то это происходит лишь на недолгий период. А дальше этот токен уже является не валидным. И именно чтобы обновить и получить новый access token нужен refresh token. Как мы знаем (или если забыли можем снова прочитать в начале) пользователь после процесса аутентификацию получает оба этих токена. И теперь по истечении времени жизни access token мы отсылаем в приложение refresh token и в ответ получаем снова два новых токена, опять же один многоразовый, но ограниченный по времени — токен доступа, а второй одноразовый, но долгоживущий — токен обновления. Время жизни refresh token вполне может измеряться месяцами, что достаточно для активного пользователя, но в случае если и этот токен окажется не валидным, то пользователю следует заново пройти идентификацию и аутентификацию, и он снова получит два токена. И весь механизм работы повторится.
Заключение
В данной статье я постарался подробно рассмотреть работу клиент-серверных приложений с токеном доступа, а конкретно на примере JSON Web Token (JWT). Еще раз хочется отметить с какой сравнительной легкостью, но в тоже время хорошей надежностью, токен позволяет решать проблемы аутентификации и авторизации, что и сделало его таким популярным. Спасибо за уделенное время.
Неприметные токены. Часть 1. Теория
В ходе тестирования на проникновение нередко удается получить доступ с правами уровня локального администратора к какому-то сетевому объекту, функционирующему под управлением операционной системы семейства Windows.
Следующим этапом, как правило, является повышение привилегий до администратора домена. Существует множество способов пост эксплуатации конечной системы при наличии к ней административного доступа. Наиболее перспективным методом представляется поиск учетных данных, таких как:
- пароли в открытом виде
- NT-хэши паролей
- TGT
- MsCache v2 хеши
- секреты в DPAPI и др.
Приведенный метод неплохо изучен и хорошо себя зарекомендовал, но всегда интересно расширить свой арсенал возможностей другими подходами. Одним из таких подходов является манипулирование токенами. В предстоящей серии материалов попробую разобраться что из себя представляют токены в Windows и как они могут пригодиться для повышения привилегий.
Первая часть содержит теорию необходимую для дальнейшего понимания материала.
Сессия пользователя#
Как обычно, основательно подойдем к изучению вопроса и начнем издалека. Прежде чем приступать к токенам необходимо разобраться с таким понятием, как сессия пользователя.
Бытовая аналогия#
Рассмотрим следующий пример — организация доступа на стадионы чемпионата мира по футболу 2018.
Поверхностно опишем процедуру прохода:
- Каждый, кому требовалось пройти на стадион, сначала приходил в один из нескольких аккредитационных центров, где предоставлял свой паспорт и обоснование для прохода.
- Сотрудник аккредитационного центра проверял личность предъявителя и его обоснование, тем самым аутентифицируя его. Если личность была болельщиком, то обоснованием являлся билет на матч, если репортером, то требовалось наличие соответствующей записи в заранее согласованной базе данных. Кроме того требовалось смотреть в базу, чтобы выявлять нежелательных посетителей, входящих в черный список (пранкеры, агрессивные фанаты). Отметим, что процедуру проверки в базе данных можно считать довольно ресурсозатратной.
- В результате успешной проверки посетителю стадиона выдавалась аккредитация. Таким образом осуществлялась авторизация.

Каждая аккредитация содержала следующую информацию:
- ФИО и фото владельца (замазаны березовым цветом)
- Перечень стадионов доступных для посещения
- Роль владельца (волонтер, обслуживающий персонал, сотрудник безопасности, журналист и т.д.)
- Перечень зон внутри стадиона, доступных для посещения (трибуны, раздевалки, подтрибунные помещения, микс зона и т.д.)
Каждый раз, когда посетитель стадиона пытался пройти в определенную зону, охрана осматривала его аккредитацию и принимала соответствующее решение о допуске. Аккредитация позволяла быстро идентифицировать человека, и понять какими полномочиями он обладает.
Также для простоты представим, что по выходу со стадиона аккредитация уничтожалась и в следующий раз процедура повторялась снова. На практике разумеется было по-другому, но для примера так лучше.
Нахождение посетителя на стадионе можно назвать сессией посетителя. Удобство заключается в том, что в рамках заданной сессии проверка личности осуществляется только один раз и нет необходимости при каждом действии заново требовать паспорт и сверять записи в базе данных.
Сессия в Windows#
Рассмотрим, что происходит при входе пользователя в Windows:
- Пользователь садится за компьютер и предоставляет системе свои аутентификационные данные (логин, пароль, название домена, smart карту или другие).
- Система передает полученную информацию в свой локальный центр безопасности (далее — LSA).
- LSA осуществляет аутентификацию пользователя.
Как именно это происходит выходит за рамки настоящего материала. Для желающих ознакомиться с тонкостями процедуры аутентификации ранее были написаны другие материалы, где рассматриваются принципы работы протокола Kerberos.
- В результате успешной аутентификации пользователю предоставляется сессия, позволяющая получать доступ к защищаемым объектам операционной системы при наличии соответствующих прав.
В Windows в качестве защищаемых объектов выступают совершенно различные объекты, например: файлы, именованные каналы, ключи реестра, процессы, потоки, сетевые папки, службы, устройства, объекты Active Directory и т.д.
Пользователю не требуется заново вводить свой пароль при каждом открытии файла или запуске прикладной программы. Это называется концепцией единого входа (Single Sign On — SSO), которая реализуется, в том числе с помощью механизма сессий.
В рамках сессии для получения доступа к защищаемому объекту пользователю необходимо обладать определенным контекстом безопасности.

Связь пользователя, сессии и контекста безопасности
Контекст безопасности представляет собой набор специальных атрибутов необходимых операционной системе для принятия решений о предоставлении пользователю доступа к защищаемому объекту или разрешении выполнить системную операцию.
Под системной операцией понимается действие, требующее наличия определенной привилегии для его выполнения, например выключение системы или изменение значения счетчика времени.
Важно отметить, что по сути контекст безопасности является своеобразной “аккредитацией” пользователя. Всякий раз, когда пользователь обращается к защищаемому объекту, система смотрит сессию пользователя и проверяет связанную с ней “аккредитацию”.
Процессы в Windows#
Пользователь использует систему для выполнения различных программ. Программа представляет собой некоторый код (набор команд и инструкций), который хранится в системе в виде файлов. Программа используется в рамках процесса.
Процесс — это объект операционной системы, содержащий набор ресурсов (в частности выделенный диапазон адресов виртуальной памяти) и данных используемых в ходе работы программы.
Уместно провести следующую аналогию:
- Программа — это рецепт в поваренной книге, то есть описание какого-то порядка действий, алгоритма.
- Процесс — это кухня, на которой работает повар, нарезаны ингредиенты, кипит кастрюля, где-то лежит листочек с рецептом, а также посуда и приборы необходимые для приготовления блюда.
Процессы позволяют системе изолировать работу программ. Таким образом исполняющая среда получается гораздо более надежной и стабильной, поскольку выход из строя одного процесса никак не сказывается на работе других процессов.
Токены в первом приближении#
Что происходит, когда пользователь открывает файл в текстовом редакторе?
Система должна определить обладает ли процесс программы, запущенной от имени определенного пользователя, необходимыми правами доступа. Внимательный читатель может предположить, что это несложно сделать с помощью контекста безопасности, связанного с сессией пользователя.
В целом, так и есть, но на практике реализовано немного сложнее. Пользователь работает с множеством программ, то есть процессов. Некоторым процессам может не требоваться наличия определенных прав пользователя, например небезопасно запускать браузер с правами на просмотр различных директорий, которыми в свою очередь может обладать проводник.
Если бы все процессы в рамках пользовательской сессии ссылались на один и тот же контекст безопасности, хранящийся в атрибутах указанной сессии, то изменение контекста безопасности одного процесса повлияло бы на контекст безопасности всех остальных процессов.
Рассмотренный пример, лишь один из многих иллюстрирующих, почему разработчики операционной системы Windows решили наделить каждый процесс своим собственным контекстом безопасности, который называется токеном доступа.
Первоначально токен создается в ходе авторизации пользователя, на одном из последних этапов входа в систему. Указанный токен присваивается первому процессу, созданному для работы от имени авторизованного пользователя.
Важно понимать, что процесс, не имеющий токена, не может существовать. В дальнейшем новые процессы по умолчанию наследуют токен доступа от родительского процесса.
Отметим, что создать токен “вручную” нельзя. Прямой доступ к отдельным атрибутам токенов из пользовательского режима невозможен. Это сделано, во избежание несанкционированного изменения полномочий пользователя путем редактирования токенов. Только программный код, выполняющийся в режиме ядра, может получить прямой доступ к атрибутам токена (равно как и к любым другим объектам операционной системы).
Чуть более подробно о локальном повышении привилегий в результате эксплуатации критической уязвимости с последующим выполнением кода в пространстве ядра и манипуляцией токеном доступа можно почитать здесь.
Таким образом между пользователем, его сессией, процессами и токенами имеется следующая связь:

Теперь в первом приближении можно ввести следующее определение:
Токен доступа пользователя — объект ядра ОС Windows, описывающий контекст безопасности процесса, работающего от имени указанного пользователя.
Синонимы: токен, токен доступа, маркер доступа, маркер безопасности.
Атрибуты токена#
Перечислим некоторые основные атрибуты, содержащиеся в токене доступа пользователя:
- Идентификатор пользователя
- Идентификатор входа в систему (Logon ID — LUID)
- Перечень локальных и доменных групп, в которые входит пользователь
- Список привилегий, которыми обладает пользователь

Пример отображения некоторых атрибутов токена
Более наглядно посмотреть токены и их атрибуты можно с помощью утилиты TokenViewer
Разумеется атрибутов гораздо больше. Для первого знакомства приведенного списка достаточно. Далее постепенно рассмотрим другие важные атрибуты, а также различные виды токенов доступа.
Уровни целостности#
Начиная с Windows Vista, в операционных системах семейства Windows реализован механизм мандатного контроля целостности (mandatory integrity control, MIC).
Согласно MIC в Windows каждому защищаемому объекту операционной системы присваивается мандатная метка, соответствующая определенному уровню целостности. Физически мандатная метка представляет собой целое число. Чем больше число, тем выше уровень целостности.
Уровень целостности процесса хранится в токене указанного процесса в списке групп:

В современных версиях Windows поддерживаются следующие уровни целостности:
| Числовой идентификатор | Название | Описание |
|---|---|---|
| 0 | Untrusted | Используется редко, например в ходе анонимного подключения. |
| 4096 | Low | Назначается процессам браузеров или контейнеров, чтобы ограничить права на запись в отношения системных файлов или ключей реестра. |
| 8192 | Medium | По умолчанию назначается большинству процессов прикладных программ. |
| 8448 | Medium Plus | Недокументирован. |
| 12288 | High | Назначается процессам прикладных программ администратором. |
| 16384 | System | Автоматически назначается всем системным процессам. Процессам прикладных программ назначаться не может. |
| 20480 | Protected Process | Назначается специальным защищаемыми процессам |
Основная идея мандатного контроля целостности заключается в том, что “низкоцелостные” субъекты не могут изменять “высокоцелостные” объекты.
Рассмотрим, как это используется на практике.
Токены привилегированных учетных записей#
Как отмечалось ранее, токен доступа создается в результате успешной аутентификации при взаимодействии с LSA. Важный момент заключается в том, что помимо прочего LSA выполняет проверку учетной записи на предмет членства в особо привилегированных группах, например:
- Builtin Administrators
- Domain / Enterprise Administrators
- Policy Administrators
- Account Operators
- Backup Operators
а также на предмет наличия следующих “опасных” привилегий:
- SeBackupPrivilege
- SeCreateTokenPrivilege
- SeDebugPrivilege
- SeImpersonatePrivilege
- SeLabelPrivilege
- SeLoadDriverPrivilege
- SeRestorePrivilege
- SeTakeOwnershipPrivilege
- SeTcbPrivilege
Если учетная запись пользователя состоит в одной из подобных групп или обладает хотя бы одной из перечисленных привилегий, то по умолчанию LSA создает два раздельных токена:
- Полный токен (в англ. “elevated token”), содержащий все назначенные права, группы, привилегии и имеющий высокий уровень целостности.
- Отфильтрованный токен, в частности обладающий следующими ограничениями:
- отсутствуют “опасные” привилегии
- присваивается средний уровень целостности
Сравнение логона администратора и обычного пользователя
Таким образом, полномочия процесса, выполняющегося на среднем уровне мандатной целостности от имени административной учетной записи, практически не отличаются от полномочий процесса, выполняющегося от имени непривилегированного пользователя. Это позволяет реализовать принцип минимизации полномочий без создания отдельных учетных записей для повседневной работы и для администрирования операционной системы.

Содержимое токена процесса администратора, запущенного в обычном режиме
Когда пользователю необходимо выполнить задачу, требующую повышенного токена доступа, Windows автоматически запрашивает подтверждение. Этот запрос называется запросом на повышение прав и реализуется с помощью специального компонента — UAC (User Account Control).

Пример оконных сообщений UAC

Содержимое токена процесса, запущенного от имени администратора
Более детальное описание механизма UAC выходит за рамки настоящего материала.
Виды логонов#
Вход в Windows можно осуществить по-разному. Для простоты можно выделить следующие два типа входа (строго говоря их больше):
- Интерактивный
- Сетевой (неинтерактивный)
Интерактивный вход подразумевался всюду ранее. По сути это обычный вход через графическую оболочку, который например осуществляют рядовые пользователи к своим домашним персональным компьютерам или администраторы к удаленным серверам с помощью RDP.
Но что происходит, когда пользователь пытается посмотреть содержимое удаленной сетевой папки, например с помощью следующей команды?

Просмотр содержимого сетевой папки
Пользователь не может просто взять и отправить по сети свой токен удаленной системе. Действительно, токен должен быть связан с какой-то определенной сессией. Таким образом, прежде чем посмотреть содержимое сетевой папки, пользователь должен осуществить вход на удаленную систему на которой располагается эта папка. Другими словами пользователь должен аутентифицироваться к удаленной системе.
Если сессия пользователя, в рамках которой запрашивается доступ к сетевой папке, создавалась с помощью интерактивного входа, то используемые для входа учетные данные пользователя были сохранены в LSASS. В этом случае система автоматически использует сохраненные учетные данные пользователя (NT хеш или TGT) для аутентификации на удаленной машине.
Это еще один из примеров, иллюстрирующий поддержку принципа единого входа в Windows.
Отметим, что между токеном процесса, работающего от имени определенного пользователя в рамках определенной сессии и учетными данными указанного пользователя имеется следующая связь:
Связь между токенами, различными видами сессий и учетными данными
Еще раз отдельно отметим:
- При интерактивном входе создается интерактивная сессия и учетные данные пользователя сохраняются в LSASS.
- При сетевой сессии пользователь подтверждает, что обладает учетными данными, но при этом никуда их не передает. В дальнейшем в LSASS указанные учетные данные не хранятся.
Теперь рассмотрим, что происходит с другой стороны в удаленной системе.
Сетевая папка по сути является сервисом, предоставляемым некоторым SMB сервером. Процесс “SMB сервера” обрабатывает запрос на аутентификацию и передает полученные учетные данные в LSA, где проверяется их подлинность. Самое интересное, что происходит в результате успешной аутентификации пользователя, запрашивающего просмотр содержимого сетевой папки.
Процесс “SMB сервера” работает от имени и с правами псевдопользователя SYSTEM. Предоставление прав системы всякому аутентифицированному к SMB серверу пользователю однозначно избыточно. Более того указанный пользователь может обладать доступом не ко всему содержимому сетевой папки.
То есть, процессу “SMB сервера” было бы очень удобно как-то работать от имени своих клиентов. В Windows такой механизм предусмотрен и называется олицетворением (англ. Impersonation).
Олицетворение#
До этого уже немного была затронута тема процессов в операционной системе Windows. Теперь расширим это понятие. У каждого процесса есть как минимум один поток, выделяемый при создании процесса (как правило, на практике потоков больше). Этот поток может породить другие потоки, те в свою очередь новые и т.д.
Рассматриваемый всюду ранее токен доступа, назначаемый процессу операционной системы при его создании, называется первоначальным токеном или токеном процесса (англ. primary token).
По умолчанию первоначальный токен наследуется дочерними потоками, но существует механизм олицетворения, который позволяет процессу назначать дочерним потокам специальные токены, отличающиеся от первоначального токена.
Токен потока, применяемый для заимствования контекста безопасности другого пользователя, называется олицетворяющим токеном (англ. impersonation token).
Таким образом появляется еще одна классификация токена: первоначальный или олицетворяющий.
В связи изложенным можно уточнить определение токена:
Токен доступа пользователя — объект ядра ОС Windows, описывающий контекст безопасности процессов или потоков, работающих от имени указанного пользователя.
SMB сервер представляет собой многопоточное приложение. Сервер обслуживает каждого клиента в рамках соответствующего выделенного потока, обладающего токеном, олицетворяющим указанного клиента.
Выдача олицетворяющего токена при сетевом входе
В данном случае олицетворяющий токен также будет связан с сессией, возникшей в результате сетевого входа.
Аутентификации при сетевом входе
Еще раз обратим внимание, что токен доступа не содержит в себе никаких учетных данных пользователя. По сути токен используется для идентификации пользователя, так как в нем хранится информация о группах и привилегиях. На основе этой информации принимается решение о предоставлении доступа к защищаемым объектам. Вспомните аналогию с аккредитационной картой. Аккредитация не аутентифицирует, но дает возможность быстро понять куда разрешен доступ.
Уровни олицетворения#
Олицетворяющие токены имеют следующую важную характеристику — уровень олицетворения, который отражает степень того, насколько сервер может олицетворять клиента. Всего существуют следующие четыре уровня олицетворения:
| Название | Описание |
|---|---|
| Anonymous | Удаленный сервер не может идентифицировать или олицетворять клиента. Используется для анонимного подключения. |
| Identification | Удаленный сервер может идентифицировать, но не может олицетворять клиента. |
| Impersonation | Удаленный сервер может идентифицировать и олицетворять клиента локально в системе. Подобные токены обычно создаются в результате сетевого входа, например при доступе к FTP серверу. |
| Delegation | Удаленный сервер может идентифицировать и олицетворять клиента в удаленных системах. Подобные токены обычно создаются в результате интерактивного входа, например по RDP. |
Классификации токенов#
Обобщая написанное выше, получается, что токен:
- Содержит перечень групп и привилегий пользователя
- Может быть полным, а может быть фильтрованным
- Может быть связан с интерактивной или неинтерактивной сессией, то есть иметь связь с учетными данными или не иметь
- Может быть первоначальным, а может быть олицетворяющим. Если токен олицетворяющий, то он обладает одним из четырех уровней олицетворения
Заключение#
В настоящей статье было рассмотрено устройство токенов доступа в Windows. Был представлен обзор основных атрибутов по которым можно классифицировать токены, а также рассказано о связи токенов, сессий и аутентификационных данных. Изложенные сведения полезны для понимания атак, связанных с манипуляциями токенами.
Что такое token?
![]()
Термин token имеет много разных смыслов. Один из смыслов разъяснен в посте @Heidel, но это лишь один из смыслов, причем не самый частый.
В контексте вопроса, скорее всего, речь идет о немного другом смысле.
Под токеном понимается замещение объекта неким обозначением/идентификатором (часто просто целым числом или строкой). Например: идентификатор сессии это классический токен. Токены широко применяются обычно в системах авторизации/идентификации и проч. Технически обычно реализуются в виде записи в БД, где токен является идентификатором записи о данных юзера или предоставленного доступа. Обычно в этом случае токен вычисляется как хэш комбинации данных юзера (логина), времени создания и ключей сервера ну и проч.
Смысл всего этого в том, что вместо того, чтобы передавать юзеру целый сонм разных данных достаточно дать ему в руки токен и сказать: вот тебе токен и с помощью него можно при необходимости сделать все что надо. Это и удобно и меньше рисков засветки данных.
zmts / tokens.md
Аутентификация(authentication, от греч. αὐθεντικός [authentikos] – реальный, подлинный; от αὐθέντης [authentes] – автор) — это процесс проверки учётных данных пользователя (логин/пароль). Проверка подлинности пользователя путём сравнения введённого им логина/пароля с данными сохранёнными в базе данных.
Авторизация(authorization — разрешение, уполномочивание) — это проверка прав пользователя на доступ к определенным ресурсам.
Например, после аутентификации юзер sasha получает право обращаться и получать от ресурса «super.com/vip» некие данные. Во время обращения юзера sasha к ресурсу vip система авторизации проверит имеет ли право юзер обращаться к этому ресурсу (проще говоря переходить по неким разрешенным ссылкам)
- Юзер c емайлом sasha_gmail.com успешно прошел аутентификацию
- Сервер посмотрел в БД какая роль у юзера
- Сервер сгенерил юзеру токен с указанной ролью
- Юзер заходит на некий ресурс используя полученный токен
- Сервер смотрит на права(роль) юзера в токене и соответственно пропускает или отсекает запрос
Собственно п.5 и есть процесс авторизации.
Дабы не путаться с понятиями Authentication/Authorization можно использовать псевдонимы checkPassword/checkAccess(я так сделал в своей API)
JSON Web Token (JWT) — содержит три блока, разделенных точками: заголовок(header), набор полей (payload) и сигнатуру. Первые два блока представлены в JSON-формате и дополнительно закодированы в формат base64. Набор полей содержит произвольные пары имя/значения, притом стандарт JWT определяет несколько зарезервированных имен (iss, aud, exp и другие). Сигнатура может генерироваться при помощи и симметричных алгоритмов шифрования, и асимметричных. Кроме того, существует отдельный стандарт, отписывающий формат зашифрованного JWT-токена.
Пример подписанного JWT токена (после декодирования 1 и 2 блоков):
Токены предоставляют собой средство авторизации для каждого запроса от клиента к серверу. Токены(и соответственно сигнатура токена) генерируются на сервере основываясь на секретном ключе(который хранится на сервере) и payload’e. Токен в итоге хранится на клиенте и используется при необходимости авторизации какого-либо запроса. Такое решение отлично подходит при разработке SPA.
При попытке хакером подменить данные в header’ре или payload’е, токен станет не валидным, поскольку сигнатура не будет соответствовать изначальным значениям. А возможность сгенерировать новую сигнатуру у хакера отсутствует, поскольку секретный ключ для зашифровки лежит на сервере.
access token — используется для авторизации запросов и хранения дополнительной информации о пользователе (аля user_id, user_role или еще что либо, эту информацию также называет payload). Все поля в payload это свободный набор полей необходимый для реализации вашей частной бизнес логики. То бишь user_id и user_role не являются требованием и представляют собой исключительно частный случай. Сам токен храним не в localStorage как это обычно делают, а в памяти клиентского приложения.
refresh token — выдается сервером по результам успешной аутентификации и используется для получения новой пары access/refresh токенов. Храним исключительно в httpOnly куке.
Каждый токен имеет свой срок жизни, например access: 30 мин, refresh: 60 дней
Поскольку токены(а данном случае access) это не зашифрованная информация крайне не рекомендуется хранить в них какую либо sensitive data (passwords, payment credentials, etc. )
Роль рефреш токенов и зачем их хранить в БД. Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам придется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).
Как ставить куки ?
Для того что бы refreshToken кука была успешно уставленна и отправлена браузером, адреса эндпоинтов аутентификации( /api/auth/login , /api/auth/refresh-tokens , /api/auth/logout ) должны располагася в доменном пространстве сайта. Тоесть для домена super.com на сервере ставим куку с такими опциями:
Таким образом кука установится в браузер и прийдет на все эндпоинты по адресу super.com/api/auth/<any-path>
Если у нас монолит и за аутентификацию отвечает один и тот-же API, тут проблем не должно быть. Но если за аутентификацию отвечает отдельный микросервис, прячем его средствами nginx по выше указанному пути ( super.com/api/auth ).
Логин, создание сессии/токенов (api/auth/login):
- Пользователь логинится в приложении, передавая логин/пароль и fingerprint браузера (ну или некий иной уникальный идентификатор устройства если это не браузер)
- Сервер проверят подлинность логина/пароля
- В случае удачи создает и записывает сессию в БД < userId: uuid, refreshToken: uuid, expiresIn: int, fingerprint: string, . >(схема таблицы ниже)
- Создает access token
- Отправляет клиенту access и refresh token uuid (взятый из выше созданной сессии)
- Клиент сохраняет токены(access в памяти приложения, refresh сетится как кука автоматом)
На что нужно обратить внимание при установке refresh куки:
- maxAge куки ставим равную expiresIn из выше созданной сессии
- В path ставим корневой роут auth контроллера ( /api/auth ) это важно, таким образом токен получат только те хендлеры которым он нужен( /api/auth/logout и /api/auth/rerfesh-tokens ), остальные обойдутся(нечего зря почём отправлять sensitive data).
Стоит заметить, что процесс добавления сессии в таблицу должен имеет свои меры безопасности. При добавлении стоит проверять сколько рефреш-сессий всего есть у юзера и, если их слишком много или юзер конектится одновременно из нескольких подсетей, стоит предпринять меры. Имплементируя данную проверку, я проверяю только что бы юзер имел максимум до 5 одновременных рефреш-сессий максимум, и при попытке установить следующую удаляю предыдущие. Все остальные проверки на ваше усмотрение в зависимости от задачи.
Таким образом если юзер залогинился на пяти устройствах, рефреш токены будут постоянно обновляться и все счастливы. Но если с аккаунтом юзера начнут производить подозрительные действия(попытаются залогинится более чем на 5’ти устройствах) система сбросит все сессии(рефреш токены) кроме последней.
Перед каждым запросом клиент предварительно проверяет время жизни access token’а (да берем expiresIn прямо из JWT в клиентском приложении) и если оно истекло шлет запрос на обновление токенов. Для большей уверенности можем обновлять токены на несколько секунд раньше. То есть кейс когда API получит истекший access токен практически исключен.
Что такое fingerprint ? Это инструмент отслеживания браузера вне зависимости от желания пользователя быть идентифицированным. Это хеш сгенерированный js’ом на базе неких уникальных параметров/компонентов браузера. Преимущество fingerprint’a в том что он нигде персистентно не хранится и генерируется только в момент логина и рефреша.
- Библиотека для хеширования: https://github.com/Valve/fingerprintjs2
- Более подробно: https://player.vimeo.com/video/151208427
- Пример ф-ции получения такого хеша: https://gist.github.com/zmts/b26ba9a61aa0b93126fc6979e7338ca3
В случае если клиент не браузер, а мобильное приложение, в качестве fingerprint используем любую уникальную строку(тот же uuid ) персистентно хранящуюся на устройстве.
Рефреш токенов (api/auth/refresh-tokens):
Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице(а надо бы в Redis’е). В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией, так званая рефреш-сессия.
- Клиент(фронтенд) проверяет перед запросом не истекло ли время жизни access token’на
- Если истекло клиент делает запрос на POST auth/refresh-tokens < fingerprint: string >в body и соответственно refreshToken куку.
- Сервер получает запись рефреш-сессии по UUID’у рефреш токена
- Сохраняет текущую рефреш-сессию в переменную и удаляет ее из таблицы
- Проверяет текущую рефреш-сессию:
- Не истекло ли время жизни
- На соответствие старого fingerprint’a полученного из текущей рефреш-сессии с новым полученным из тела запроса
Tip: Для отправки запроса с куками для axios есть опция
В момент рефреша то есть обновления access token’a обновляются ОБА токена. Но как же refresh token может сам себя обновить, он ведь создается только после успешной аутентификации ? refresh token в момент рефреша сравнивает себя с тем refresh token’ом который лежит в БД и вслучае успеха, а также если у него не истек срок, система рефрешит токены.
Вопрос зачем refresh token’y срок жизни, если он обновляется каждый раз при обновлении access token’a ? Это сделано на случай, если юзер будет в офлайне более 60 дней, тогда придется заново вбить логин/пароль.
- Front-end делает кол POST: api/auth/logout c refreshToken в куке или бади (лучше в куки)
- Front-end удаляет локально сохраненный в памяти accessToken
- Back-end удаляет запись из таблицы refreshSessions по refreshToken
accessToken умирает по истечению строка его жизни. Руками банить, удалять, хранить accessToken не нужно, это нарушает всю суть эксесс токена.
В случае кражи access токена и refresh куки:
- Хакер воспользовался access token’ом
- Закончилось время жизни access token’на
- Клиент хакера отправляет refresh token и fingerprint
- Сервер смотрит fingerprint хакера
- Сервер не находит fingerprint хакера в рефреш-сессии и удаляет ее из БД
- Сервер логирует попытку несанкционированного обновления токенов
- Сервер перенаправляет хакера на станицу логина. Хакер идет лесом
- Юзер пробует зайти на сервер >> обнаруживается что refresh token отсутствует
- Сервер перенаправляет юзера на форму аутентификации
- Юзер вводит логин/пароль
В случае кражи access токена, refresh куки и fingerprint’а:
Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобный с точки зрения UX (без примера в кодовой базе supra-api-nodejs ).
Предложу несколько вариантов решения данной проблемы:
- Хранить IP или Subnet залогиненного клиента
- Хакер воспользовался access token’ом
- Закончилось время жизни access token’на
- Хакер отправляет refresh куку и fingerprint
- Сервер проверяет IP хакера, хакер идет лесом
UX минус: нужно логинится с каждого нового IP.
- Удалять все сессии в случае если refresh токен не найден
- Хакер воспользовался access token’ом
- Закончилось время жизни access token’на
- Хакер отправляет refresh куку и fingerprint
- На сервере создается новый refresh токен («от хакера»)
- Хакер получает новую пару токенов
- Юзер пробует отправить запрос на сервер >> обнаруживается что refresh токен не валиден
- Сервер удаляет все сессии юзера, в последствии чего хакер больше не сможет обновлять access токен
- Сервер создает новую сессию для пользователя
UX минус: в каждом случае когда сервер не будет находить рефреш токен — будут сбрасиватся все сессии юзера на всех устройствах.
Зачем все это ? JWT vs Cookie sessions
Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?