6 lines
18 KiB
JSON
6 lines
18 KiB
JSON
{
|
||
"index": 178,
|
||
"slug": "editorial-2023-01-field-threat-model",
|
||
"title": "Модель угроз для одного потока: от симптома до проверяемого контроля",
|
||
"excerpt": "Как разобрать security-sensitive изменение, когда команда предлагает контроль, но не называет актив, границу и злоупотребление. На примере одного API-потока — схема, код, таблица диагностики и критерий готовности.",
|
||
"contentHtml": "<p>В ревью появляется знакомая фраза: «давайте добавим подпись», «закроем endpoint» или «поставим ограничение». Но никто не может ответить, какие данные защищаются и какой запрос должен быть отклонён. Команда выбирает технологию до того, как описывает угрозу. Ошибка стоит дороже лишней строки кода: контроль может сломать легитимный поток, не закрыть нужное злоупотребление и оставить владельца без доказательства результата.</p>\n<p>Модель угроз нужна не для красивой схемы. Она связывает четыре вещи: ценный актив, источник запроса, границу доверия и действие, которое не должно пройти. Из этой связи следует контроль и проверка. Если связь не записана, «добавить безопасность» остаётся пожеланием. Если проверка не содержит отрицательного случая, команда не знает, работает ли защита.</p>\n<h2>Начните с наблюдаемого симптома</h2>\n<p>Опишите не тревогу, а факт. Например: обработчик принимает запрос на изменение заявки, но контракт не говорит, как он отвергает неподтверждённый вход. Это не доказывает уязвимость. Факт только показывает пробел: у изменения нет явного условия отказа и нет артефакта, который его подтверждает.</p>\n<p>Затем зафиксируйте цену ошибки. Неподписанный запрос может изменить чужую заявку, если другая проверка не перекрывает этот путь. Слишком общий контроль может, наоборот, отвергать запросы клиентов и создавать обходной ручной процесс. В обоих случаях команда спорит о механизме, пока не назвала объект защиты и допустимое поведение.</p>\n<p>Для первого прохода достаточно одного потока. Возьмём учебный пример: browser-client отправляет запрос в public-api-to-handler, handler записывает change-request в хранилище. Asset — заявка на изменение. Boundary — место, где публичный вход становится внутренним вызовом обработчика. Abuse — неподтверждённый запрос пытается изменить заявку. Это условная модель. Она не описывает конкретный продукт и не доказывает безопасность production-системы.</p>\n<pre><code>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: тест показывает отказ такого входа</code></pre>\n<p>Схема полезна только тогда, когда каждый элемент ведёт к вопросу. Asset отвечает, какое свойство нельзя потерять. Boundary показывает, где меняются предположения о доверии. Abuse описывает действие нарушителя. Control формулирует решение. Evidence показывает, что решение проверили. Слово «система» не заменяет ни один из этих элементов.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Диагностика неполной модели угроз</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Назван control, но нет asset</td><td>Выбрали привычный механизм</td><td>Что потеряет свойство при злоупотреблении?</td><td>Назвать один актив и его свойство</td></tr><tr><td>Есть threat, но нет boundary</td><td>Не указано место защитного решения</td><td>Где вход перестаёт быть доверенным?</td><td>Поставить границу на DFD и описать переход</td></tr><tr><td>Есть control, но нет отрицательной ветки</td><td>Проверяли только успешный путь</td><td>Какой вход обязан получить отказ?</td><td>Добавить тест на отказ и ожидаемый результат</td></tr><tr><td>В evidence написано «проверено»</td><td>Не назван метод и артефакт</td><td>Что увидит независимый проверяющий?</td><td>Указать test, log, trace или ручной шаг</td></tr><tr><td>Rollback означает «вернуть всё»</td><td>Модель смешана с поставкой</td><td>Какие файлы, права и данные меняются?</td><td>Разделить исходный snapshot и release-план</td></tr></tbody></table></div>\n<p>Таблица отсекает ложную полноту. Заполненная строка не означает, что риск мал. Она означает, что следующий вопрос имеет адресата и ожидаемый ответ. Если ответ не находится, оставьте поле пустым и остановите выбор контроля. «Неизвестно» полезнее, чем выдуманное «защищено».</p>\n<h2>Сначала граница, потом механизм</h2>\n<p>Проведите границу там, где меняются правила доверия. Для публичного API это может быть вход в handler, но не всегда. Если gateway уже проверяет подпись, а handler получает внутренний вызов, модель должна показать обе границы и владельца каждой проверки. Если вы назвали границей сеть только потому, что она видна на архитектурной схеме, контроль может оказаться не на том участке потока.</p>\n<p>В примере ниже signed-request — лишь учебная гипотеза. Её обещание узкое: handler принимает запрос только после проверяемого подтверждения. Это не синоним шифрования транспорта, аутентификации пользователя или авторизации операции. В настоящем API могут потребоваться другой протокол, nonce, защита от повторной отправки, проверка полномочий и журналирование. Статья не выбирает их за владельца системы.</p>\n<figure><img src=\"/assets/editorial/2023/threat-model-2023-diagnosis-rollback.svg\" alt=\"Маршрут диагностики модели угроз: актив, граница, злоупотребление, контроль, доказательство и откат\" loading=\"lazy\" /><figcaption>Маршрут вопросов для одного потока. Рисунок показывает порядок диагностики, а не карту реального продукта, отчёт сканера или план развёртывания.</figcaption></figure>\n<h2>Проверьте минимальный контракт кодом</h2>\n<p>Маленькая функция может поймать механические пропуски до обсуждения реализации. Она принимает actor, asset, boundary, abuse и один из заранее названных типов контроля. Для принятой записи возвращает evidence и snapshot для учебного отката. Такой код проверяет только структуру модели. Он не ходит в сеть, не проверяет ключ, не вызывает API и не оценивает риск.</p>\n<pre><code>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}</code></pre>\n<p>В этом фрагменте есть намеренный отрицательный путь. Пустой asset, неизвестный control или отсутствие boundary должны вернуть отказ, а не «почти принятую» модель. Название функции и ответ отражают учебный контракт. Не переносите его в production без отдельной проверки требований, реализации, секретов, прав и совместимости.</p>\n<p>После успешной проверки контракта найдите реальную точку потока. Сопоставьте имя asset с полем документации или схемы, boundary — с middleware, gateway или handler, а evidence — с конкретным тестом или журналом. Если сопоставление не получается, локальный PASS ничего не говорит о приложении. Он только говорит, что четыре строки заполнены.</p>\n<h2>Порядок действий</h2>\n<ol><li>Зафиксируйте симптом одной фразой: какой вход, какой обработчик и какое решение сейчас не имеют проверяемого условия.</li><li>Назовите один asset и свойство, которое нужно сохранить. Не используйте «данные» или «безопасность» без уточнения.</li><li>Опишите actor и abuse как действие. Например: внешний клиент повторяет запрос и меняет чужую заявку.</li><li>Нарисуйте границу потока и назначьте владельца решения на каждой стороне. Проверьте, не дублируют ли два слоя одну и ту же проверку.</li><li>Сформулируйте control как наблюдаемое поведение отказа. «Используем подпись» слабее, чем «неподтверждённый запрос получает отказ до записи».</li><li>Прогоните минимальный контракт на полном и неполном входе. Сохраните результат и причину отказа.</li><li>Добавьте проверку реализации с явными данными, окружением и ожидаемым результатом. Отдельно укажите, что тест не проверяет.</li><li>Подготовьте rollback до выпуска. Для модели сохраните snapshot; для продукта опишите обратимые изменения, совместимость, права, ключи и наблюдение.</li><li>Попросите независимого участника воспроизвести проверку по записи. Если ему приходится угадывать вход или критерий PASS, change не готов.</li></ol>\n<h2>Когда путь нужно остановить</h2>\n<p>Остановите выбор контроля, если asset неизвестен или принадлежит нескольким владельцам. Нельзя оценить ущерб, пока не ясно, какое свойство защищается. Остановите работу, если boundary спорна и команда не может показать, где именно принимается решение. В этом случае уточните поток и ответственность, а не добавляйте второй механизм наугад.</p>\n<p>Остановите объявление готовности, если есть только успешный тест. Контроль, который пропускает хороший запрос, ещё не показывает, что плохой запрос получает отказ. Нужны отрицательный вход, ожидаемый код или состояние, а также подтверждение, что запись не изменилась.</p>\n<p>Не называйте локальную функцию security review, pentest или compliance evidence. Она не видит production, реальные роли, конфигурацию, ротацию ключей, повторную отправку, лимиты и операционные журналы. Учебный пример помогает проверить форму рассуждения. Он не заменяет анализ системы и согласование риска.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Один поток готов к следующему этапу, когда независимый проверяющий по записи может назвать asset, actor, boundary и abuse; найти контроль в конкретном месте; запустить отрицательную проверку; увидеть ожидаемый отказ до изменения asset; определить сохранённый snapshot и условия отката. Если хотя бы один пункт требует устного пояснения автора, модель ещё не завершена.</p>\n<p>Критерий не означает «угроз больше нет». Он означает, что команда понимает выбранный риск, границу утверждения и следующий реальный тест. Результат может быть «контроль не выбран», «проверка не выполнена» или «нужен владелец». Это корректные исходы. Они лучше фиктивного PASS, который не связан с поведением приложения.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://owasp.org/www-project-threat-modeling/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Threat Modeling Project</a> — поддерживаемое описание четырёх вопросов и способов вести модель; методику нужно выбирать по контексту системы.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/154/ipd\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-154, Guide to Data-Centric System Threat Modeling</a> — официальный initial public draft; NIST указывает, что документ планируют финализировать, поэтому это не финальный стандарт.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218, Secure Software Development Framework 1.1</a> — официальный финальный документ о практиках безопасной разработки; он не подтверждает безопасность конкретного endpoint или кода.</li></ul>"} |