{ "index": 179, "slug": "editorial-2023-01-mechanism-threat-model", "title": "Модель угроз: от актива к проверяемому контролю", "excerpt": "Как связать актив, злоупотребление, границу и контроль в одну проверяемую модель — с учебным контрактом, отрицательным путём и явными ограничениями.", "contentHtml": "
В ревью появляется знакомый симптом: в изменении уже назван контроль — подпись, шифрование, rate limit или дополнительная проверка, — но никто не может ответить, какой актив он защищает и какой вход должен быть отвергнут. Обсуждение быстро сводится к выбору технологии: один инженер предлагает JWT, другой — mTLS, третий — ещё один middleware. Цена ошибки — ложное закрытие риска. Команда может защитить не ту границу, а при откате не понять, какую гарантию она снимает.
\nМодель угроз помогает вернуть разговор к проверяемой цепочке. Для одного потока нужно назвать участника, актив, границу, злоупотребление, контроль и evidence — наблюдаемое доказательство результата. Название технологии не заменяет ни одного из этих полей. Ниже — учебная модель, которая проверяет полноту записи, а не доказывает безопасность продукта.
\nВозьмём учебный поток: browser-client отправляет change-request через public-api-to-handler, обработчик принимает или отклоняет запрос, затем записывает его в store. Актив — конкретный change-request, а не «данные вообще». Злоупотребление — отправить запрос без действительной подписи. Граница — место, где вход перестаёт быть доверенным. Контроль — решение handler до записи. Evidence — наблюдаемый отказ на неподписанном входе.
\nЭта схема содержит шесть полей, но отвечает на четыре вопроса. Что мы строим — actor, asset и boundary. Что может пойти не так — abuse. Что предпримем — control. Как поймём, что решение достаточно проверено, — evidence. Так модель не смешивает объект защиты, сценарий атаки и способ проверки.
\n| Поле | Учебное значение | Вопрос | Граница вывода |
|---|---|---|---|
| Actor | browser-client | кто формирует вход? | не доказывает личность и права пользователя |
| Asset | change-request | что нужно сохранить? | не заменяет классификацию всех данных |
| Boundary | public-api-to-handler | где меняется доверие к входу? | не описывает полную топологию сети |
| Abuse | unsigned request | какое действие не должно пройти? | не является полным списком атак |
| Control | signed-request | какое условие проверяет handler? | не доказывает достаточность криптографии |
| Evidence | local rejection check | что можно наблюдать? | не является результатом production-проверки или pentest |
Риск здесь — не название уязвимости и не оценка «высокий». Это связка сценария злоупотребления с последствием для актива. Если неподписанный change-request меняет запись, последствием становится потеря целостности этого актива. Контроль должен уменьшать именно такой путь, а evidence — показать, что неподписанный вход действительно не дошёл до записи.
\nКоличественную оценку риска эта заметка не вводит. Для неё потребовались бы вероятность, ущерб, стоимость исправления и согласованный способ приоритизации. Поэтому не стоит превращать слово «risk» в число без метода расчёта. Сначала зафиксируйте путь атаки и свойство актива, затем решайте, нужна ли отдельная оценка.
\nНиже — небольшой JavaScript-валидатор записи модели. Он не создаёт ключи, не проверяет подпись HTTP-запроса и не обращается к базе. Его задача уже: не дать пропустить неполное описание потока или контроль, которого нет в согласованном наборе. Запускать пример можно в Node.js или в консоли браузера.
\nfunction checkThreatRecord(input = {}) {\n const required = ['actor', 'asset', 'boundary', 'abuse', 'control', 'evidence'];\n const missing = required.filter((key) => {\n return typeof input[key] !== 'string' || input[key].trim() === '';\n });\n\n if (missing.length > 0) {\n return { status: 'reject', reason: 'missing: ' + missing.join(', ') };\n }\n\n if (input.control !== 'signed-request') {\n return { status: 'reject', reason: 'control-not-approved' };\n }\n\n return {\n status: 'accept',\n asset: input.asset,\n abuse: input.abuse,\n control: input.control,\n evidence: input.evidence\n };\n}\n\nconst complete = {\n actor: 'browser-client',\n asset: 'change-request',\n boundary: 'public-api-to-handler',\n abuse: 'unsigned request',\n control: 'signed-request',\n evidence: 'local rejection check'\n};\n\nconsole.log(checkThreatRecord(complete).status); // accept\nconsole.log(checkThreatRecord({ ...complete, asset: '' }).reason); // missing: asset\nconsole.log(checkThreatRecord({ ...complete, control: 'rate-limit' }).reason); // control-not-approved\nВызов с полной записью возвращает accept. Пустой asset и неподтверждённый control дают разные причины отказа. Это воспроизводимый результат именно для контракта описания. Он не означает, что реальный handler проверяет подпись, что ключи защищены, что запрос нельзя повторить или что пользователь имеет право изменить запись. Следующий тест должен обращаться к настоящему handler и проверять разрешённый и неподписанный входы отдельно.
Полезный поворот расследования происходит не при выборе нового middleware, а при проверке отрицательного пути. Если запись показывает контроль, но не показывает вход, который он должен отвергнуть, команда ещё не связала защиту с угрозой. Если evidence ограничено словом «проверено», другой инженер не сможет повторить вывод.
\n| Симптом | Проверка | Действие | Что ещё не доказано |
|---|---|---|---|
| Есть control, но нет asset | какой объект теряет свойство? | назвать один asset до выбора механизма | что контроль защищает весь продукт |
| Есть abuse, но нет boundary | где вход меняет статус доверия? | показать границу на потоке | что другие входы устроены так же |
| Есть control, но нет evidence | какой вход должен получить отказ? | добавить тест, лог решения или ручную проверку | что реализация уже исправлена |
| Evidence равно «проверено» | какой вход, результат и артефакт? | записать три этих значения | что тест покрывает все ветви |
| Rollback равно «вернуть всё» | какие поля и ресурсы изменились? | описать границу отката отдельно | что удаление контроля безопасно |
Минимальная модель не ранжирует риск, не считает вероятность, не строит attack tree и не перечисляет все trust zones. Она не проверяет криптографический протокол, identity provider, права доступа, хранение секретов или журналирование. Для персональных данных, платежей и критичных операций понадобятся классификация, владелец риска и независимая проверка.
\nПодпись не заменяет авторизацию. Шифрование канала не доказывает целостность бизнес-операции. Rate limit не решает проблему подмены личности. Нельзя переносить учебное signed-request на реальный API без проверки протокола, ключей, времени жизни, повторной отправки и обработки ошибок. Полная запись модели — условие для следующего этапа, а не сертификат безопасности.
\nOWASP описывает threat modeling как структурированный и повторяемый процесс: сначала моделируют систему, затем выявляют угрозы, выбирают меры и проводят review/validation. В этой рамке отдельно названы потоки данных, хранилища и trust boundaries, поэтому boundary в учебном контракте нельзя заменять общим словом «система».
\nNIST SP 800-154 связывает data-centric threat modeling с четырьмя шагами: определить систему и интересующие данные, выбрать векторы атаки, охарактеризовать меры защиты и зафиксировать результаты. Это Initial Public Draft 2016 года, а страница NIST сообщает о планах финализации; документ нельзя цитировать как завершённый стандарт. Для конкретного проекта его ограничения и актуальность нужно проверять отдельно.
\nМодель готова к следующему этапу, если независимый инженер без устных пояснений называет actor, asset, boundary, abuse, control и evidence; воспроизводит отказ на неподписанном входе; видит однозначный pass или fail; и понимает, что именно откатывается. Это критерий полноты записи. Для реализации нужен отдельный тест настоящего handler с разрешённым и явно неподписанным входом, а его результат должен принадлежать согласованному артефакту проверки.
\n