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

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

Рассмотрим гипотетический мультитенантный сервис отчётности. Пользователь тенанта A может запустить экспорт данных тенанта A. В новом релизе проверку доступа переносят из сервиса экспорта в общий компонент политик. Интерфейс уже новый, половина API-подов ещё старая, в очереди лежит экспорт, созданный до смены роли, а кэш решения живёт несколько минут. Обычный позитивный сценарий проходит на обеих версиях точки API. Ни один путь никогда не должен пересекать границу тенанта B. После отзыва у пользователя права на экспорт в тенанте A отложенная задача также должна следовать согласованному для продукта правилу после отзыва. Это иллюстрация, а не описание проекта клиента.

Проверяется инвариант, а не одна сборка

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

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

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

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

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

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

Листайте таблицу по горизонтали

СостояниеУправляемая подготовкаНегативное утверждениеПозитивный контрольСохраняемое доказательство
Исходное/текущееСтарый клиент, старый сервис, текущая схемаЧужой тенант недоступенАвторизованный субъект своего тенанта получает доступМаркер сборки, субъект, тенант, решение, идентификатор корреляции
Смешанные версииСтарые и новые сервисы или воркеры одновременно принимают работуОтозванный или чужой субъект отклонён каждой версиейРазрешённый запрос доходит до каждой версии и проходитМаршрутизация и отдельный результат по каждой версии
Флаг включён/выключенПринудительно выбирается каждая значимая веткаНи одна ветка не расширяет полномочияДопустимый субъект проходит оба предусмотренных путиКонтекст флага и вычисленная вариация
Резерв флагаВ тестовом контуре имитируется ошибка вычисленияРезервное значение не обходит серверную политикуПодтверждена резервная ветка; разрешённое действие или независимая проверка готовностиОшибка вычисления, резервное значение и решение сервера
Старые отозванные учётные данныеРабочая сессия подтверждается, затем роль или разрешение отзываетсяТе же данные или токен отклонены в согласованный срокСвежий разрешённый токен продолжает работатьВремя выдачи, отзыва, запроса и решения
Отложенное событиеБезопасная привилегированная задача удерживается до отзыва или смены тенантаИсполнение следует согласованному правилу после измененияЭквивалентная актуально разрешённая задача проходитID сообщения, неизменяемый контекст, версия воркера
Откат или движение вперёдПроверяется согласованный откат на старый код или исправленная новая сборка с текущими схемой и политикамиНи один путь не возвращает устаревшие полномочияСовместимая разрешённая операция проходитВерсии схемы и кода, выбранный путь, результат

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

Проверяйте старые и новые пути приложения отдельно

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

Проверяйте совместимость в обе стороны. Направьте старый клиент на новый сервер и, если это допускает реальная топология релиза, новый клиент на старый сервер. Повторите инвариант для каждого API, экспорта, поиска, интерфейса поддержки и фонового пути, способного выполнить защищённое действие. Скрытая кнопка в интерфейсе не является запретом: прямой запрос должен отклонить сервер.

Особое внимание старому коду нужно после изменения политики или данных. Откат способен восстановить доступность вместе с прежней ошибкой авторизации. «Старая версия запускается» и «старая версия соблюдает текущие правила тенантов и отзыва» — разные критерии приёмки.

Фича-флаги создают состояния рядом с политикой доступа

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

Используйте детерминированные синтетические контексты, чтобы получить включённое, выключенное, целевое и резервное состояния. Проверяйте инвариант на сервере. Резерв должен давать явный, заранее рассмотренный результат; «что вернёт SDK» не является критерием приёмки.

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

Отзыв должен обгонять сессии, токены и кэши решений

Полезный тест начинается с учётных данных или токена, которые доказанно работают. Зафиксируйте успешное действие внутри своего тенанта, отзовите роль или разрешение и повторите то же безопасное действие на всех доступных старых и новых путях. HTTP 200 от OAuth-точки отзыва сам по себе недостаточен: стандарт допускает тот же ответ для уже недействительного токена и признаёт задержку распространения знания об отзыве между серверами.

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

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

Для отложенных сообщений нужен явный контракт авторизации

Очередь разделяет момент приёма и момент исполнения. Стандартная очередь может доставить сообщение более одного раза, поэтому обработчик должен быть идемпотентным. Безопасность добавляет второй вопрос: чьи полномочия проверяются, когда воркер наконец запускается?

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

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

Безопасность миграции определяет реальность отката

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

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

Критерий релиза должен разделять обратимые и необратимые состояния. До разрушительного удаления старой структуры откат может означать возврат старого кода. После него то же действие может быть недопустимо: старый код не понимает новые данные или не соблюдает текущую политику. Тогда согласованным ответом станет новый исправляющий релиз (roll-forward), восстановление в изолированной среде или отдельно проверенный компенсирующий релиз. Формулировка «всегда можно откатить» скрывает это решение.

Проведите пятишаговый авторизованный тест без вредоносных данных

Используйте синтетические тенанты A и B, тестовые роли, случайные идентификаторы объектов и безвредные записи. Письменно согласуйте разрешение, лимиты трафика, условия остановки, окно, хранение доказательств и возможность приостановить воркеры или очередь. Для проверки инварианта не нужны неконтролируемое сканирование и разрушительные запросы.

Приёмка переходного состояния
  1. Базовая точкаДокажите, что старые учётные данные и текущий разрешённый контроль до отзыва достигают нужного ресурса.
  2. ПереходПереведите систему в одно названное состояние версий, флага, очереди или миграции и докажите, какая версия обрабатывает действие.
  3. Событие безопасностиОтзовите роль, членство в тенанте, сессию или разрешение и зафиксируйте авторитетное время.
  4. Парные проверкиВыполните запрещённое действие и разрешённый позитивный контроль на всех доступных синхронных и отложенных путях.
  5. РешениеСравните задержку и конечные изменения с инвариантом; повторите тест исправленной сборки или остановитесь перед необратимой контрольной точкой.

Запись должна быть воспроизводимой и минимальной: ID сценария, версии кода и схемы, состояние флага, синтетические учётные записи, значимые временные отметки, источник решения, идентификатор корреляции запроса или сообщения, результат и очищенное доказательство. Храните хэши токенов или непрозрачные метки, а не рабочие секреты. Успешная проверка означает, что инвариант сохранился в названных состояниях и окне; она не доказывает все комбинации или отсутствие других уязвимостей.

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

Превратите матрицу в решение о релизе и понятное задание на пентест

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

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

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

PENTEST.RED

Обсудить объём и стоимость пентеста

Сначала согласуем системы, доступы, ограничения и ожидаемый результат.

Подготовить контур проверки

Статья подготовлена на основе глубокого исследования и прошла редакционную переработку.

PENTEST.RED / RED JOURNAL