security · advanced · 8d
Моделирование угроз и закаливание небольшого приложения
Возьми небольшое приложение, которым ты полностью владеешь, и прогони его через цикл, в котором живёт настоящий инженер по безопасности: сначала разберись, как атакующий реально его сломает, а затем закрой эти пути один за другим. Ты построишь дерево атаки против собственного сервиса, а потом починишь аутентификацию, авторизацию, обращение с секретами, заголовки безопасности и валидацию ввода — и запишешь, какое исправление убивает какую ветку дерева. Это и есть всё ремесло защитной безопасности в миниатюре: не чек-лист, а цепочка от угрозы к мере, которую ты сможешь защитить вслух.
Результат
Самостоятельно размещённое приложение, которым ты владеешь, закалённое по аутентификации/авторизации, секретам, заголовкам безопасности и валидации ввода, вместе с деревом атаки и коротким разбором, где каждое конкретное исправление сопоставлено с угрозой, которую оно нейтрализует.
Этапы
0/6 · 0%- 01Нарисуй дерево атаки
Прежде чем что-то чинить, нужно понять, от чего ты защищаешься, — и «от всего» это не ответ. Выбери одно приложение, которым ты полностью владеешь, и работай только против собственной, намеренно уязвимой, самостоятельно размещённой копии. Набросай его диаграмму потоков данных: где входят запросы, где данные пересекают границу доверия, где живут секреты. Затем помести цель атакующего в корень дерева — например, «прочитать данные другого пользователя» — и разветви её вниз на конкретные способы туда добраться: отсутствующая проверка авторизации, угадываемый токен сессии, утёкший секрет в истории git. Не пытайся охватить всё; будь честен. Маленькое дерево с тремя реальными ветками лучше огромного, набитого угрозами, которые в твоей архитектуре просто невозможны.
Критерии готовности- Есть диаграмма потоков данных, на которой отмечены каждая точка входа, граница доверия и место, где живут секреты или пользовательские данные.
- Дерево атаки с как минимум одной целью атакующего в корне и тремя или более конкретными, привязанными к архитектуре листовыми атаками.
- 02Почини «кто ты» и «что тебе можно»
Большинство реальных взломов не экзотичны — это сессия, которая никогда не истекает, и эндпоинт, забывший проверить владение. Возьми ветки аутентификации и авторизации своего дерева и закрой их. По части authn: сделай сессии или токены неугадываемыми, недолговечными и корректно аннулируемыми при выходе, и перестань доверять всему, что клиент заявляет о собственной личности. По части authz обеспечь наименьшие привилегии в точке каждого чувствительного действия — вопрос никогда не «вошёл ли этот пользователь?», а «можно ли именно этому пользователю трогать именно этот объект?». Классическая ошибка здесь — IDOR: GET /orders/123 срабатывает у любого, кто поменяет число. Докажи себе, что смена id на чужой теперь возвращает 403, а не чужие данные.
Критерии готовности- Сессии/токены недолговечны и аннулируются при выходе; устаревший или подменённый токен отклоняется.
- Доступ к чужому объекту через смену id наглядно возвращает 403/404, а не данные этого объекта.
- 03Убери секреты из кода
Секрет, закоммиченный однажды, закоммичен навсегда — git помнит, как помнят и зеркала, и логи CI. Выследи каждый учётный материал, к которому прикасается приложение: пароли БД, API-ключи, ключи подписи, секрет JWT. Вынеси их из исходников и конфигов в переменные окружения, обеспеченные менеджером секретов (или хотя бы зашифрованным хранилищем вроде SOPS), и проследи, чтобы настоящие значения никогда не попадали в репозиторий. Затем сделай то, что обычно пропускают: проротируй всё, что когда-либо было закоммичено, ведь удаление из текущего файла не удаляет это из истории. Считай утёкший ключ уже скомпрометированным. Условие победы не «нет секретов в последнем коммите», а «свежий клон репозитория не содержит ни одного живого секрета, и каждый ранее раскрытый проротирован».
Критерии готовности- В исходниках, конфигах и истории git нет ни одного живого секрета; приложение читает все секреты из окружения/менеджера.
- Любой учётный материал, который ранее коммитился, проротирован, а не просто удалён из текущих файлов.
- 04Заголовки на границе, валидация на входе
Два дешёвых слоя останавливают на удивление много урона. Первое — заголовки безопасности: Content-Security-Policy, блокирующий внедрённые скрипты, HSTS, чтобы браузер отказывался откатываться на HTTP, плюс X-Content-Type-Options и разумная Referrer-Policy. Не копируй слепо чей-то блок из интернета — настрой CSP под своё приложение и смотри, что он ломает. Второе — валидация ввода на каждой границе доверия: каждое тело запроса, query-параметр и заголовок враждебны, пока не доказано обратное. Проверяй по схеме, отклоняй несоответствующее понятным 4xx и никогда не собирай запрос или shell-команду склейкой строк с пользовательским вводом. Валидация — это не вежливость к хорошим клиентам; это стена, превращающая «атакующий управляет этим полем» в «атакующий получает 400».
Критерии готовности- Ответы несут настроенные CSP и HSTS плюс базовые заголовки, проверенные сканером заголовков.
- Каждый ввод, пересекающий границу доверия, валидируется по схеме; некорректная или инъекционная нагрузка возвращает 4xx, а не 500 и не тихое выполнение.
Опирается на - 05Сопоставь каждое исправление с его угрозой
Исправление, которое нельзя проследить до угрозы, — это просто суета, а угроза без исправления — открытый риск, который ты делаешь вид, что не замечаешь. Замкни цикл: построй матрицу прослеживаемости, где каждый лист дерева атаки указывает на конкретное изменение, которое его нейтрализует, — и ранжируй то, что осталось. Какие-то ветки ты отрежешь полностью (проротировал утёкший ключ); какие-то лишь снизишь (валидация уменьшает, но не стирает риск инъекции); какие-то осознанно примешь и объяснишь почему. Этот разбор и есть результат, отделяющий любителя от инженера: он показывает, что ты не просто пощёлкал настройками, а рассуждал, какие угрозы важны, что дало каждое изменение и какой остаточный риск ты сознательно несёшь.
Критерии готовности- Матрица прослеживаемости связывает каждый лист дерева атаки с конкретным исправлением либо с задокументированным решением «принять/отложить».
- Каждый оставшийся риск проранжирован (например, по вероятности × влияние) с однострочным обоснованием.
- 06Заставь конвейер ловить регрессии
Ручное закаливание гниёт: следующий коммит снова вносит секрет, поднимает зависимость с известной CVE или тихо роняет заголовок. Преврати только что проделанную работу в проверки, выполняющиеся на каждом push. Добавь сканер секретов, чтобы закоммиченный ключ ронял сборку, проверку зависимостей/SCA, чтобы уязвимый пакет блокировал слияние, и — если у приложения есть тесты — автоматическую проверку заголовков или авторизации, которая громко ломается при регрессии защиты. Держи это честным: шумный сканер, который все игнорируют, хуже маленького с нулём ложных срабатываний, которому команда реально доверяет. Цель не зелёный бейдж, а то, что в день, когда кто-то снова сольёт секрет или закрепит плохую версию, конвейер скажет «нет» прежде, чем это дойдёт до main.
Критерии готовности- CI роняет сборку при закоммиченном секрете и при зависимости с известной критической уязвимостью.
- Намеренная регрессия (например, удаление заголовка или повторное добавление секрета) ловится конвейером, а не человеком.
Рубрика
| Джуниор | Миддл | Сеньор | |
|---|---|---|---|
| Перечисление поверхности атаки и моделирование угроз | Диаграмма потоков данных набросана, некоторые категории угроз названы. Дерево атаки мелкое — один-два обобщённых листовых угрозы, а не конкретные эксплойты для данной архитектуры. Категории STRIDE могут быть перечислены, но не применены к конкретным границам доверия. | Диаграмма потоков данных отмечает точки входа и границы доверия. Дерево атаки имеет как минимум три листовых атаки, специфичных для архитектуры (например, IDOR на /orders/:id, брутфорс токена сессии, секрет JWT в истории git). Каждый лист привязан к категории STRIDE с однострочным обоснованием. | Угрозы приоритизированы по вероятность × влияние, а не просто перечислены. Ты можешь назвать возможности атакующего, которые предполагает каждый лист (неаутентифицированный пользователь интернета, аутентифицированный инсайдер, скомпрометированный внутренний сервис), и объяснить, почему это допущение определяет, какие меры стоят своей цены, а какие — оверинжиниринг для реальной модели угроз. |
| Закаливание аутентификации и авторизации | Сессии недолговечны и выход аннулирует токен. Проверка IDOR добавлена, но реализована на стороне клиента или в middleware, которую можно обойти прямым вызовом обработчика. | Авторизация применяется на уровне обработчика к каждой чувствительной операции — не только за воротами логина. Смена id объекта в любом запросе возвращает 403 или 404 для ресурса другого пользователя, что демонстрируемо. Токены сессий криптографически неугадываемы, истекают и аннулируются на сервере при выходе. | Ты называешь остаточный риск после исправления: что легитимный аутентифицированный пользователь мог бы всё ещё сделать при компрометации его аккаунта (горизонтальная эскалация привилегий). Ты разграничиваешь аутентификацию (кто ты) и авторизацию (что тебе можно) на уровне кода и можешь объяснить, почему централизация логики авторизации в одном месте снижает вероятность пропущенной проверки на новом эндпоинте. |
| Гигиена секретов и валидация ввода | Секреты убраны из текущих файлов. Приложение читает из переменных окружения. Ранее закоммиченные секреты не ротированы; история git их по-прежнему содержит. | Ни одного живого секрета нет в исходниках, конфигах или истории git. Ранее закоммиченные значения проротированы. Ввод валидируется по схеме на каждой границе доверия; некорректные или инъекционные нагрузки возвращают 4xx, а не 500 и не тихое выполнение. Заголовки безопасности (CSP, HSTS, X-Content-Type-Options) присутствуют и настроены под приложение. | CSP настроен под реальные источники скриптов и стилей приложения, а не пермиссивный фолбэк. Ты можешь продемонстрировать, что нарушение CSP порождает событие в report-uri. Ты рассуждаешь о том, что утечка API-ключа даёт атакующему: read-only ключ БД, ключ записи и admin-ключ имеют очень разные радиусы поражения, и ротация секретов приоритизируется по этому рейтингу, а не по алфавиту. |
| Прослеживаемость от угрозы к мере | Исправления применены, список изменений написан. Связь между конкретным исправлением и конкретным листовым угрозой, которую оно закрывает, подразумевается, а не обозначена явно. | Матрица прослеживаемости связывает каждый лист дерева атаки с конкретным исправлением или задокументированным решением «принять/отложить» с обоснованием. Оставшиеся риски ранжированы по вероятность × влияние. | Для каждого принятого риска ты называешь компенсирующий контроль (меру, снижающую вероятность без устранения пути атаки) и триггер (какое событие переведёт этот риск из «принят» в «нужно чинить сейчас»). Ты разграничиваешь исправление, убирающее класс атак, и то, что лишь повышает стоимость эксплуатации, — и честно описываешь последнее как снижение риска, а не его устранение. |
Эталонный разбор (спойлер)
STRIDE как инструмент структурированного перечисления: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege естественно сопоставляются с границами доверия на диаграмме потоков данных. Ценность не в аббревиатуре, а в дисциплине задавать каждый вопрос на каждой границе, чтобы не пропустить дыру в аутентификации из-за сосредоточенности на риске инъекции.
Авторизация на уровне обработчика, а не за воротами логина: middleware, проверяющая «вошёл ли пользователь?», необходима, но недостаточна. Класс IDOR сохраняется всякий раз, когда обработчик получает ресурс по переданному вызывающим id без проверки владения. Исправление — одна строка: добавить проверку владельца в запрос или ответ, — но она должна быть в каждом обработчике; именно поэтому централизованная авторизация (политика OPA, сервис прав или общий middleware, выполняющий проверку владения) является ответом уровня senior.
Матрицы прослеживаемости закрывают разрыв между «мы закалили приложение» и «мы снизили конкретный риск, который эксплуатировал бы конкретный атакующий». Исправление, которое нельзя проследить до листового угрозы, — это либо оверинжиниринг, либо закрытие угрозы, которую ты не сформулировал; полезно знать и то, и другое. Принятые риски с явным обоснованием честнее, чем ложно чистая модель угроз.
Сделай по-сеньорски
- Подключи SAST/DAST-скан (например, базовый OWASP ZAP) в CI против эфемерного экземпляра приложения, чтобы каждый PR получал автоматический активный скан и дифф новых находок.
- Добавь структурированное логирование безопасности для отказов authn/authz и подай его в простое правило обнаружения, чтобы попытка IDOR или брутфорса проявлялась как сигнал, а не растворялась в шуме.
- Перезапусти своё дерево атаки после закаливания и зафиксируй, какие ветки теперь невозможны, а какие лишь стали труднее, — срез «до/после», доказывающий, что меры действительно сдвинули риск.