Диаграммы потоков данных и границы доверия
Диаграмма потоков данных превращает систему в элементы и линии, которые данные пересекают между ними. Границы доверия — это линии, где меняется владелец или привилегия и где концентрируются угрозы. DFD делает STRIDE-по-элементам системой, а не догадками.
Команда правильно построила модель угроз для оформления заказа: они перечислили STRIDE-угрозы для каждой коробки на доске. Через полгода их взломали через сервис рекомендаций — внутренний gRPC-эндпоинт, который «вызывают только другие сервисы». Никто не нарисовал линию там, где запрос на самом деле зарождался, поэтому никто не спросил, достижим ли этот сервис из открытого интернета. Он был достижим: неправильно настроенный ingress-маршрут открывал его напрямую, и сервис доверял любому вызывающему, потому что на диаграмме жил безопасно «внутри». Угрозы упустили не потому, что команда не знала STRIDE. Их упустили потому, что граница доверия на диаграмме была нарисована не в том месте — или не нарисована вовсе.
К концу этого урока ты сможешь нарисовать диаграмму потоков данных реального веб-приложения, разместить границы доверия там, где реально меняется владелец или привилегия, и использовать эти пересечения, чтобы вести STRIDE по элементам, а не смотреть на пустой лист.
Четыре элемента диаграммы потоков данных
STRIDE даёт виды угроз — спуфинг, подмену (tampering), отрицание (repudiation), раскрытие информации, отказ в обслуживании, повышение привилегий. Но STRIDE, применённый ко «всей системе», — это туман. Чтобы сделать его системным, сначала нужна модель того, как данные реально движутся, и стандартная модель — это диаграмма потоков данных (DFD). DFD намеренно крошечная: в ней ровно четыре типа элементов, и каждая коробка на твоей доске — один из них:
- Внешняя сущность — актор вне твоего контроля, который порождает или принимает данные: браузер, мобильное приложение, API партнёра, человек. Ты не запускаешь их код; ты можешь только проверять то, что они передают. Рисуется прямоугольником.
- Процесс — код, который запускаешь ты и который преобразует данные: API-сервер, Lambda, фоновый воркер, парсер. Здесь живёт логика — а значит, и большинство багов. Рисуется кругом.
- Хранилище данных — место, где данные покоятся: таблица Postgres, бакет S3, кэш Redis, файл логов, очередь сообщений в покое. Рисуется двумя параллельными линиями.
- Поток данных — стрелка, соединяющая любые два из перечисленных: HTTP-запрос, SQL-запрос, публикация в Kafka. Поток несёт данные, и именно поток атакующий наблюдает или подменяет в транзите. Рисуется направленным ребром.
Это весь словарь. Сила не в коробках — она в том, что тебя заставляют нарисовать каждую стрелку, потому что каждая стрелка — место, где данные раскрыты, а каждая коробка — место, где данным доверяют или их преобразуют. DFD, которую ты не можешь нарисовать, — это система, которую ты на самом деле не понимаешь.
Границы доверия: где живут угрозы
Теперь наложи одну аннотацию, которая превращает DFD из документации в инструмент моделирования угроз: границу доверия. Граница доверия — это линия, которую ты проводишь поперёк потоков данных везде, где данные переходят от одного субъекта (principal), уровня привилегий или домена владения к другому. Конкретно, граница стоит в каждой точке, где меняется ответ на вопрос «кто контролирует данные по эту сторону?»:
- Между браузером и твоим API (недоверенный клиент → твой код).
- Между твоим процессом API и базой данных (привилегия приложения → привилегия данных; SQL-инъекция здесь — это пересечение границы).
- Между твоим кодом и сторонним сервисом (твои данные покидают твой контроль).
- Между двумя твоими собственными микросервисами, когда они работают на разных уровнях привилегий или доверия.
- Между низкодоверенным процессом в DMZ и высокодоверенным внутренним.
Причина, по которой границы так важны, столь же статистическая, сколь и концептуальная: угрозы концентрируются на пересечениях. Данные, которые никогда не покидают одну зону доверия, сравнительно безопасны — они уже внутри контроля одного субъекта. Опасные моменты — это передачи, потому что именно там одна сторона должна решить, доверять ли другой, и неверное решение есть уязвимость. Спуфинг происходит при пересечении в твою границу аутентификации; подмена и раскрытие — на потоках, пересекающих границу; повышение привилегий — когда процесс на доверенной стороне принимает ввод, который следовало считать недоверенным. Граница — это адрес, по которому каждая буква STRIDE приходит за сбором.
Поверхность атаки: сумма всех пересечений
Когда границы проведены, поверхность атаки вытекает из диаграммы почти бесплатно. Анализ поверхности атаки, в формулировке OWASP, — это работа по перечислению каждой точки, где атакующий может попытаться ввести данные в систему или извлечь их из неё, и на DFD эти точки — ровно те потоки данных, которые пересекают границу доверия. Каждое такое пересечение — точка входа или выхода: форма логина, эндпоинт API, загрузка файла, приёмник вебхуков, исходящий вызов к третьей стороне, очередь, из которой читает твой код.
Старший разработчик относится к поверхности атаки как к чему-то, что измеряют и сокращают, а не просто инвентаризируют. Cheat sheet прямо говорит о компромиссе: больше точек входа, больше привилегированного кода и больше доверенных внешних зависимостей — всё это увеличивает поверхность, и именно поверхность, а не число строк кода, атакующий реально прощупывает. Поэтому ты спрашиваешь по каждому пересечению: должен ли этот эндпоинт вообще существовать? Может ли он требовать аутентификацию? Может ли он работать с меньшими привилегиями? Эндпоинт, который ты удалил, нельзя проэксплуатировать; внутренний сервис, который ты сделал недостижимым извне, убирает целую границу с карты атакующего. Сокращение поверхности — самый дешёвый контроль, которым ты владеешь, потому что оно убирает угрозу, а не смягчает её.
▸Почему это работает
Почему круги, прямоугольники и параллельные линии, а не просто подписанные коробки? Формы — не украшение: они кодируют, кто запускает код. Прямоугольник (внешняя сущность) значит «не твой код, проверяй всё, что он шлёт». Круг (процесс) значит «твой код, где живут логические баги». Параллельные линии (хранилище) значат «данные в покое, спроси о шифровании и контроле доступа». Когда ты неправильно подписываешь коробку — скажем, рисуешь сервис партнёра как процесс, потому что он ощущается «частью системы», — ты тихо двигаешь границу доверия, и стёртая граница — это именно та, через которую атакующий проходит насквозь. Нотация — это форсирующая функция честности о том, что ты на самом деле контролируешь.
От DFD к STRIDE-по-элементам
Вот выгода, ради которой стоит рисовать диаграмму: STRIDE-по-элементам. Вместо свободного брейншторма угроз ты методично обходишь DFD и применяешь только те буквы STRIDE, которые каждый тип элемента реально может пострадать. Тип элемента подсказывает, какие буквы вообще в игре, что схлопывает открытый брейншторм в конечный, проверяемый чек-лист:
- Внешние сущности уязвимы к S и R (спуфинг, отрицание) — может ли кто-то выдать себя за этого актора и может ли актор отрицать действие?
- Процессы могут пострадать от всего набора STRIDE — это самая богатая мишень, особенно T, I и E (подмена, раскрытие, повышение).
- Хранилища данных в основном подвержены T, R, I, D (подмена, отрицание при слабом логировании, раскрытие, отказ в обслуживании).
- Потоки данных подвержены T, I, D (подмена, раскрытие, отказ) — именно поэтому поток, пересекающий границу, заслуживает TLS, проверок целостности и rate-лимитов.
Ты применяешь это строже всего на пересечениях границ. Поток данных, остающийся внутри одной зоны доверия, тоже проверяют, но поток, пересекающий границу, — там, где подмена и раскрытие наиболее вероятны и опасны, поэтому он заслуживает самого глубокого разбора. DFD плюс границы плюс отображение «тип элемента → STRIDE» — это и есть весь движок: он превращает «что может пойти не так?» — вопрос, рождающий панику или махание руками, — в «для каждого элемента какие из этих конкретных букв применимы и контролируется ли пересечение?».
Твоя DFD показывает внутренний gRPC-процесс `recommendations`, который «должны» вызывать только другие сервисы, поэтому команда нарисовала его внутри доверенной зоны без границы на входящем потоке. Как модель угроз должна с ним поступить?
| Элемент DFD | Форма | Применимый STRIDE | Вопрос на его границе |
|---|---|---|---|
| Внешняя сущность | Прямоугольник | S, R | Можно ли выдать себя за неё? Может ли она отрицать действие? |
| Процесс | Круг | S, T, R, I, D, E (все) | Доверяет ли вводу, которому не должен? Можно ли повысить? |
| Хранилище данных | Параллельные линии | T, R, I, D | Шифруется в покое? Контроль доступа? Аудит-лог? |
| Поток данных | Стрелка | T, I, D | TLS? Проверка целостности? Rate-лимит на пересечении? |
Сбойный режим: граница, которую ты не нарисовал
Инцидент с рекомендациями из хука — это канонический сбой DFD, и у него есть имя, которое стоит усвоить: пропущенная граница доверия. Он почти никогда не выглядит как некомпетентность. Команда сделала STRIDE; они просто смоделировали систему как развёрнутые намерения, а не реальную достижимость. Внутренний сервис был «внутренним», поэтому его нарисовали внутри доверенной зоны, поэтому он стоял ни на какой границе, поэтому STRIDE-по-элементам так и не спросил «можно ли выдать себя за этого вызывающего?» — и контроль, которого этот вопрос потребовал бы (аутентифицировать каждого вызывающего, наименьшие привилегии), так и не построили.
Дисциплина, которая это предотвращает: рисуй границы там, где данные реально могут пересечь, а не там, где ты намереваешься. Каждый раз, размещая коробку внутри доверенной зоны, задавай враждебный вопрос — «а что, если это достижимо извне?» — и проверяй достижимость по реальному конфигу (правила ingress, security groups, политики service mesh), а не по слайду архитектуры. Сеть, которую ты считаешь сегментированной, но никогда не проверял, — это плоская сеть с лишними шагами; классическая атака SSRF-к-метаданным (сервер обманом заставляют сходить за http://169.254.169.254/) — это ровно использование твоего же процесса для пересечения границы, до которой ты считал атакующего недосягаемым. Граница на диаграмме — это утверждение о мире. Если ты не проверил утверждение, ты не нарисовал границу — ты нарисовал желание.
На диаграмме потоков данных что именно определяет, где должна быть проведена граница доверия?
Почему STRIDE-по-элементам начинается с ТИПА элемента DFD (внешняя сущность, процесс, хранилище, поток), а не просто с брейншторма угроз?
Расставь шаги построения модели угроз из системы, от первого к последнему:
- 1 Нарисовать DFD: каждую внешнюю сущность, процесс, хранилище и поток данных
- 2 Разместить границы доверия там, где реально меняется владелец или привилегия
- 3 Перечислить поверхность атаки: потоки, пересекающие границу
- 4 Применить STRIDE по элементам, строже всего на пересечениях
- 5 Решить, какие контроли или сокращение поверхности нужны каждой неустранённой угрозе
- 01Назови четыре элемента диаграммы потоков данных и объясни, что такое граница доверия и почему на ней концентрируются угрозы.
- 02Как DFD ведёт STRIDE-по-элементам и что такое сбой «пропущенной границы доверия»?
Диаграмма потоков данных моделирует систему всего четырьмя элементами — внешняя сущность (прямоугольник), процесс (круг), хранилище данных (параллельные линии) и поток данных (стрелка) — и заставляет тебя нарисовать каждую стрелку, потому что каждая стрелка это место, где данные раскрыты, а каждая коробка — место, где им доверяют или их преобразуют. Границы доверия — это линии, которые ты проводишь поперёк потоков везде, где меняется владелец или привилегия: браузер→API, API→БД, твой код→третья сторона или между внутренними сервисами на разных уровнях доверия. Угрозы концентрируются на этих пересечениях, потому что пересечение — там, где одна сторона решает, доверять ли другой, и неверное решение это баг. Границы делают поверхность атаки видимой (каждое пересечение — точка входа/выхода, которую надо аутентифицировать, лишить привилегий или удалить) и делают STRIDE системным: по типу элемента ты точно знаешь, какие буквы применимы, и проверяешь их строже всего на пересечениях. Сбой, который взламывает команды, сделавшие всё остальное правильно, — это пропущенная граница: моделирование сервиса по тому, куда ты намереваешься направить трафик, вместо того, куда он реально может дотянуться. Поэтому, рисуя коробку внутри доверенной зоны, задай враждебный вопрос и проверь его по реальному конфигу: действительно ли это недостижимо извне — или я просто нарисовал желание?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.