{ "index": 178, "slug": "editorial-2023-01-field-threat-model", "title": "Модель угроз для одного потока: от симптома до проверяемого контроля", "excerpt": "Как разобрать security-sensitive изменение, когда команда предлагает контроль, но не называет актив, границу и злоупотребление. На примере одного API-потока — схема, код, таблица диагностики и критерий готовности.", "contentHtml": "

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

\n

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

\n

Начните с наблюдаемого симптома

\n

Опишите не тревогу, а факт. Например: обработчик принимает запрос на изменение заявки, но контракт не говорит, как он отвергает неподтверждённый вход. Это не доказывает уязвимость. Факт только показывает пробел: у изменения нет явного условия отказа и нет артефакта, который его подтверждает.

\n

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

\n

Для первого прохода достаточно одного потока. Возьмём учебный пример: browser-client отправляет запрос в public-api-to-handler, handler записывает change-request в хранилище. Asset — заявка на изменение. Boundary — место, где публичный вход становится внутренним вызовом обработчика. Abuse — неподтверждённый запрос пытается изменить заявку. Это условная модель. Она не описывает конкретный продукт и не доказывает безопасность production-системы.

\n
browser-client\n      |\n      | request\n      v\n public-api-to-handler   <-- boundary\n      |\n      v\n    handler ----> change-request store (asset)\n\nabuse: неподтверждённый запрос меняет заявку\ncontrol: обработчик отклоняет вход без проверяемого подтверждения\nevidence: тест показывает отказ такого входа
\n

Схема полезна только тогда, когда каждый элемент ведёт к вопросу. Asset отвечает, какое свойство нельзя потерять. Boundary показывает, где меняются предположения о доверии. Abuse описывает действие нарушителя. Control формулирует решение. Evidence показывает, что решение проверили. Слово «система» не заменяет ни один из этих элементов.

\n

Симптом → причина → проверка → действие

\n
Диагностика неполной модели угроз
СимптомПричинаПроверкаДействие
Назван control, но нет assetВыбрали привычный механизмЧто потеряет свойство при злоупотреблении?Назвать один актив и его свойство
Есть threat, но нет boundaryНе указано место защитного решенияГде вход перестаёт быть доверенным?Поставить границу на DFD и описать переход
Есть control, но нет отрицательной веткиПроверяли только успешный путьКакой вход обязан получить отказ?Добавить тест на отказ и ожидаемый результат
В evidence написано «проверено»Не назван метод и артефактЧто увидит независимый проверяющий?Указать test, log, trace или ручной шаг
Rollback означает «вернуть всё»Модель смешана с поставкойКакие файлы, права и данные меняются?Разделить исходный snapshot и release-план
\n

Таблица отсекает ложную полноту. Заполненная строка не означает, что риск мал. Она означает, что следующий вопрос имеет адресата и ожидаемый ответ. Если ответ не находится, оставьте поле пустым и остановите выбор контроля. «Неизвестно» полезнее, чем выдуманное «защищено».

\n

Сначала граница, потом механизм

\n

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

\n

В примере ниже signed-request — лишь учебная гипотеза. Её обещание узкое: handler принимает запрос только после проверяемого подтверждения. Это не синоним шифрования транспорта, аутентификации пользователя или авторизации операции. В настоящем API могут потребоваться другой протокол, nonce, защита от повторной отправки, проверка полномочий и журналирование. Статья не выбирает их за владельца системы.

\n
\"Маршрут
Маршрут вопросов для одного потока. Рисунок показывает порядок диагностики, а не карту реального продукта, отчёт сканера или план развёртывания.
\n

Проверьте минимальный контракт кодом

\n

Маленькая функция может поймать механические пропуски до обсуждения реализации. Она принимает actor, asset, boundary, abuse и один из заранее названных типов контроля. Для принятой записи возвращает evidence и snapshot для учебного отката. Такой код проверяет только структуру модели. Он не ходит в сеть, не проверяет ключ, не вызывает API и не оценивает риск.

\n
const plan = planTeachingThreatModel({\n  actor: 'browser-client',\n  asset: 'change-request',\n  boundary: 'public-api-to-handler',\n  abuse: 'unsigned request changes a request',\n  control: 'signed-request'\n});\n\nif (!plan.accepted) throw new Error(plan.reason);\nif (plan.evidence.length !== 1) throw new Error('missing evidence');\nif (plan.rollback.snapshot.asset !== 'change-request') {\n  throw new Error('rollback snapshot is incomplete');\n}
\n

В этом фрагменте есть намеренный отрицательный путь. Пустой asset, неизвестный control или отсутствие boundary должны вернуть отказ, а не «почти принятую» модель. Название функции и ответ отражают учебный контракт. Не переносите его в production без отдельной проверки требований, реализации, секретов, прав и совместимости.

\n

После успешной проверки контракта найдите реальную точку потока. Сопоставьте имя asset с полем документации или схемы, boundary — с middleware, gateway или handler, а evidence — с конкретным тестом или журналом. Если сопоставление не получается, локальный PASS ничего не говорит о приложении. Он только говорит, что четыре строки заполнены.

\n

Порядок действий

\n
  1. Зафиксируйте симптом одной фразой: какой вход, какой обработчик и какое решение сейчас не имеют проверяемого условия.
  2. Назовите один asset и свойство, которое нужно сохранить. Не используйте «данные» или «безопасность» без уточнения.
  3. Опишите actor и abuse как действие. Например: внешний клиент повторяет запрос и меняет чужую заявку.
  4. Нарисуйте границу потока и назначьте владельца решения на каждой стороне. Проверьте, не дублируют ли два слоя одну и ту же проверку.
  5. Сформулируйте control как наблюдаемое поведение отказа. «Используем подпись» слабее, чем «неподтверждённый запрос получает отказ до записи».
  6. Прогоните минимальный контракт на полном и неполном входе. Сохраните результат и причину отказа.
  7. Добавьте проверку реализации с явными данными, окружением и ожидаемым результатом. Отдельно укажите, что тест не проверяет.
  8. Подготовьте rollback до выпуска. Для модели сохраните snapshot; для продукта опишите обратимые изменения, совместимость, права, ключи и наблюдение.
  9. Попросите независимого участника воспроизвести проверку по записи. Если ему приходится угадывать вход или критерий PASS, change не готов.
\n

Когда путь нужно остановить

\n

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

\n

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

\n

Не называйте локальную функцию security review, pentest или compliance evidence. Она не видит production, реальные роли, конфигурацию, ротацию ключей, повторную отправку, лимиты и операционные журналы. Учебный пример помогает проверить форму рассуждения. Он не заменяет анализ системы и согласование риска.

\n

Проверяемый критерий готовности

\n

Один поток готов к следующему этапу, когда независимый проверяющий по записи может назвать asset, actor, boundary и abuse; найти контроль в конкретном месте; запустить отрицательную проверку; увидеть ожидаемый отказ до изменения asset; определить сохранённый snapshot и условия отката. Если хотя бы один пункт требует устного пояснения автора, модель ещё не завершена.

\n

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

\n

Проверяемые источники

\n"}