Обход аутентификации и атаки на сессии
Аутентификация подтверждает личность один раз; сессия несёт это доказательство на каждом следующем запросе. Атакующий редко угадывает пароль — он крадёт, подделывает или переигрывает доказательство. Разберём как, и почему каждый контроль — структурный фикс.
На авторизованном задании тестировщик входит в целевое приложение, копирует из браузера свой собственный токен сессии, выходит из учётной записи и вставляет токен обратно в свежий запрос. Приложение отвечает так, будто он по-прежнему залогинен. Одно это наблюдение говорит обо всём: сервер никогда не привязывал токен к самому акту входа — он привязал личность к обладанию строкой. С кресла атакующего пароль никогда не был интересной частью. Пароль проверяется ровно один раз, у стены входа, под лимитами частоты, блокировками и, возможно, MFA. А сессия — bearer-учётка, которую сервер выдаёт в ответ и затем доверяет на каждом последующем запросе — проверяется постоянно, ездит повсюду и обычно охраняется куда небрежнее, чем стена, которую она заменяет. Поэтому реальный угон аккаунта — это редко «угадай пароль». Это украсть доказательство, подделать доказательство или обманом заставить браузер жертвы потратить доказательство за тебя. Этот урок — карта атакующего по этим трём ходам: обход, угон и подделка, прочитанная как вскрытие, которое защитник делает над собственным auth-кодом.
К концу урока ты сможешь объяснить, как атакующий обходит аутентификацию, крадёт или подделывает сессию и злоупотребляет CSRF — и какой структурный контроль убивает каждый ход в корне.
Сначала об авторизации: вскрытие глазами защитника
Всё ниже предполагает подписанное задание с явным скоупом — намеренно уязвимую лабораторию, CTF, твоё собственное приложение или программу bug bounty с целью в списке активов. Переигрывание или подделка чужой сессии против системы, которой ты не владеешь, в большинстве юрисдикций — несанкционированный доступ, точка; «я использовал только свой токен» перестаёт быть оправданием, как только ты наводишь его на чужой аккаунт. Мы изучаем рассуждение атакующего по одной причине: нельзя надёжно закрыть auth-изъян, который понимаешь лишь как пункт чек-листа. Рабочих токенов, чужих cookie и готовых эксплойтов здесь нет — только механизм, чтобы ты узнал пробел на код-ревью и спроектировал его закрытым до того, как кто-то до него доберётся.
Форма проблемы: где живёт доказательство
Аутентификация и управление сессиями — две разные задачи, которые новички сливают в одну. Аутентификация — одноразовое событие у стены входа: докажи, что ты тот, кем себя называешь, обычно паролем плюс, возможно, вторым фактором. Управление сессией — всё, что после: сервер выдаёт учётку — идентификатор сессии в cookie или самодостаточный токен вроде JWT — и затем доверяет этой учётке как «это тот пользователь, что вошёл» на каждом следующем запросе, потому что сам HTTP не имеет состояния и ничего не помнит.
Эта передача — мягкая цель. Стена входа закалена: лимиты частоты, блокировки, MFA, проверки на утёкшие пароли. А учётка сессии, которую стена производит, — это bearer-токен: кто держит его, того и считают пользователем — и она переигрывается на сотнях запросов, кэшируется в браузерах, иногда логируется, иногда попадает в URL. Атакующий, который никогда не трогает пароль, но получает, предсказывает или фабрикует эту учётку, имеет тот же доступ, что и легитимный пользователь. Три семейства атак целят в этот шов:
- Обход аутентификации — достичь авторизованного состояния, не завершив вход.
- Угон сессии — захватить валидную сессию, принадлежащую кому-то другому.
- Подделка сессии — изготовить учётку, которую сервер ошибочно примет (ловушки JWT).
CSRF — четвёртый ход с подвохом: атакующий вообще не держит учётку — он заставляет собственный браузер жертвы потратить её.
Обход аутентификации: попасть внутрь без стены
Обход — это когда атакующий достигает авторизованного состояния, не удовлетворив проверку, которую разработчик считал обязательной. Повторяющаяся корневая причина та же, что у более широкого сбоя контроля доступа: приложение предполагает, что шаг произошёл, вместо того чтобы проверить, что он произошёл.
- Принудительный обход / отсутствие проверок на уровне функций. Форма входа закрывает ссылку на
/dashboard, но сам/dashboardникогда заново не проверяет сессию, поэтому прямой запрос к нему входит насквозь. «Аутентификация» была спрятанной дверью, а не замком. - Логические изъяны в многошаговых потоках. Сброс пароля шлёт токен на почту, но эндпоинт «задать новый пароль» принимает имя пользователя и новый пароль, не привязывая их к валидному, непогашенному, неистёкшему токену — поэтому атакующий сбрасывает любой аккаунт. Или многошаговый вход ставит флаг «прошёл первый фактор», который клиент может перещёлкнуть, минуя MFA.
- Доверие к управляемому клиентом состоянию. Запрос несёт
role=adminилиisAuthenticated=trueв cookie, заголовке или claim’е JWT, который сервер читает, не выводя его заново на своей стороне. Клиент владеет всем, что отправляет; считать любое из этого проверенным фактом — и есть баг.
Зрелый рефлекс — запрет по умолчанию и проверка каждого шага на стороне сервера: каждый защищённый маршрут независимо заново устанавливает, кто вызывающий и что ему можно, вычисляя это из серверного состояния сессии, — никогда не выводя из того, что маршрут стоит «за» страницей входа, и не из значения, присланного клиентом.
Угон сессии: кража валидной учётки
Угон захватывает сессию, которая по-настоящему валидна, — атакующий ничего не подделывает, он получает настоящий bearer-токен. Интересный для защитника вопрос — как токен утекает, потому что у каждого пути утечки есть точный структурный фикс.
| Атака | Как добывается учётка | Структурный фикс |
|---|---|---|
| Сниффинг в транзите | Токен считан с провода на нешифрованном или пониженном соединении | TLS везде + HSTS; флаг cookie Secure, чтобы она не уходила по HTTP |
| Кража через XSS | Внедрённый скрипт читает cookie или токен из JS | Cookie HttpOnly (JS не читает) + чинить XSS в корне |
| Фиксация сессии | Атакующий подсаживает известный ему id сессии, жертва входит под ним | Перегенерировать id сессии при каждой смене привилегий (вход) |
| Предсказание | Id сессий последовательны или низкоэнтропийны, потому угадываемы | Криптослучайные id с высокой энтропией из CSPRNG |
| Токен в URL | Сессия утекает через Referer, историю браузера, логи, общие ссылки | Никогда не класть токены сессии в URL; держать в cookie/заголовках |
Две из них достойны более пристального взгляда. Фиксация сессии переворачивает обычный порядок: вместо кражи токена, который у жертвы уже есть, атакующий даёт жертве id сессии, который сам уже знает (через подготовленную ссылку или подсаженную cookie), ждёт, пока жертва аутентифицируется, и — если сервер сохраняет тот же id через вход — наследует аутентифицированную сессию. Фикс — одна строка дисциплины: ротировать идентификатор сессии при каждом переходе привилегий, чтобы id, под которым жертва аутентифицируется, был новеньким и неизвестным атакующему. HttpOnly и XSS — пара: хранимый XSS, способный выполнить document.cookie, превращает сессию каждого посетителя в кражу, поэтому HttpOnly (cookie невидима для JavaScript) — это локализация, отвязывающая «у нас есть баг XSS» от «баг XSS осушает каждую сессию» — хотя это локализация, а не лечение самого XSS.
Ловушки JWT: подделка учётки, которую сервер примет
JWT — это самодостаточная учётка: claim’ы (кто ты, твоя роль, срок) едут внутри токена, и сервер доверяет им, потому что токен подписан. Этот дизайн переносит границу доверия на подпись — ровно туда, где живут классические подделки. Со стороны атакующего вопрос всегда один: могу ли я заставить сервер принять токен, который я сформировал?
- Ловушка
alg: none. Ранние библиотеки JWT уважали заголовок, объявляющий алгоритмnone— «этот токен не подписан» — и принимали его как валидный. Атакующий правит payload (например,role: admin), ставит алгоритм none, выбрасывает подпись, и сервер доверяет токену, который он на деле никогда не проверял. Фикс: сервер фиксирует ожидаемый алгоритм; он никогда не даёт заголовку токена выбирать, как его проверять. - Путаница алгоритмов (RS256 в HS256). Когда проверка использует асимметричный ключ (RS256), публичный ключ по замыслу публичен. Если сервер наивно проверяет тем алгоритмом, что называет заголовок, атакующий переключает заголовок на HS256 (симметричный) и подписывает поддельный токен этим публичным ключом как секретом HMAC — который сервер затем «проверяет» тем же публичным значением. Фикс тот же: фиксировать алгоритм на стороне сервера; никогда не выводить его из управляемого атакующим ввода.
- Слабый или утёкший секрет. Токен HS256, подписанный угадываемым секретом (
secret,changeme), брутфорсится офлайн; как только секрет известен, атакующий чеканит любые токены. Фикс: длинные, случайные, управляемые секрет-менеджером ключи — и ротация. - Игнор
exp/ нет отзыва. Самодостаточный токен валиден до истечения, и серверного списка для его отзыва нет. Если сервер забывает проверятьexp, перехваченный токен годен вечно; даже когда проверяет, украденный токен пригоден до истечения. Фикс: принудительный срок, короткие времена жизни и связка с механизмом refresh/denylist для выхода и отзыва.
▸Почему это работает
Почему «пусть токен сам говорит серверу, как его проверять» — это баг за обоими: ловушкой alg: none и путаницей RS256 в HS256? Потому что это отдаёт атакующему контроль над процедурой проверки, а не только над данными. Подпись осмысленна, лишь если проверяющий независимо решает, как выглядит валидная подпись — какой алгоритм, какой ключ. В тот миг, когда сервер читает алгоритм из собственного (правимого атакующим) заголовка токена и подчиняется ему, токен сам себе ставит оценку: он может объявить себя неподписанным или объявить себя подписанным HMAC, чтобы публичный ключ стал «секретом». Структурный фикс в обоих случаях один и не имеет отношения к патчу версии библиотеки: сервер фиксирует ожидаемый алгоритм и ключ вне диапазона запроса и считает поддельным любой токен, который не совпадает. Проверка должна быть свойством, которое утверждает сервер, а не параметром, который поставляет учётка, — тот же принцип запрета по умолчанию, что проходит через каждый auth-контроль.
CSRF: заставить браузер жертвы потратить учётку
CSRF — изящнейшая, потому что атакующий ничего не крадёт и не подделывает — он эксплуатирует собственную услужливость браузера. Браузер автоматически прикрепляет cookie к любому запросу на сайт, включая запрос, инициированный другим сайтом. Поэтому если жертва залогинена в bank.example и заходит на страницу атакующего, скрытая форма, автоотправляющая POST bank.example/transfer, едет вместе с настоящей cookie сессии жертвы, и банк видит полностью аутентифицированный, безупречно валидный запрос — инициированный атакующим. Сессию не компрометировали; её окружающую власть (ambient authority) позаимствовали.
Структурный фикс бьёт прямо в пробел: сервер должен требовать доказательства, что запрос пришёл от его собственного фронтенда, чего окружающие cookie дать не могут. Два механизма, в идеале слоями:
- Анти-CSRF токены — сервер вшивает непредсказуемый, посессионный токен в собственные формы и требует его на запросах, меняющих состояние. Межсайтовая страница атакующего не может прочитать этот токен (политика одного источника блокирует), поэтому не может его вложить, и поддельный запрос отклоняется.
- Cookie
SameSite—SameSite=LaxилиStrictвелит браузеру не слать cookie сессии на межсайтовых запросах вообще, убирая окружающую власть, что и делает CSRF возможным. Lax — сильное недорогое значение по умолчанию; токены остаются ремнём к подтяжкам SameSite.
Заметь связь: CSRF работает именно потому, что легитимная сессия валидна, так что «закали вход» ничего для него не делает. Он защищается на слое приёма запроса, а не на слое аутентификации — ровно поэтому это своя категория.
У банка `POST /transfer` проходит всякий раз, когда присутствует валидная cookie сессии, без иной проверки. Тестировщик демонстрирует CSRF: скрытая автоотправляющая форма на странице атакующего двигает деньги, используя собственную залогиненную сессию жертвы. Выбери фикс, закрывающий класс.
Библиотека JWT принимает токен, чей заголовок объявляет алгоритм как `none`, считая его валидным неподписанным токеном. Почему это эксплуатируемо и каков корневой фикс?
Атакующий подсаживает в браузер жертвы известный ему id сессии, жертва входит, и сервер сохраняет тот же id — теперь атакующий разделяет аутентифицированную сессию. Что это и каков фикс?
Упорядочи, как авторизованный тестировщик рассуждает от целевого входа к угону аккаунта через сессию, затем к устойчивому фиксу:
- 1 Аутентифицироваться, затем найти bearer-учётку, которой сервер доверяет после (cookie сессии или JWT)
- 2 Прощупать, как защищена учётка: привязана ли к входу, подписана ли верно, ограничена ли одним источником?
- 3 Эксплуатировать слабое звено — переиграть ещё валидный токен, подделать JWT или проехать на сессии через CSRF
- 4 Закрыть класс структурно: ротировать id на входе, фиксировать алгоритм JWT на сервере, требовать анти-CSRF доказательство
- 01Почему атакующий целит в сессию, а не в пароль, и каковы три семейства атак на сессию плюс CSRF?
- 02Объясни ловушки подделки JWT и единый принцип, который их чинит, плюс как работает CSRF и почему закалка входа его не останавливает.
Аутентификация подтверждает, кто ты, один раз — у закалённой стены входа; управление сессией — всё, что после: сервер выдаёт bearer-учётку (id сессии в cookie или самодостаточный JWT) и доверяет ей на каждом следующем запросе, потому что HTTP без состояния. Этот шов, а не пароль, и есть место, где реально случается угон аккаунта. Обход аутентификации попадает внутрь, не завершив вход: принудительный обход маршрутов, которые не перепроверяют, логические изъяны в потоках сброса или MFA, или доверие к роли и состоянию входа, которыми управляет клиент, — чинится запретом по умолчанию и проверкой каждого шага на сервере. Угон сессии крадёт по-настоящему валидный токен сниффингом, XSS, фиксацией, предсказанием или утечкой в URL, и у каждого пути свой точный структурный фикс: TLS плюс HSTS и cookie Secure, cookie HttpOnly, ротация id сессии при каждой смене привилегий, высокоэнтропийные id и никаких токенов в URL. Подделка JWT идёт от того, что токену позволяют выбирать свою проверку — ловушка алгоритма none и путаница RS256 в HS256 падают перед одним правилом: сервер фиксирует алгоритм и ключ и считает любое несовпадение подделкой, рядом с сильными секретами и принудительным сроком. CSRF не нужна украденная учётка вовсе; он заставляет браузер жертвы потратить её собственную сессию через автоматически прикрепляемую cookie, поэтому защищается на приёме запроса анти-CSRF токенами и SameSite, а не у стены входа. Поэтому в следующий раз, читая auth-код, твой рефлекс — вывернутый наизнанку вопрос атакующего: после входа чему именно доверяет этот сервер — и может ли строки, которую клиент держит, формирует или на которой едет, хватить, чтобы быть пользователем?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.