diff --git a/editorial/agent-rewrites/180.json b/editorial/agent-rewrites/180.json index 41d1e89..7670091 100644 --- a/editorial/agent-rewrites/180.json +++ b/editorial/agent-rewrites/180.json @@ -1,7 +1,7 @@ { "index": 180, "slug": "editorial-2023-01-practice-threat-model", - "title": "Модель угроз до чек-листа: назвать актив, поток и границу", - "excerpt": "Практический способ разобрать один поток до выбора контроля: назвать актив, злоупотребление, границу доверия и проверяемое действие.", - "contentHtml": "

Команда получает задачу: добавить подпись, шифрование или ограничение частоты. Через день в изменении уже есть middleware, но никто не может ответить на два вопроса: какой актив защищает контроль и на какой границе он срабатывает. В продакшене это дорого. Лишняя проверка ломает легитимный поток. Слабая проверка пропускает изменение данных. При инциденте команда не может связать отказ, запись в журнале и исходную угрозу.

\n

Тезис статьи простой: модель угроз начинается не с перечня контролей. Сначала нужно назвать один поток, актив, злоупотребление и границу доверия. Потом выбрать контроль и сразу описать наблюдаемое доказательство его работы. Такой порядок не доказывает безопасность системы. Он делает решение проверяемым и показывает отрицательный путь.

\n

Малый поток вместо общей тревоги

\n

Рассмотрим учебный поток. browser-client отправляет change-request через границу public-api-to-handler. Обработчик записывает запрос в хранилище. Предполагаемое злоупотребление — отправить запрос без корректной подписи. Это не карта реальной сети и не результат аудита. Пример нужен, чтобы проверить способ рассуждения на одном объекте.

\n

В потоке есть пять разных понятий. Актив — объект, потеря или изменение которого имеет цену. Актор — источник действия. Граница доверия — место, где нельзя переносить прежнее предположение о входе. Злоупотребление — конкретное нежелательное действие. Контроль — правило, которое должно изменить это действие или его последствия.

\n
Минимальная запись одного потока
ПолеУчебное значениеПроверочный вопросЧего запись не доказывает
Акторbrowser-clientКто формирует вход?Личность пользователя
Активchange-requestЧто нельзя потерять или исказить?Классификацию всех данных
Границаpublic-api-to-handlerГде вход перестаёт быть доверенным?Полную топологию сети
ЗлоупотреблениеЗапрос без подписиКакое действие нужно остановить?Полный список атак
КонтрольПроверка подписиКакое решение меняет поток?Достаточность реализации
ДоказательствоОтказ неподписанного запросаЧто можно наблюдать?Результат проверки продакшена
\n

Механизм: граница задаёт место решения

\n

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

\n

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

\n

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

\n
\"Учебный
Схема связывает источник, актив, границу, злоупотребление, контроль и доказательство. Она не показывает реальную сеть, ключи, роли, журналирование или результат аудита.
\n

Конкретный пример: строгая запись модели

\n

Ниже приведён учебный JavaScript-код. Он только проверяет полноту записи в памяти. Он не создаёт ключи, не проверяет криптографическую подпись и не обращается к HTTP или хранилищу. Его задача — не дать пропустить пустой актив, неизвестную границу или контроль без названного условия.

\n
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);
\n

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

\n

Отрицательный путь важнее удачного примера

\n

Если поле asset пустое, функция должна вернуть missing-asset. Если граница не названа, результатом будет missing-boundary. Если вместо явного контроля передать строку encrypt-everything, модель должна вернуть unknown-control. Она не должна угадывать, что автор имел в виду. Молчаливое угадывание превращает неполное описание в ложное согласие.

\n

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

\n

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

\n
Диагностика неполной модели угроз
СимптомПричинаПроверкаДействие
В PR написано «добавить подпись»Контроль выбран раньше активаПопросить назвать объект, который изменится при атакеЗаписать актив и операцию отдельно
Все говорят о сервисе целикомНе названа граница потокаНарисовать источник, процесс, хранилище и переход доверияПоставить решение на конкретный переход
Есть только happy pathНе описан вход, который должен быть отклонёнПередать пустую или неверную подписьЗафиксировать код отказа и отсутствие записи
Тест проверяет ответ, но не состояниеКонтроль отделён от последствияПроверить, что запрос не попал в storeДобавить проверку побочного эффекта
В журнале написано «security check failed»Событие нельзя связать с потокомНайти correlation ID и безопасные поля контекстаДобавить наблюдаемое решение без секрета
Ссылка на стандарт заменяет решениеКаталог принят за проект системыСпросить, какое требование применено к этому активуОставить ссылку как источник, а не как доказательство
\n

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

\n
  1. Выберите один поток. Ограничьте разбор одной операцией: например, отправкой изменения профиля или созданием платежного поручения. Не начинайте с инвентаризации всего продукта.
  2. Назовите актив. Запишите конкретный объект и нежелательное свойство: потеря конфиденциальности, целостности или доступности.
  3. Проведите границу. Укажите место, где данные становятся входом для следующего компонента. Название должно быть понятным без знания внутреннего жаргона.
  4. Сформулируйте злоупотребление. Опишите действие, а не абстрактную «атаку»: отправить запрос без подписи, повторить старый запрос или изменить чужой идентификатор.
  5. Выберите контроль. Свяжите его с условием отказа. Если контроль не меняет описанный поток, вернитесь к границе и активу.
  6. Назначьте доказательство. Выберите тест, лог, ручную проверку или иной наблюдаемый результат. Он должен показывать именно выбранное условие, а не общий статус сборки.
  7. Проверьте побочный эффект. Для отказа убедитесь, что данные не записались, сообщение не ушло в очередь и повторная попытка не получила новый побочный эффект без основания.
  8. Разберите ограничения. Запишите, что остаётся за пределами модели: ключи, права, конкурирующие запросы, прокси, кеши и операционный откат.
\n

Ограничения и отрицательные выводы

\n

Малый DFD не перечисляет все активы и злоупотребления. Он не ранжирует риск и не выбирает владельца решения. Он не проверяет настройки прокси, identity provider, хранилища ключей, очереди и сетевые ACL. Если поток зависит от повторов, параллельных запросов или доставки через несколько доменов, одной строки о подписи недостаточно.

\n

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

\n

Не называйте локальную проверку security review. Не называйте отказ одного тестового запроса доказательством отсутствия уязвимостей. Считайте модель готовой только для следующего шага, а не для закрытия всей темы.

\n

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

\n

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

\n

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

\n

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

\n" + "title": "Модель угроз перед чек-листом: поток, актив и граница", + "excerpt": "Практический способ разобрать один поток до выбора контроля: назвать актив, злоупотребление, границу доверия и наблюдаемое доказательство.", + "contentHtml": "

Команда получает задачу: добавить подпись, шифрование или ограничение частоты. Через день в изменении уже есть middleware, но никто не может ответить на два вопроса: какой актив защищает контроль и на какой границе он срабатывает. В продакшене это дорого. Лишняя проверка ломает легитимный поток. Слабая проверка пропускает изменение данных. При инциденте команда не может связать отказ, запись в журнале и исходную угрозу.

\n

Модель угроз начинается не с перечня контролей. Сначала назовите один поток, актив, злоупотребление и границу доверия. Затем выберите контроль и опишите наблюдаемое доказательство его работы. Такой порядок не доказывает безопасность системы, но делает решение проверяемым и заранее показывает отрицательный путь.

\n

Малый поток вместо общей тревоги

\n

Рассмотрим учебный поток. browser-client отправляет change-request через границу public-api-to-handler. Обработчик записывает запрос в хранилище. Предполагаемое злоупотребление — отправить запрос без корректной подписи. Это не карта реальной сети и не результат аудита. Пример нужен, чтобы проверить способ рассуждения на одном объекте.

\n

В потоке есть пять разных понятий. Актив — объект, потеря или изменение которого имеет цену. Актор — источник действия. Граница доверия — место, где нельзя переносить прежнее предположение о входе. Злоупотребление — конкретное нежелательное действие. Контроль — правило, которое должно изменить это действие или его последствия.

\n
Минимальная запись одного потока
ПолеУчебное значениеПроверочный вопросЧего запись не доказывает
Акторbrowser-clientКто формирует вход?Личность пользователя
Активchange-requestЧто нельзя потерять или исказить?Классификацию всех данных
Границаpublic-api-to-handlerГде вход перестаёт быть доверенным?Полную топологию сети
ЗлоупотреблениеЗапрос без подписиКакое действие нужно остановить?Полный список атак
КонтрольПроверка подписиКакое решение меняет поток?Достаточность реализации
ДоказательствоОтказ неподписанного запросаЧто можно наблюдать?Результат проверки продакшена
\n

Механизм: граница задаёт место решения

\n

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

\n

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

\n

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

\n
\"Учебный
Схема связывает источник, актив, границу, злоупотребление, контроль и доказательство. Она не показывает реальную сеть, ключи, роли, журналирование или результат аудита.
\n

Конкретный пример: строгая запись модели

\n

Ниже приведён учебный JavaScript-код. Он только проверяет полноту записи в памяти. Он не создаёт ключи, не проверяет криптографическую подпись и не обращается к HTTP или хранилищу. Его задача — не дать пропустить пустой актив, неизвестную границу или контроль без названного условия.

\n
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); // unsigned request is rejected\n\nconst rejected = planThreatModel({\n  actor: 'browser-client',\n  asset: '',\n  boundary: 'public-api-to-handler',\n  abuse: 'send request without a valid signature',\n  control: 'signed-request'\n});\n\nconsole.log(rejected.reason); // missing-asset
\n

Положительный результат означает только то, что все поля заполнены и контроль известен модели; в примере выводится true. Отрицательный вызов с пустым asset выводит missing-asset. Это не означает, что подпись действительно проверяется. Для такого вывода нужен отдельный тест обработчика с форматом подписи, ключом, временем действия и ожидаемым кодом отказа. Учебный пример намеренно не прячет эти границы за общим словом «безопасно».

\n

Отрицательный путь важнее удачного примера

\n

Если поле asset пустое, функция должна вернуть missing-asset. Если граница не названа, результатом будет missing-boundary. Если вместо явного контроля передать строку encrypt-everything, модель должна вернуть unknown-control. Она не должна угадывать, что автор имел в виду. Молчаливое угадывание превращает неполное описание в ложное согласие.

\n

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

\n

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

\n
Диагностика неполной модели угроз
СимптомПричинаПроверкаДействие
В PR написано «добавить подпись»Контроль выбран раньше активаПопросить назвать объект, который изменится при атакеЗаписать актив и операцию отдельно
Все говорят о сервисе целикомНе названа граница потокаНарисовать источник, процесс, хранилище и переход доверияПоставить решение на конкретный переход
Есть только happy pathНе описан вход, который должен быть отклонёнПередать пустую или неверную подписьЗафиксировать код отказа и отсутствие записи
Тест проверяет ответ, но не состояниеКонтроль отделён от последствияПроверить, что запрос не попал в storeДобавить проверку побочного эффекта
В журнале написано «security check failed»Событие нельзя связать с потокомНайти correlation ID и безопасные поля контекстаДобавить наблюдаемое решение без секрета
Ссылка на стандарт заменяет решениеКаталог принят за проект системыСпросить, какое требование применено к этому активуОставить ссылку как источник, а не как доказательство
\n

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

\n
  1. Выберите один поток. Ограничьте разбор одной операцией: например, отправкой изменения профиля или созданием платежного поручения. Не начинайте с инвентаризации всего продукта.
  2. Назовите актив. Запишите конкретный объект и нежелательное свойство: потеря конфиденциальности, целостности или доступности.
  3. Проведите границу. Укажите место, где данные становятся входом для следующего компонента. Название должно быть понятным без знания внутреннего жаргона.
  4. Сформулируйте злоупотребление. Опишите действие, а не абстрактную «атаку»: отправить запрос без подписи, повторить старый запрос или изменить чужой идентификатор.
  5. Выберите контроль. Свяжите его с условием отказа. Если контроль не меняет описанный поток, вернитесь к границе и активу.
  6. Назначьте доказательство. Выберите тест, лог, ручную проверку или иной наблюдаемый результат. Он должен показывать именно выбранное условие, а не общий статус сборки.
  7. Проверьте побочный эффект. Для отказа убедитесь, что данные не записались, сообщение не ушло в очередь и повторная попытка не получила новый побочный эффект без основания.
  8. Разберите ограничения. Запишите, что остаётся за пределами модели: ключи, права, конкурирующие запросы, прокси, кеши и операционный откат.
\n

Ограничения и отрицательные выводы

\n

Малый DFD не перечисляет все активы и злоупотребления. Он не ранжирует риск и не выбирает владельца решения. Он не проверяет настройки прокси, identity provider, хранилища ключей, очереди и сетевые ACL. Если поток зависит от повторов, параллельных запросов или доставки через несколько доменов, одной строки о подписи недостаточно.

\n

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

\n

Не называйте локальную проверку security review. Не называйте отказ одного тестового запроса доказательством отсутствия уязвимостей. Считайте модель готовой только для следующего шага, а не для закрытия всей темы.

\n

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

\n

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

\n

Это компактная рабочая запись, а не обязательный стандартный формат. OWASP описывает threat model через предмет моделирования, проверяемые допущения, угрозы, меры и способ валидации. NIST SP 800-154 рассматривает threat modeling как оценку риска для выбранного объекта; наши шесть полей лишь переводят эту идею в короткую карточку одного потока.

\n

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

\n

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

\n" }