Школа

← К программе

Модель угроз и базовая гигиена инфраструктуры

Урок курса «Курс подготовки DevOps-инженеров»

Хронометраж: занятие 60 минут — теория 30 минут, практика 30 минут. Домашняя работа: 1–2 часа.

Кратко о чём урок: Что защищаем и от кого, поверхность атаки типового сервиса, разбор реалистичных сценариев (утёкший ключ CI, открытый порт БД, уязвимая зависимость), приоритизация мер по риску.


1. Что изучаем

  • строить модель угроз по четырём вопросам: что строим, что может пойти не так, что сделаем, достаточно ли;
  • проходить STRIDE по элементам системы и находить классы угроз, а не только очевидные;
  • перечислять фактическую поверхность атаки командами и сверять её со схемой;
  • отличать находку от секрета (триаж) и приоритизировать по риску;
  • применять базовую гигиену — минимум, отсекающий дешёвые атаки, и понимать DevSecOps.

2. Зачем это нужно и где применяется

Безопасность не прикручивают в конце — её проектируют. Модель угроз — это структурированный способ заранее спросить «что может пойти не так» и решить, что с этим делать, пока система на схеме, а не в проде под атакой. Базовая гигиена — дешёвый минимум (наименьшие привилегии, нет секретов в коде, минимум открытых портов, обновления), который отсекает большинство массовых атак; без неё дорогие средства защиты бессмысленны.

Это первый урок блока 18, и он задаёт рамку для остальных: управление доступом (18.02), секреты (18.03), цепочка поставок (18.04), сканирование (18.05), харденинг (18.06), политики Kubernetes (18.07), сеть (18.08), реакция на CVE (18.09), аудит (18.10) — всё это конкретные ответы на угрозы, которые модель угроз помогает увидеть. DevSecOps — это встраивание этих проверок в конвейер (блок 09), чтобы безопасность была общей ответственностью и автоматической, а не разовым аудитом раз в год.

3. Теория (30 минут)

Модель угроз отвечает на четыре вопроса. Определение OWASP: «Threat modeling works to identify, communicate, and understand threats and mitigations within the context of protecting something of value.» Процесс организован четырьмя вопросами: «What are we working on?», «What can go wrong?», «What are we going to do about it?», «Did we do a good job?». Это не разовый документ ради галочки, а способ думать: описать систему, найти угрозы, назначить решения, проверить себя — и пересматривать при изменениях.

Вопрос 1: что строим — схема потоков и границы доверия. Начинают с диаграммы потоков данных: кто с кем говорит, где хранятся данные. Ключевое понятие — граница доверия: место, где данные переходят с одного уровня доверия на другой (интернет → прокси, прокси → приложение, приложение → БД). Именно на границах чаще всего и возникают угрозы, потому что по одну сторону данным доверять нельзя. Здесь же перечисляют активы (что защищаем) и точки входа (откуда в систему можно попасть).

Вопрос 2: что может пойти не так — STRIDE. Чтобы не полагаться на интуицию «что вспомнилось», используют классификацию. STRIDE (Microsoft) — шесть классов угроз, и по каждому элементу схемы проходят все шесть:

Класс Угроза Нарушает
Spoofing подмена личности (выдать себя за другого) подлинность
Tampering искажение данных или кода целостность
Repudiation отказ от совершённого действия неотказуемость
Information disclosure утечка данных конфиденциальность
Denial of service отказ в обслуживании доступность
Elevation of privilege повышение привилегий авторизация

Ценность STRIDE — систематичность: пройдя все шесть классов по каждому элементу, находишь угрозы, о которых бы не вспомнил, — например, repudiation (нет журналов, некому доказать, кто что сделал) замечают редко, пока не пройдут по списку.

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

Вопрос 4: достаточно ли — проверка себя. Прошли ли STRIDE по всем элементам, у каждой ли угрозы есть решение, совпадает ли поверхность атаки со схемой. И главное — модель живёт: новая зависимость или новый вход означают новую угрозу, и модель пересматривают (та же мысль, что «однократно построенная надёжность деградирует» из 17.09).

Поверхность атаки перечисляют командами, а не по схеме — и она всегда шире. Это главный практический навык урока. Схема рисует «пользователь → прокси → приложение», но фактическая поверхность атаки — это то, что реально доступно, и её надо снять инструментами. На учебном стеке (обратный прокси + приложение) перечисление дало:

  • опубликованные порты: не один (прокси :8080), а два — приложение оказалось опубликовано напрямую на :18000. nmap подтвердил оба открытыми;
  • прямой обход точки контроля: curl напрямую в приложение :18000 вернул 200 — то есть все проверки прокси обходятся; штатный вход :8080 тоже 200. Это угроза класса spoofing/EoP: запрос минует контроль;
  • кто внутри контейнеров: приложение работает от uid 10001 (non-root, хорошо), а прокси — от root (побег из контейнера опаснее, 08.01/08.04);
  • секреты в переменных окружения: нашлись DB_PASSWORD (утечка, класс information disclosure — виден через docker inspect, 07.03) и, неожиданно, GPG_KEY — унаследованный от базового образа python.

Находка — не всегда секрет: нужен триаж. GPG_KEY в примере — это не утечка: базовый образ python кладёт в окружение отпечаток публичного ключа, которым проверял свою сборку, и он не секретен. А DB_PASSWORD — настоящий секрет. Автоматический поиск по слову «key» поднимает оба, и решает человек: это чувствительное или нет. Отсюда правило: перечисление даёт кандидатов, а не приговор; каждую находку триажат по факту. И отдельный урок: в окружение приезжает то, что вы не клали, — наследие базовых образов (та же находка, что в 07.02/08.10), и это тоже часть поверхности атаки.

Риск = вероятность, умноженная на ущерб; чинить всё нельзя. Угроз всегда больше, чем рук. Приоритизируют по риску: насколько вероятна атака и насколько велик ущерб. Прямой обход прокси и секрет в окружении — высокий риск (легко и больно), их чинят первыми; теоретическая угроза, требующая доступа, которого ни у кого нет, — можно принять. Это тот же прагматизм, что бюджет ошибок в 17.02: цель не «абсолютная защита» (недостижима и разорительна), а осознанное управление риском.

Что защищаем — триада CIA. Определение OWASP говорит «protecting something of value»; это «ценное» раскладывается на три свойства, и STRIDE бьёт ровно по ним. Конфиденциальность (данные видит только тот, кому положено) — по ней бьёт information disclosure. Целостность (данные и код не искажены) — tampering. Доступность (сервис работает, когда нужен) — denial of service. Плюс подлинность (против spoofing), неотказуемость (против repudiation) и авторизация (против elevation of privilege). Полезно, определяя активы, сразу помечать, какое из свойств для каждого важнее: для пароля БД это конфиденциальность, для журнала аудита — целостность и неотказуемость, для самого сервиса — доступность. Это подсказывает, какие угрозы для актива опаснее, и куда тратить силы.

Кто и зачем атакует — почти всегда не про вас лично. Подавляющее большинство атак оппортунистические: автоматические сканеры непрерывно обходят интернет и пробуют известные уязвимости и слабые пароли на всём подряд. Им неважно, кто вы, — важно, что дверь открыта. Именно поэтому базовая гигиена так эффективна: она закрывает дешёвые двери, а на дорогую целевую атаку большинство систем просто не окупает усилий атакующего. Целевые атаки (конкретно на вашу организацию) реже, дороже и требуют другого уровня защиты — но начинать с них, не закрыв массовое, бессмысленно. Отсюда порядок: сначала то, что ломают массово и дёшево (открытые порты, дефолтные пароли, непропатченные CVE), потом редкое и дорогое.

DevSecOps — безопасность внутри конвейера, а не аудит раз в год. Классическая модель «разработали, потом служба безопасности проверила перед релизом» не работает на скорости CI/CD: проверка в конце либо тормозит выкат, либо её пропускают. DevSecOps сдвигает проверки влево — в конвейер (блок 09), чтобы они шли автоматически при каждом изменении и были общей ответственностью, а не только службы безопасности. Конкретно в конвейер уезжает: сканирование зависимостей на уязвимости (SCA) и статический анализ кода (SAST) — 18.05; сканирование образов и SBOM — 18.04, 18.05; проверка политик как кода перед применением в кластер — 18.07; поиск секретов в коде — 18.05. Модель угроз при этом — вход: она говорит, какие проверки вообще нужны этому сервису. Автоматизация не заменяет мышление, но снимает с человека рутину и не даёт забыть.

Базовая гигиена — дешёвый минимум, отсекающий массовые атаки. До сложных средств защиты идут простые правила, каждое из которых закрывает целый класс угроз: наименьшие привилегии (non-root, минимальные права IAM/RBAC, MFA — 18.02, 18.06); минимизация поверхности (только нужные порты, нет обходов — 18.01, 18.08); секреты вне кода и окружения (хранилище, ротация — 18.03); обновления и цепочка поставок (закреплять и сканировать — 18.04, 18.05, 18.09); аудит (журналы доступа — 18.10); защита в глубину (несколько линий обороны) и безопасные умолчания (запрещено по умолчанию, разрешено явно — 14.05, 18.07). Большинство реальных взломов эксплуатируют отсутствие именно гигиены, а не хитрые уязвимости.

Защита в глубину — несколько линий, потому что каждая пробиваема. Ни один контроль не абсолютен: сеть настроят с ошибкой, в приложении найдут баг, секрет однажды утечёт. Поэтому защиту строят слоями, чтобы пробитие одного не открывало всё. На учебном стеке это выглядит так: сеть (приложение не опубликовано наружу, вход только через прокси — 18.08), затем права (приложение работает от non-root, чтобы побег из контейнера дал меньше — 18.06), затем политики (кластер не пускает привилегированные поды — 18.07), затем секреты (пароль не в коде, а в хранилище — 18.03), затем аудит (даже при успешной атаке останется след — 18.10). Атакующему, чтобы дойти до данных, нужно пробить каждый слой; защищающемуся достаточно, чтобы сработал хотя бы один. Обратная ошибка — «одна большая стена»: настроили периметр и успокоились, а внутри всё открыто (плоская сеть, root везде, секреты в окружении) — пробитие периметра означает полный доступ. Модель угроз помогает увидеть, где слой всего один, и это — приоритет на добавление второго.

3.1. Примеры и варианты реализации

Модель угроз — методология, она не зависит от среды; различается, что именно попадает в поверхность атаки.

Среда Что в поверхности атаки Чем перечислять
Локальный стенд (Docker) опубликованные порты, образы, окружение, root в контейнере docker ps/inspect, nmap (демо урока)
VPS / bare-metal открытые порты узла, службы, пользователи, ssh, обновления ss/nmap, systemctl, аудит ssh (01, 18.06)
Yandex Cloud публичные адреса и бакеты, права SA, метаданные ВМ, security groups yc, инвентаризация (11.02, 11.04, 11.06)

Локальный стенд. Демо перечисляет поверхность атаки docker-стека командами и находит расхождение со схемой (прямой обход, секрет в окружении, root).

VPS / bare-metal. Поверхность — это открытые порты узла и службы; перечисляют ss/nmap, проверяют ssh и учётные записи. Подробнее — в 18.06 (харденинг).

Yandex Cloud. В облаке поверхность иная: публичные адреса и открытые бакеты, чрезмерные права сервисных аккаунтов, доступ к сервису метаданных ВМ, широкие security groups. Перечисляют инвентаризацией облака (11.02, 11.04, 11.06). Замером в этом уроке не проверялось.

Что выбрать. Метод один в любой среде: описать систему и границы доверия, пройти STRIDE, назначить решения, перечислить фактическую поверхность и сверить со схемой. Меняется только инструмент перечисления и состав поверхности.

4. Практика на занятии (30 минут)

Курс проходят самостоятельно, поэтому каждый шаг говорит, что ввести (или сделать), зачем, что должно получиться и что делать при неудаче. Стенд и материалы — в examples/18.01-threat-model/.

Стенд — два контейнера в общей docker-сети (course-obs-app:1 как приложение и обратный прокси nginx:1.30-alpine), без kind и Kubernetes: предмет урока — методология построения модели угроз, а не оркестрация. Скрипт lab-threatmodel.sh поднимает стек намеренно с несколькими огрехами (описаны прямо в его коде) — их предстоит найти перечислением поверхности атаки, а не подсмотреть заранее.

Шаг 1 (8 минут). Поднять стек и перечислить поверхность атаки.

  • Что ввести:
cd blocks/18-security/examples/18.01-threat-model
./lab-threatmodel.sh up
./lab-threatmodel.sh surface
  • Зачем: схема рисует «пользователь → прокси → приложение», а фактическая поверхность атаки — это то, что реально доступно; расхождение между ними видно только перечислением командами, а не по рисунку.
  • Ожидаемый результат: up печатает «стек поднят: прокси на :8080, приложение (напрямую!) на :18000». surface выводит пять разделов: два открытых порта (nmap подтверждает оба open), код 200 и у прямого обращения к приложению на :18000, и у обращения через прокси на :8080 (то есть контроль прокси обходится), образы обоих контейнеров без закреплённого digest, пользователя 10001 внутри приложения и root внутри прокси, и в конце — строки НАЙДЕН: DB_PASSWORD=... и НАЙДЕН: GPG_KEY=... среди переменных окружения.
  • Если не так: если surface не находит открытые порты — стек не поднялся, проверьте docker ps --filter name=lab1601 и запустите up заново; если nmap не установлен на хозяине, запускайте шаг в контейнере с сетевыми утилитами (блок 04, course-net:13).
  • Ответьте: сколько точек входа оказалось на самом деле против одной на схеме? Что из найденного нарушает контроль, а что — просто небрежность (незакреплённый тег образа)?

Шаг 2 (5 минут). Триаж секретов.

  • Что сделать: разберите две находки из переменных окружения — DB_PASSWORD и GPG_KEY — и решите для каждой, секрет это или нет.
  • Зачем: автоматический поиск по слову «key»/«secret» поднимает кандидатов, а не приговор; решает человек, и ошибка в обе стороны дорога — пропущенный секрет остаётся утечкой, а ложная тревога тратит время реагирования.
  • Ожидаемый результат: DB_PASSWORD — настоящий секрет (пароль приложения, виден через docker inspect), GPG_KEY — не секрет, а отпечаток публичного ключа, которым базовый образ python проверял свою сборку.
  • Если не так: если оба помечены как секреты — проверьте, ссылается ли значение на что-то приватное (пароль, токен) или на публично проверяемый факт (отпечаток ключа, версия); если сомневаетесь — посмотрите, унаследована ли переменная из базового образа (docker history того же базового образа без вашего слоя).
  • Ответьте: по какому признаку вы отличили секрет от несекрета? Откуда в окружении взялся GPG_KEY, которого вы не задавали?

Шаг 3 (10 минут). Заполнить модель угроз по STRIDE.

  • Что сделать: откройте threat-model.md, пройдите STRIDE по каждому элементу схемы (пользователь, прокси, приложение, БД) и назначьте каждой найденной угрозе решение — снизить, принять, передать или устранить — с владельцем.
  • Зачем: угроза без решения — это не учтённая угроза; STRIDE даёт систематичность, без которой забывают классы вроде repudiation (нет журналов — некому доказать, кто что сделал).
  • Ожидаемый результат: в threat-model.md заполнены все шесть классов STRIDE минимум по одному примеру на класс (используйте находки шага 1: обход прокси — spoofing/elevation of privilege, секрет в окружении — information disclosure, root в прокси — elevation of privilege), и у каждой строки стоит конкретное решение с владельцем, а не «надо бы».
  • Если не так: если для какого-то класса STRIDE нет ни одной угрозы — вернитесь к схеме и подумайте, что по этому классу могло бы пойти не так именно с этим элементом, а не пропускайте класс.
  • Ответьте: для какой угрозы вы выбрали «принять», а не «снизить», и почему это осознанный выбор, а не лень?

Шаг 4 (5 минут). Сверить чек-лист базовой гигиены.

  • Что сделать: пройдите hygiene-checklist.md по пунктам и отметьте, какие из них стек нарушает.
  • Зачем: базовая гигиена — дешёвый минимум, отсекающий массовые атаки; прежде чем думать о сложных средствах защиты, стоит убедиться, что закрыты именно эти дыры.
  • Ожидаемый результат: отмечены минимум три нарушения — «работает не от root» (прокси — root), «нет прямого доступа в обход точки контроля» (:18000 в обход прокси), «секретов нет в переменных окружения» (DB_PASSWORD в docker inspect).
  • Если не так: если нарушений не нашлось — вы не сверяли пункт с фактом (командой), а поверили описанию стека на словах; вернитесь к выводу шага 1 и сверяйте каждый пункт буквально.
  • Ответьте: какое из нарушенных правил гигиены закрыло бы сразу два найденных огреха?

Шаг 5 (2 минуты). Убрать за собой.

  • Что ввести:
./lab-threatmodel.sh down
  • Зачем: контейнеры и сеть стенда не удаляются сами и остаются занимать порты и ресурсы машины.
  • Ожидаемый результат: строка «убрано»; docker ps -a --filter name=lab1601 ничего не показывает.
  • Если не так: если контейнеры остались — удалите вручную docker rm -f lab1601-app lab1601-proxy и docker network rm lab1601-net.

Проверка результата. Вы перечислили поверхность атаки командами, нашли расхождение со схемой, отличили секрет от несекрета и заполнили модель угроз с решениями.

5. Домашняя работа (1–2 часа)

Задание. Построить модель угроз для своей системы (или учебного стека) по четырём вопросам и STRIDE, перечислить её поверхность атаки и составить план устранения по риску.

Критерии приёмки.

  • нарисована схема потоков данных с границами доверия, перечислены активы и точки входа;
  • пройден STRIDE по каждому элементу схемы (все шесть классов), угрозы записаны;
  • каждой угрозе назначено решение (снизить/принять/передать/устранить) с владельцем и сроком;
  • поверхность атаки перечислена командами и сверена со схемой; расхождения отмечены;
  • проведён триаж находок (секрет или нет) хотя бы на одном примере;
  • угрозы приоритизированы по риску (вероятность и ущерб), названы те, что чинят первыми;
  • пройден чек-лист базовой гигиены, нарушенные пункты отмечены.

Файлы для работы: blocks/18-security/examples/18.01-threat-model/

6. Частые ошибки и подводные камни

  • Моделируют по схеме, а не по факту. Симптом: в модели один вход, в проде — три. Причина: не перечислили поверхность командами. Что делать: снимать surface инструментами.
  • Проходят только очевидные угрозы. Симптом: repudiation и EoP не рассмотрены. Причина: полагались на интуицию. Что делать: STRIDE по каждому элементу, все шесть классов.
  • Угроза без решения. Симптом: список угроз есть, ничего не изменилось. Причина: нет действий с владельцем. Что делать: каждой угрозе — решение и срок.
  • Всякую находку считают секретом. Симптом: паника из-за GPG_KEY, который публичный. Причина: нет триажа. Что делать: перечисление даёт кандидатов, решает человек.
  • Чинят всё подряд. Симптом: недели на теоретические угрозы, реальные открыты. Причина: нет приоритизации. Что делать: риск = вероятность на ущерб, сначала высокий.
  • Модель пишут один раз. Симптом: через полгода не отражает систему. Причина: не пересматривают. Что делать: обновлять при изменении системы.
  • Гигиену считают «слишком простой». Симптом: дорогой WAF при секрете в коде. Причина: пропустили базу. Что делать: сначала гигиена, потом сложное.

7. Проверка усвоения

  1. Назовите четыре вопроса модели угроз.
  2. Что такое граница доверия и почему угрозы концентрируются на ней?
  3. Расшифруйте STRIDE и приведите пример угрозы каждого класса.
  4. Почему поверхность атаки перечисляют командами, а не по схеме? Что нашлось на стенде?
  5. Чем находка отличается от секрета? Приведите пример триажа.
  6. Как приоритизировать угрозы, если чинить все нельзя?
  7. Перечислите правила базовой гигиены и класс угроз, который закрывает каждое.
  8. Что такое DevSecOps и как он связан с конвейером (блок 09)?

8. Материалы

Минимум два варианта на выбор: официальная документация плюс книга/статья/видео.


← Предыдущий: 17.09. Chaos engineering и учебные аварии · Блок 18 · Следующий: 18.02. Управление доступом: наименьшие привилегии, IAM, MFA, SSO →

Материалы для практики

Скрипты, шпаргалки и заготовки к заданиям урока. Текст урока — на этой странице.