8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
||
"index": 180,
|
||
"slug": "editorial-2023-01-practice-threat-model",
|
||
"title": "Модель угроз до чек-листа: назвать актив, поток и границу",
|
||
"excerpt": "Практический способ разобрать один поток до выбора контроля: назвать актив, злоупотребление, границу доверия и проверяемое действие.",
|
||
"contentHtml": "<p>Команда получает задачу: добавить подпись, шифрование или ограничение частоты. Через день в изменении уже есть middleware, но никто не может ответить на два вопроса: какой актив защищает контроль и на какой границе он срабатывает. В продакшене это дорого. Лишняя проверка ломает легитимный поток. Слабая проверка пропускает изменение данных. При инциденте команда не может связать отказ, запись в журнале и исходную угрозу.</p>\n<p>Тезис статьи простой: модель угроз начинается не с перечня контролей. Сначала нужно назвать один поток, актив, злоупотребление и границу доверия. Потом выбрать контроль и сразу описать наблюдаемое доказательство его работы. Такой порядок не доказывает безопасность системы. Он делает решение проверяемым и показывает отрицательный путь.</p>\n<h2>Малый поток вместо общей тревоги</h2>\n<p>Рассмотрим учебный поток. <code>browser-client</code> отправляет <code>change-request</code> через границу <code>public-api-to-handler</code>. Обработчик записывает запрос в хранилище. Предполагаемое злоупотребление — отправить запрос без корректной подписи. Это не карта реальной сети и не результат аудита. Пример нужен, чтобы проверить способ рассуждения на одном объекте.</p>\n<p>В потоке есть пять разных понятий. Актив — объект, потеря или изменение которого имеет цену. Актор — источник действия. Граница доверия — место, где нельзя переносить прежнее предположение о входе. Злоупотребление — конкретное нежелательное действие. Контроль — правило, которое должно изменить это действие или его последствия.</p>\n<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>Актор</td><td><code>browser-client</code></td><td>Кто формирует вход?</td><td>Личность пользователя</td></tr><tr><td>Актив</td><td><code>change-request</code></td><td>Что нельзя потерять или исказить?</td><td>Классификацию всех данных</td></tr><tr><td>Граница</td><td><code>public-api-to-handler</code></td><td>Где вход перестаёт быть доверенным?</td><td>Полную топологию сети</td></tr><tr><td>Злоупотребление</td><td>Запрос без подписи</td><td>Какое действие нужно остановить?</td><td>Полный список атак</td></tr><tr><td>Контроль</td><td>Проверка подписи</td><td>Какое решение меняет поток?</td><td>Достаточность реализации</td></tr><tr><td>Доказательство</td><td>Отказ неподписанного запроса</td><td>Что можно наблюдать?</td><td>Результат проверки продакшена</td></tr></tbody></table>\n<h2>Механизм: граница задаёт место решения</h2>\n<p>Граница доверия не обязана совпадать с границей сервиса. Она появляется там, где обработчик меняет отношение к данным. До границы запрос принадлежит внешнему актору. После неё приложение может использовать его для записи, очереди или вызова другого сервиса. Если обработчик принимает тело запроса как уже проверенное, злоумышленник получает возможность подменить актив ещё до проверки.</p>\n<p>Поэтому контроль нужно ставить рядом с переходом, который он защищает. Проверка подписи на входе отвечает на один вопрос: можно ли принимать этот запрос как созданный доверенным источником? Она не отвечает на другие вопросы. Подпись не проверяет право пользователя изменить конкретный объект. Она не заменяет валидацию схемы, защиту от повторной отправки и журналирование решения.</p>\n<p>Контроль становится полезным, когда его связывают с условием отказа. В нашем примере условие выглядит так: запрос пересёк границу, но подпись отсутствует или не проходит проверку. Ожидаемое действие — не передавать запрос обработчику записи. Доказательство — наблюдаемый ответ с отказом и, если это предусмотрено политикой, запись причины без секретов.</p>\n<figure><img src=\"/assets/editorial/2023/threat-model-2023-flow.svg\" alt=\"Учебный поток модели угроз: browser-client отправляет change-request через границу public-api-to-handler, handler записывает его в store, а неподписанный запрос отклоняется контролем signed-request\" loading=\"lazy\" /><figcaption>Схема связывает источник, актив, границу, злоупотребление, контроль и доказательство. Она не показывает реальную сеть, ключи, роли, журналирование или результат аудита.</figcaption></figure>\n<h2>Конкретный пример: строгая запись модели</h2>\n<p>Ниже приведён учебный JavaScript-код. Он только проверяет полноту записи в памяти. Он не создаёт ключи, не проверяет криптографическую подпись и не обращается к HTTP или хранилищу. Его задача — не дать пропустить пустой актив, неизвестную границу или контроль без названного условия.</p>\n<pre><code>function planThreatModel(input) {\n const required = ['actor', 'asset', 'boundary', 'abuse'];\n\n for (const field of required) {\n if (typeof input?.[field] !== 'string' || !input[field].trim()) {\n return { accepted: false, reason: 'missing-' + field };\n }\n }\n\n if (input.control !== 'signed-request') {\n return { accepted: false, reason: 'unknown-control' };\n }\n\n return {\n accepted: true,\n flow: {\n source: input.actor,\n asset: input.asset,\n boundary: input.boundary,\n abuse: input.abuse\n },\n control: {\n id: input.control,\n evidence: 'unsigned request is rejected'\n }\n };\n}\n\nconst plan = planThreatModel({\n actor: 'browser-client',\n asset: 'change-request',\n boundary: 'public-api-to-handler',\n abuse: 'send request without a valid signature',\n control: 'signed-request'\n});\n\nconsole.log(plan.accepted); // true\nconsole.log(plan.control.evidence);</code></pre>\n<p>Положительный результат означает только то, что все поля заполнены и контроль известен модели. Он не означает, что подпись действительно проверяется. Для этого нужен отдельный тест обработчика с конкретным форматом подписи, ключом, временем действия и ожидаемым кодом отказа. Учебный пример намеренно не прячет эти границы за общим словом «безопасно».</p>\n<h2>Отрицательный путь важнее удачного примера</h2>\n<p>Если поле <code>asset</code> пустое, функция должна вернуть <code>missing-asset</code>. Если граница не названа, результатом будет <code>missing-boundary</code>. Если вместо явного контроля передать строку <code>encrypt-everything</code>, модель должна вернуть <code>unknown-control</code>. Она не должна угадывать, что автор имел в виду. Молчаливое угадывание превращает неполное описание в ложное согласие.</p>\n<p>В реальном приложении отрицательный путь проходит дальше. Неподписанный запрос должен остановиться до записи в хранилище. Ответ не должен раскрывать секрет, внутренний идентификатор ключа или причину, которая помогает перебору. Журнал должен содержать достаточно данных для расследования: время, тип решения, корреляционный идентификатор и безопасный контекст. Формат зависит от системы. Важно сначала назвать ожидаемое поведение.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<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>В PR написано «добавить подпись»</td><td>Контроль выбран раньше актива</td><td>Попросить назвать объект, который изменится при атаке</td><td>Записать актив и операцию отдельно</td></tr><tr><td>Все говорят о сервисе целиком</td><td>Не названа граница потока</td><td>Нарисовать источник, процесс, хранилище и переход доверия</td><td>Поставить решение на конкретный переход</td></tr><tr><td>Есть только happy path</td><td>Не описан вход, который должен быть отклонён</td><td>Передать пустую или неверную подпись</td><td>Зафиксировать код отказа и отсутствие записи</td></tr><tr><td>Тест проверяет ответ, но не состояние</td><td>Контроль отделён от последствия</td><td>Проверить, что запрос не попал в store</td><td>Добавить проверку побочного эффекта</td></tr><tr><td>В журнале написано «security check failed»</td><td>Событие нельзя связать с потоком</td><td>Найти correlation ID и безопасные поля контекста</td><td>Добавить наблюдаемое решение без секрета</td></tr><tr><td>Ссылка на стандарт заменяет решение</td><td>Каталог принят за проект системы</td><td>Спросить, какое требование применено к этому активу</td><td>Оставить ссылку как источник, а не как доказательство</td></tr></tbody></table>\n<h2>Порядок действий</h2>\n<ol><li><strong>Выберите один поток.</strong> Ограничьте разбор одной операцией: например, отправкой изменения профиля или созданием платежного поручения. Не начинайте с инвентаризации всего продукта.</li><li><strong>Назовите актив.</strong> Запишите конкретный объект и нежелательное свойство: потеря конфиденциальности, целостности или доступности.</li><li><strong>Проведите границу.</strong> Укажите место, где данные становятся входом для следующего компонента. Название должно быть понятным без знания внутреннего жаргона.</li><li><strong>Сформулируйте злоупотребление.</strong> Опишите действие, а не абстрактную «атаку»: отправить запрос без подписи, повторить старый запрос или изменить чужой идентификатор.</li><li><strong>Выберите контроль.</strong> Свяжите его с условием отказа. Если контроль не меняет описанный поток, вернитесь к границе и активу.</li><li><strong>Назначьте доказательство.</strong> Выберите тест, лог, ручную проверку или иной наблюдаемый результат. Он должен показывать именно выбранное условие, а не общий статус сборки.</li><li><strong>Проверьте побочный эффект.</strong> Для отказа убедитесь, что данные не записались, сообщение не ушло в очередь и повторная попытка не получила новый побочный эффект без основания.</li><li><strong>Разберите ограничения.</strong> Запишите, что остаётся за пределами модели: ключи, права, конкурирующие запросы, прокси, кеши и операционный откат.</li></ol>\n<h2>Ограничения и отрицательные выводы</h2>\n<p>Малый DFD не перечисляет все активы и злоупотребления. Он не ранжирует риск и не выбирает владельца решения. Он не проверяет настройки прокси, identity provider, хранилища ключей, очереди и сетевые ACL. Если поток зависит от повторов, параллельных запросов или доставки через несколько доменов, одной строки о подписи недостаточно.</p>\n<p>Подпись также имеет условия применимости. Нужно определить, кто подписывает запрос, как выбирают ключ, как действуют при ротации, какой срок допустим и как предотвращают повторное использование сообщения. Если система уже доверяет серверному каналу, подпись может решать другую задачу или быть лишней. Нельзя переносить учебный контроль в продакшен только потому, что он выглядит конкретно.</p>\n<p>Не называйте локальную проверку security review. Не называйте отказ одного тестового запроса доказательством отсутствия уязвимостей. Считайте модель готовой только для следующего шага, а не для закрытия всей темы.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Один поток можно считать подготовленным к инженерной проверке, если другой разработчик без устных пояснений находит в записи актор, актив, границу, злоупотребление, контроль и доказательство. Для отрицательного входа указаны ожидаемый отказ и отсутствие побочного эффекта. Для контроля названо хотя бы одно ограничение применимости. Для настоящей системы отдельно проверены формат входа, права, ключи и конкурентные переходы.</p>\n<p>Практический тест готовности короткий: передайте запись коллеге, который не видел обсуждение. Попросите его указать, где остановится неподписанный запрос и какое состояние останется неизменным. Если ответы расходятся, модель ещё не описывает контракт. Если ответы совпадают, можно переходить к реализации и отдельным проверкам границы.</p>\n<h2>Проверяемые источники</h2>\n<ul><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> — официальный документ о построении модели угроз вокруг защищаемых данных и сценариев риска. Он не выбирает контроль за конкретную систему.</li><li><a href=\"https://owasp.org/www-community/Threat_Modeling\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP: Threat Modeling</a> — официальное описание практики, связывающее активы, угрозы и меры защиты. Страница не заменяет проверку реализации.</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</a> — официальный SSDF с практиками, которые помогают встроить моделирование и проверку в разработку. Он не подтверждает безопасность учебного кода.</li></ul>"
|
||
}
|