Деревья атак и abuse-кейсы: думать как атакующий
Дерево атак раскладывает одну цель атакующего на AND/OR-подцели вплоть до листьев со стоимостью, а abuse-кейс пишет «злую» пользовательскую историю рядом с настоящей. Это взгляд от цели, тогда как STRIDE — от элемента.
Инженер по безопасности кладёт на стол STRIDE-таблицу: тридцать строк, по одной на каждый элемент потока данных, в каждой добросовестно заполнены все категории угроз. Все кивают. И тут кто-то задаёт единственный важный вопрос — «а какой самый дешёвый способ на самом деле украсть залогиненную сессию?» — и таблица замолкает. STRIDE сказал, какие элементы можно атаковать; он так и не связал их в путь с ценником. Атакующему плевать на ваш список элементов. У него одна цель, и он пойдёт по той ветке, что дешевле. Эта ветка — та, которую ваш поэлементный разбор никогда не собрал воедино, — и есть дерево атак.
К концу урока ты сможешь разложить цель атакующего на AND/OR-дерево атак, оценить стоимость его листьев, чтобы найти самый дешёвый путь, и писать abuse-кейсы, которые встраивают намерение атакующего в требования рядом с пользовательской историей.
У атакующего есть цель, а не чек-лист
STRIDE (таксономия spoofing/tampering/repudiation/info-disclosure/DoS/elevation, которую ты прогоняешь по DFD) смотрит от элемента: ты встаёшь у каждого блока и стрелки диаграммы потока данных и спрашиваешь «что может пойти не так здесь». Это исчерпывающе и отлично для покрытия — но угроза дробится. Атакующий никогда не мыслит элементами. Он мыслит целями: «хочу валидную admin-сессию», «хочу прочитать данные чужого тенанта», «хочу обойти оплату». Дерево атак — это дополнение со взглядом от цели: ты пишешь цель атакующего в корне и раскладываешь её вниз, пока не дойдёшь до конкретных листовых атак, которым уже можно назначить стоимость.
Брюс Шнайер формализовал эту структуру в 1999 году. Корневой узел — цель атакующего. Каждый узел ниже — подцель, которая, будучи достигнутой, вносит вклад в родителя. Ты продолжаешь раскладывать, пока листья не станут атомарными действиями: «выфишить учётку», «реплейнуть утёкший токен», «проэксплуатировать известную CVE в библиотеке аутентификации». Ценность не в диаграмме; она в том, что как только дерево построено, ты можешь разметить каждый лист и позволить структуре вычислить, какие целостные пути атаки выполнимы.
Как читать AND vs OR (в этом вся суть)
Каждый внутренний узел — это либо OR-узел, либо AND-узел, и спутать их — значит вывернуть наизнанку приоритеты защиты.
- OR-узел означает, что любой один потомок достигает родителя. Подцели — это альтернативные маршруты. Чтобы добраться до родителя, атакующий выбирает самого дешёвого и выполнимого потомка — поэтому стоимость OR-узла есть минимум по его потомкам. OR-узлы — это место, где атакующие выбирают лёгкий путь.
- AND-узел означает, что все потомки должны быть достигнуты вместе. Стоимость AND-узла — это сумма его потомков, а выполнимость ограничена самым трудным потомком. AND-узлы — это место, где твои защиты перемножаются: добавление ещё одного обязательного шага (второй фактор, подписанный nonce) поднимает стоимость всего поддерева.
В этом и состоит сеньорский рефлекс: защитники стремятся превратить OR в AND. Если у «украсть сессию» сейчас три дешёвые OR-ветки, у тебя три независимых способа проиграть. Если ты сможешь заставить каждый путь дополнительно требовать чего-то дорогого — скажем, привязанной к устройству учётки, которую нельзя реплейнуть, — ты превратил альтернативы в конъюнкцию, и теперь атакующий платит за объединение, а не за минимум.
Стоимость и выполнимость листьев могут быть чем угодно, о чём ты можешь рассуждать последовательно: денежная цена, требуемый навык, время, заметность или простое булево «возможно / невозможно». Даже грубая разметка «дёшево / средне / дорого» достаточна, чтобы самый дешёвый путь стал очевиден, а самый дешёвый выполнимый путь — это ровно то, что ты чинишь первым.
Разобранное дерево: «украсть аутентифицированную сессию»
Пройди дерево выше так, как прошёл бы атакующий, снизу вверх. Корневая цель — получить валидную аутентифицированную сессию жертвы. Это OR — три независимых способа выиграть:
- Украсть токен сессии. Сам по себе OR: эксфильтровать его через баг XSS (токен в
localStorage, читаемый любым скриптом), соскрести из лога или заголовка referer, где он утёк, или снять со скомпрометированного устройства. Каждый лист дёшев-до-среднего и одношаговый. Эта ветка обычно и есть минимум, поэтому именно так чаще всего и происходит реальная кража сессии. - Перехватить живую сессию. Прокатиться на уже аутентифицированном соединении — атаки с сетевой позиции или предсказуемый id сессии, который можно угадать. Средняя стоимость, часто нужна привилегированная сетевая позиция, поэтому нередко дороже ветки 1.
- Фиксация сессии. AND-узел: атакующий должен и подсадить известный ему id сессии, и заставить жертву аутентифицироваться под ним. Два обязательных шага означают суммированную стоимость, а более трудный потомок (заставить жертву войти на засеянной тобой сессии) ограничивает выполнимость — строго дороже одношаговой кражи токена.
Теперь читай структуру: эффективная стоимость для атакующего — это минимум по OR-корню, и этот минимум почти всегда — самый дешёвый лист в ветке 1. Поэтому твой первый фикс целится в этот лист: убей поверхность XSS и перестань класть токены туда, где их читает скрипт. Но заметь, что делает с целым деревом один-единственный контроль: привяжи сессию к устройству (учётка, которую нельзя реплейнуть вне устройства) — и ты поднял стоимость каждой ветки разом, потому что и реплей украденного токена, и перехват, и фиксация исходили из bearer-токена, работающего где угодно. В этом и сила чтения дерева сверху вниз: один контроль на корневом допущении бьёт три заплатки на листьях.
▸Почему это работает
Зачем вообще оценивать стоимость листьев, а не просто перечислить атаки? Потому что безопасность — это задача о бюджете, а не о полноте. Список из двадцати атак ничего не говорит о порядке; дерево даже с грубыми метками стоимости говорит, что реальный путь атакующего — это единственный самый дешёвый выполнимый лист, и что твоё скудное время на ремонт принадлежит именно туда, а не страшно звучащей, но дорогой атаке, за которую никто не возьмётся. Оценка стоимости — это то, что превращает каталог угроз в приоритизированную очередь работ. Сделай метки примерно верными, и ранжирование устойчиво, даже когда точные числа — догадки.
Abuse-кейсы: «злая» пользовательская история рядом с настоящей
Деревья атак — инструмент анализа. Abuse-кейсы (и их близкий родственник, misuse-кейсы) — инструмент требований: они вводят атакующего в разговор, пока ты ещё пишешь фичу, а не после её выкатки.
Идея обезоруживающе проста. Для каждой пользовательской истории, которую ты пишешь, напиши перевёрнутую историю с точки зрения атакующего. Пользовательская история говорит: «Как клиент, я хочу сбросить пароль по почте, чтобы вернуть доступ.» Abuse-кейс говорит: «Как атакующий, я хочу запускать сбросы пароля для произвольных аккаунтов и собирать reset-токены, чтобы захватывать аккаунты.» Теперь требование — уже не просто «сделать сброс пароля», а «сделать сброс пароля так, чтобы атакующий не мог перебирать аккаунты, не мог брутфорсить токен и не мог завалить почтовый ящик жертвы». Контроль (rate limiting, одноразовые токены высокой энтропии, нейтральные ответы, не раскрывающие, существует ли аккаунт) становится критерием приёмки, а не запоздалой мыслью.
Misuse-кейс — это формальный UML-кузен: на той же диаграмме, что и use-кейсы, ты рисуешь misuser’а и его misuse-кейсы, связанные с use-кейсами, которым они угрожают, и с security-use-кейсами, которые их смягчают. Различие в основном в нотации — оба кодируют один и тот же ход: называют враждебного актора и его цель полноправным артефактом рядом с дружественным. Выгода тут культурная не меньше, чем техническая. Пользовательская история с парным abuse-кейсом не может тихо выкатиться без того, чтобы кто-то решил, что делать со злоупотреблением, — злой двойник прямо тут, в бэклоге, и требует ответа.
Когда тянуться за деревьями, за STRIDE и за abuse-кейсами
Это дополнения, а не конкуренты, и сеньор выбирает по заданному вопросу:
- Тянись за STRIDE, когда нужно покрытие — у тебя есть DFD, и ты хочешь убедиться, что ни spoofing/tampering/и т. д. ни одного элемента не остались без рассмотрения. Это поиск в ширину по поверхности системы.
- Тянись за деревом атак, когда нужно рассуждать о конкретной цели и самом дешёвом пути к ней — «как кто-то реально получает админа?» — и особенно когда хочешь приоритизировать защиты по стоимости или доказать, что контроль поднимает цену для атакующего на многих путях.
- Тянись за abuse-/misuse-кейсами, когда ты на стадии требований и хочешь, чтобы угроза была встроена в критерии приёмки, и контроль выкатился вместе с фичей, а не достраивался после пентеста.
На практике они выстраиваются в цепочку: STRIDE находит угрозы поэлементно, abuse-кейс превращает страшные в требования, а дерево атак — это то, что ты рисуешь, когда одна цель (кража сессии, обход оплаты) достаточно важна, чтобы заслужить разбор по путям с оценкой стоимости.
У команды есть готовая диаграмма потока данных, и она хочет убедиться, что перед следующим релизом не упустила возможные угрозы ни одного элемента (spoofing, tampering, info disclosure и т. д.). Какая техника лучше всего подходит для этого вопроса?
Внутренний узел в дереве атак — это OR-узел с тремя потомками, стоимости которых дёшево, средне и дорого. Какую стоимость атакующий фактически платит, чтобы достичь этого узла, и почему?
Ты пишешь пользовательскую историю «Как клиент, я хочу сбросить пароль по почте». Что реально даёт её сопряжение с abuse-кейсом?
Упорядочь шаги построения дерева атак, от отправной точки до результата, по которому ты действуешь:
- 1 Записать цель атакующего как корневой узел
- 2 Разложить её на OR/AND-подцели
- 3 Раскладывать дальше, пока листья не станут конкретными атомарными атаками
- 4 Разметить каждый лист стоимостью / выполнимостью
- 5 Прочитать путь минимальной стоимости и починить его первым
- 01Объясни разницу между OR-узлом и AND-узлом в дереве атак, как считается стоимость каждого, и что значит «защитники превращают OR в AND».
- 02Что такое abuse-кейс, чем он отличается от misuse-кейса, и когда тянуться за abuse-кейсами против дерева атак против STRIDE?
Дерево атак — это дополнение со взглядом от цели к STRIDE, смотрящему от элемента: ты коренишь задачу атакующего («украсть аутентифицированную сессию»), раскладываешь её на подцели и идёшь дальше, пока листья не станут конкретными, оцениваемыми атаками. Внутренние узлы — это OR (любой один потомок выигрывает — стоимость есть минимум, поэтому атакующие выбирают самый дешёвый путь) или AND (нужны все потомки — стоимость есть сумма, ограниченная самым трудным потомком). Разметка листьев даже грубой стоимостью делает самый дешёвый выполнимый путь очевидным — его ты чинишь первым, — а стратегический ход в том, чтобы добавить контроль, который должна удовлетворить каждая ветка, превратив OR в AND и подняв цену всей цели. Abuse-кейсы вводят атакующего на стадию требований: пиши «злую» пользовательскую историю рядом с настоящей, чтобы контроли стали критериями приёмки, с misuse-кейсами как формальной UML-нотацией того же хода. Выбирай по вопросу: STRIDE для покрытия DFD в ширину, дерево атак для самого дешёвого пути к одной цели, abuse-кейсы — чтобы встроить угрозу в спецификацию. В следующий раз, оценивая чувствительную фичу, спрашивай не «какие элементы можно атаковать», а «какова цель атакующего и какой самый дешёвый лист её достигает?»
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.