awesome-everything EN
↑ Обратно к восхождению

Безопасность

Эшелонированная защита: брешь живёт в шве

Суть Ни один контроль безопасности не достаточен сам по себе. Каждый слой — TLS, аутентификация, авторизация, ввод, секреты, зависимости — может быть корректным по отдельности, а система всё равно падает: брешь живёт в шве, где встречаются два корректных контроля.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 17 min

Флоу входа — образцовый. Пароли хешируются Argon2id, сессия — короткоживущий JWT, TLS принуждается через HSTS, API-ключ лежит в вольте. А потом пользователь меняет id в GET /accounts/{id}/statements и читает чужие банковские выписки. Ни один контроль не сломан. Аутентификация проверила, кто вызывает, — безупречно — а эндпоинт выписок просто никогда не спросил, имеет ли этот вызывающий право читать эту запись. Каждый слой прошёл свой собственный тест. Брешь жила в зазоре между ними.

Один запрос, шесть слоёв

Проследи одно чувствительное действие — залогиненный пользователь переводит деньги — и ты пройдёшь через все контроли, что покрыл трек безопасности. Каждый — отдельный слой со своей задачей, и сеньор рассуждает о них как о стеке, а не как о чек-листе:

  1. Транспорт — TLS плюс HSTS, чтобы запрос нельзя было прочитать или даунгрейднуть до открытого текста на проводе.
  2. Аутентификация — доказать, кто вызывает. Короткоживущий access-токен (минуты, не дни), пароли хранятся как хеши Argon2id или bcrypt, никогда сам пароль.
  3. Авторизация — решить, имеет ли этот вызывающий право делать это с этим объектом. Запрет-по-умолчанию, наименьшие привилегии. Это риск №1 у OWASP не просто так.
  4. Обработка ввода — относись к каждому полю как к враждебному: валидируй, параметризуй запросы, кодируй вывод.
  5. Секреты — ключи и креды БД живут в вольте или env, никогда в репозитории, никогда в JWT.
  6. Цепочка поставок — зависимости, исполняющие всё вышеперечисленное, сами проверены и запинены.

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

Брешь живёт в шве

Опасные провалы — это не отсутствующий слой, такие ловят на ревью. Это два корректных слоя, которые не композируются. Хук — каноничный пример: аутентификация ответила «да, это валидный залогиненный пользователь», а эндпоинт доверил этому ответу значение «…значит, пусть читает запись», чего аутентификация никогда не утверждала. AuthN — это не AuthZ. Знание, кто человек, не говорит ничего о том, к чему он может прикасаться.

OWASP ставит Broken Access Control на A01 — самый частый серьёзный изъян — именно потому, что он живёт в этих швах. Фикс структурный, а не заплатка на один роут: принуждай авторизацию в доверенном серверном коде, запрещай по умолчанию для каждого непубличного ресурса и проверяй владение записью на объекте, который называет запрос, а не только на роуте. Атакующий, переключающий {id} в GET /accounts/{id}/statements, делает IDOR; останавливает это только сервер, спрашивающий «владеет ли этот субъект этим объектом?» на каждом запросе.

СлойПо отдельности выглядит верноШов, который всё равно его ломает
Аутентификация (JWT)Подписан, короткоживущий, проверенЭндпоинт не проверяет owns(subject, object) → IDOR
Хеширование пароляArgon2id, с солью, ~250мсУ эндпоинта сброса нет рейт-лимита → захват аккаунта
Хранение токенаСильный, валидный JWTЛежит в localStorage → один XSS читает всё
Защита от CSRFКука SameSite=LaxМеняющий состояние роут на GET обходит её полностью

Шов JWT-в-localStorage

Решение о хранении токена — чистая проблема композиции, поэтому заслуживает близкого взгляда. Безупречно выпущенный JWT — подписанный, короткий срок, верные claims — побеждается тем, куда ты его кладёшь. Положи его в localStorage, и любой успешный XSS выполняет localStorage.getItem('token') и эксфильтрует его на сервер атакующего; собственная сила токена нерелевантна, как только JavaScript может его прочитать. Митигация — HttpOnly-кука, которую JavaScript не может тронуть, поэтому XSS её не украдёт — но куки шлются автоматически кросс-сайтово, что вновь открывает CSRF. Это ты закрываешь через SameSite плюс CSRF-токен. Заметь форму: каждый фикс решает один слой и обнажает шов со следующим. Нет единой настройки, которая побеждает; ты композируешь HttpOnly + Secure + SameSite + короткий TTL, и всё равно обязан убить XSS у источника кодированием вывода и CSP, потому что атакующий, способный исполнить скрипт в твоём origin, уже прошёл мимо вопроса хранения.

Почему это работает

«Мы используем JWT, значит, мы stateless и в безопасности» смешивает две несвязанные вещи. JWT доказывает аутентификацию и может нести claims; он ничего не говорит об авторизации на конкретном объекте и ничего не делает для защиты собственного хранения. Statelessness — это выбор архитектуры. Устойчивость к IDOR и XSS — отдельные контроли, которые ты всё равно обязан построить: формат токена не даёт ни того, ни другого.

Наименьшие привилегии везде, не только у двери

Эшелонированная защита — ещё и про радиус взрыва. Когда слой всё же откажет — а модель предполагает, что один откажет, — наименьшие привилегии решают, как далеко расползётся ущерб. Кред из вольта, что использует API, должен давать только те строки и операции, что нужны эндпоинту, чтобы скомпрометированный сервис не осушил всю базу. Access-токен должен быть короткоживущим, чтобы украденный истёк за минуты, а не недели. Зависимость, которую ты подтянул, должна работать под тем же надзором, потому что вредоносный транзитивный пакет исполняется внутри твоей границы доверия. NIST SP 800-63B и руководство OWASP по паролям задают пол для слоя аутентификации — Argon2id, настроенный примерно на 250–500мс на хеш, или bcrypt с cost factor 12+, с уникальной солью на пользователя, — но сильный хеш за эндпоинтом сброса без рейт-лимита всё равно падает. Число на одном слое никогда не оправдывает зазор в следующем.

Выбери лучший вариант

Залогиненный пользователь вызывает `GET /accounts/{id}/statements` с валидным подписанным JWT, но с id, который не его. Что на самом деле останавливает брешь?

Викторина

Сильный подписанный JWT хранится в localStorage. Каким реалистичным способом его крадут?

Викторина

Что фундаментально предполагает «эшелонированная защита» о твоих контролях?

Расставь шаги по порядку

Расставь слои, через которые проходит один чувствительный запрос, от внешнего к внутреннему:

  1. 1 Транспорт: TLS + HSTS, чтобы запрос нельзя было прочитать или даунгрейднуть
  2. 2 Аутентификация: доказать, кто вызывает (короткоживущий токен, хешированный пароль)
  3. 3 Авторизация: запрет-по-умолчанию, проверить, что этот субъект владеет этим объектом
  4. 4 Обработка ввода: валидировать, параметризовать, кодировать каждое поле
  5. 5 Секреты + зависимости: ключи в вольте, пакеты проверены и запинены
Вспомните перед уходом
  1. 01
    Коллега говорит, что флоу входа безопасен, потому что пароли хешируются Argon2id и JWT сильный. Объясни, почему этого недостаточно и где прячется настоящая брешь.
  2. 02
    Пройди по тому, почему безупречно валидный JWT всё равно крадут, и как выглядит композиция контролей на практике.
Итог

Один чувствительный запрос проходит через слои — транспорт, аутентификация, авторизация, обработка ввода, секреты, цепочка поставок — и трек безопасности покрыл каждый. Урок их сборки воедино в том, что ни один контроль не достаточен, а опасные провалы — это не отсутствующие слои, а два корректных слоя, которые не композируются. Аутентификация доказывает, кто вызывает, и ничего не говорит о том, может ли он трогать конкретный объект, поэтому Broken Access Control — риск №1 у OWASP: брешь живёт в шве, эндпоинт доверяет валидному токену значение владения. Сильный хеш Argon2id падает за эндпоинтом сброса без рейт-лимита; сильный JWT эксфильтруется из localStorage одним XSS. Эшелонированная защита предполагает, что каждый контроль рано или поздно откажет, поэтому ты складываешь независимые слои, запрещаешь по умолчанию, применяешь наименьшие привилегии к кредам и срокам жизни токенов и принуждаешь авторизацию на объекте — не только на личности — на каждом запросе. Моделируй угрозы всему флоу, а не каждому контролю по отдельности, потому что зазор между двумя корректными контролями — ровно там, где живут атакующие.

Продолжить восхождение ↑Security capstone: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.