Атака повторного воспроизведения — как валидный токен становится оружием

Атака повторного воспроизведения — как валидный токен становится оружием

Атака повторного воспроизведения — это неприятная честность мира: злоумышленник не взламывает алгоритмы и не крушит криптографию, он просто переотправляет ваше же корректное сообщение ещё раз. Если система проверяет только подпись и подлинность отправителя, но не заботится о свежести и уникальности, повтор окажется убедительным. С этого начинаются двойные списания, повторная выдача прав, дубли заказов и другие «чудеса», которые потом долго и дорого раскручивать.

В 2025 году эта тема особенно живая. Мы переносим логику в API , общаемся через вебхуки, ускоряем соединения с 0-RTT в TLS 1.3, доверяем мобильным и IoT-устройствам , а где-то до сих пор не дожали дисциплину ключей и токенов. Ниже — последовательное объяснение, что именно повторяют, почему одной подписи мало, где чаще всего «шатается» архитектура и как настроить многоуровневую защиту так, чтобы надёжность не пострадала.

Что такое атака повторного воспроизведения и чем она не является

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

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

Как из повтора получается инцидент: путь сообщения от записи до дубликата

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

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

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

Где повторы случаются чаще всего: конкретные сценарии

Платёжное API: один и тот же «создать платёж» дважды

Клиент отправляет запрос на создание транзакции. Вы проверяете подпись, всё чисто — деньги списаны, сущность создана. Через двадцать секунд прилетает тот же байтовый поток: его записали на промежуточном узле. Если у запроса не было одноразового значения и метки времени, а на стороне сервера нет памяти «мы это уже видели», произойдёт второе списание. Частая ошибка — рассчитывать, что платёжный провайдер «спасёт». Провайдер спасёт на своей стороне, а ваш сервис вполне может завести дубликат операции и дойти до бизнес-последствий.

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

Вебхуки: «подписано — значит честно» не работает

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

О важной тонкости. Подписывайте именно сырой байтовый поток тела запроса. Любые автоматические «украшения» JSON в промежуточных слоях ломают сверку подписей. Если сырым потоком оперировать нельзя, жёстко приводите данные к каноническому виду и подписывайте уже его — в точном и детерминированном формате.

Токены и сессии: жетон на предъявителя живёт слишком долго

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

Мобильные и IoT: устройство без часов — мечта для повтора

Недорогие контроллеры часто не умеют хранить время и состояние между перезагрузками. После сбоя они отправляют тот же «разрешающий» пакет, который сервер с радостью принимает «ещё раз». Здесь временные окна бессильны. Работают монотонные счётчики и сохранённое состояние на устройстве: сервер запоминает последний номер кадра, устройство не забывает свой счётчик при перезапуске. Любой кадр с номером меньше либо равным последнему отбрасывается. Подпись сообщения ключом устройства обязательна — иначе счётчик нетрудно подделать.

0-RTT в TLS 1.3: ускорение, которое нужно уметь приручать

Ранние данные позволяют отправить запрос ещё до завершения рукопожатия. По природе такие данные воспроизводимы. Базовое правило простое: ранние запросы либо идиоматически «безопасные» (чтение, проверка статуса), либо запрещены на чувствительных маршрутах. Если ограничения по бизнесу нет, придётся вести маленький «словарь ранних обращений» в коротком окне и отклонять дубликаты. И всё равно лучше не пускать в 0-RTT то, что меняет деньги и состояние.

Почему одной подписи мало: три свойства, которые надо проверять вместе

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

Защита от повтора: слои, которые складываются в систему

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

Одноразовые значения и временные окна

Нонс позволяет серверу сказать: «это уже было». Вы можете выдавать его заранее, а можете принимать идентификатор от клиента. В обоих случаях у сервера должен быть быстрый журнал израсходованных значений с коротким сроком хранения. Метка времени плюс узкое окно делают записанный пакет бесполезным уже через считанные десятки секунд. Высоконагруженным системам помогает лёгкий фильтр в памяти и «скользящее» окно; распределённым — общий быстрый кэш. Главное — не пытаться лечить отсутствие часов у устройств гигантскими окнами: это убивает смысл защиты.

Привязка к клиенту и двусторонние сертификаты

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

Подпись полезной нагрузки и канонический вид

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

Идемпотентность как страховка от человеческих и сетевых ошибок

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

Как это собрать в реальных системах: рекомендации по ролям

Интернет и публичные API

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

Аутентификация и авторизация

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

Вебхуки и интеграции

Принимайте уведомления быстро: проверили подпись, метку времени и идентификатор события, отметили «видено», вернули «получено». Обработку переносите в очередь. Событие с тем же идентификатором не должно менять состояние второй раз. Так вы дружите с повторами поставщика и не делаете себе «петлю» из дубликатов.

Мобильные и IoT

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

Платежи и расчёты

Любая денежная операция должна быть либо идемпотентной, либо двухфазной. Подтверждение привязывайте к конкретному намерению, повтор подтверждения должен быть безвреден. Уведомления от платёжных систем принимайте с подписью, меткой времени и уникальным идентификатором; храните их в кратком журнале, чтобы дубликат не запустил повторное начисление.

Наблюдаемость: как увидеть попытки повтора, пока не поздно

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

Как тестировать защиту от повтора: короткий, но рабочий план

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

Анти-паттерны, которые снова ломают защиту

  • Подпись есть, а свежести и уникальности нет: метку времени и нонс «забыли», журнал израсходованных значений не ведётся.
  • Долгоживущие токены без привязки к клиенту: украл — пользуйся неделями.
  • Доверие только к TLS: 0-RTT включили везде, а приложение про повторы не знает.
  • Вебхуки без идемпотентности: «подписано — значит можно повторно начислить».
  • Подпись не тех данных: форматирование тела по дороге меняется, сверка «гуляет», дубликаты пролезают.
  • Пытались компенсировать отсутствие часов широкими окнами — и тем самым убили защиту.

Частые вопросы без маркетинговых ответов

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

Это сильно замедлит систему? Нет, если делать с умом. Узкие окна и нонс — это миллисекунды. Идемпотентность — быстрые вставки в кэш. Наоборот, вы сократите издержки на разруливание дублей и спорных списаний.

Нам обязательно отключать 0-RTT? Не обязательно. Дайте ему «коридор»: только безопасные маршруты без изменения состояния. Всё остальное — после полноценного рукопожатия.

Итог

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

атака повторного воспроизведения вебхуки OAuth 0-RTT TLS 1.3 IoT ретрансляция человек посередине
Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.

Роботы-матки: человечность снята с производства

От капсул вместо утробы до детей с премиум-опциями: как мы превратили рождение в бизнес-план и сервис по подписке.


Техно Леди

Технологии и наука для гуманитариев