Атака повторного воспроизведения (replay attack)

Атака повторного воспроизведения (replay attack) относится к классу атак, при которых злоумышленник не пытается изменить или расшифровать сообщение, а лишь перехватывает его и повторно отправляет в систему, добиваясь повторного выполнения уже совершённого действия.

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

Типичный сценарий выглядит следующим образом:

  • Пользователь отправляет запрос на сервер: логин, перевод средств, изменение состояния ресурса
  • Запрос проходит по сети и содержит валидные данные (например, JWT, подписанный payload или зашифрованный объект)
  • Злоумышленник перехватывает этот запрос на уровне сети или прокси
  • Позже он повторно отправляет тот же самый запрос
  • Сервер, не имея механизма защиты от повторов, выполняет действие ещё раз

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

Почему атака работает

Replay attack становится возможной из-за нескольких типичных архитектурных ошибок:

  • отсутствие временных ограничений на запросы или токены
  • отсутствие одноразовых идентификаторов сообщений
  • доверие к “валидности подписи” как единственному критерию проверки
  • отсутствие серверного состояния (stateless проверка без контекста использования)
  • использование длительно живущих токенов без механизма отзыва

Особенно уязвимы системы, построенные на полностью stateless-аутентификации без дополнительных проверок уникальности запросов.

Контекст применения в веб-приложениях

В JavaScript-экосистеме replay attack часто проявляется в следующих случаях:

  • повтор HTTP-запросов с токеном авторизации
  • повтор операций оплаты или транзакций
  • повтор действий API (создание ресурса, отправка формы)
  • повтор использования подписанных payload’ов

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

Роль криптографических библиотек и Iron

Библиотека @hapi/iron используется для “запечатывания” (seal) и “распечатывания” (unseal) структур данных. Она обеспечивает:

  • конфиденциальность (шифрование данных)
  • целостность (защита от изменения)
  • аутентичность (проверка подписи)

Однако важно понимать: Iron сам по себе не предотвращает replay attack.

Запечатанный объект может быть валиден криптографически, но при этом быть использован повторно, если система не накладывает дополнительных ограничений.

Пример типичного использования:

const Iron = require('@hapi/iron');

const password = 'strong-encryption-password';

const sealed = await Iron.seal(
  { userId: 123, role: 'admin' },
  password,
  Iron.defaults
);

const unsealed = await Iron.unseal(sealed, password, Iron.defaults);

В этом сценарии:

  • sealed строка защищена от подделки
  • но может быть скопирована и использована повторно

Если такой токен используется как “ключ доступа”, злоумышленник может просто повторно отправлять его на сервер.

Почему Iron не решает проблему повторов

Важно разделять задачи:

  • Iron решает задачу защиты данных (encryption + integrity)
  • replay attack относится к уровню протокола и логики приложения

Даже идеально защищённый токен не становится одноразовым автоматически.

Если система принимает:

  • один и тот же sealed payload
  • без проверки времени
  • без проверки уникальности

то повторная отправка будет успешной.

Основные способы защиты

Временные ограничения (TTL)

Одним из базовых методов является добавление срока жизни:

  • токен действителен только ограниченное время
  • сервер отклоняет устаревшие запросы

Часто в payload добавляется поле:

{
  userId: 123,
  iat: Date.now(),
  exp: Date.now() + 5 * 60 * 1000
}

При обработке сервер проверяет exp.

Одноразовые идентификаторы (nonce / jti)

Более строгий подход — использование уникального идентификатора запроса:

  • nonce (number used once)
  • jti (JWT ID)

Пример:

{
  userId: 123,
  jti: "8f3c2b1a-..."
}

Сервер:

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

Это один из наиболее эффективных способов защиты от replay.

Серверное состояние

Хотя stateless архитектуры популярны, защита от повторов часто требует состояния:

  • Redis-кеш использованных токенов
  • база данных для jti
  • временные блокировки

Пример логики:

  • принять запрос
  • проверить jti
  • если существует → отклонить
  • если нет → сохранить и обработать

Привязка к контексту

Токен может быть привязан к:

  • IP-адресу (с осторожностью)
  • user-agent
  • устройству
  • сессии

Это усложняет повторное использование перехваченного токена в другом контексте.

Короткоживущие токены

Снижение времени жизни уменьшает окно атаки:

  • access token: 5–15 минут
  • refresh token: отдельный защищённый механизм

Replay attack и HTTP API

Особенно уязвимыми оказываются API, где:

  • операции неидемпотентны
  • нет проверки повторов
  • отсутствует уникальный идентификатор запроса

Например:

  • POST /payment
  • POST /create-order

Если такой запрос повторить, будет создан дубликат операции.

Связь с идемпотентностью

Идемпотентные операции устойчивы к replay attack по своей природе.

Например:

  • GET-запросы
  • PUT с фиксированным ресурсом

Для небезопасных методов (POST) необходимо вручную добавлять защиту:

  • idempotency key
  • jti
  • серверную проверку состояния

Практическая модель угроз

Replay attack становится особенно опасной при сочетании факторов:

  • перехват трафика (MITM на уровне прокси или логирования)
  • отсутствие TLS или его компрометация
  • длительно живущие токены
  • отсутствие server-side проверки уникальности

В таких условиях даже полностью корректная криптография не предотвращает повторное выполнение действий.

Архитектурные принципы защиты

Системная защита от replay attack строится не на одном механизме, а на комбинации:

  • криптографическая защита (например, Iron)
  • временные ограничения
  • уникальные идентификаторы
  • серверное хранение состояния
  • проверка контекста выполнения
  • минимизация срока жизни токенов

Только сочетание этих факторов делает атаку практически неэффективной.