Разбор вопроса 1 послеобеденной сессии экзамена Registered Information Security Specialist, весна 2024 (Рэйва 6) — JWT alg=none, авторизация API и временная защита WAF
· Го Комура · Зарегистрированный специалист по информационной безопасности, Зарегистрированный специалист по безопасности, API, Безопасность API, JWT, Аутентификация, Авторизация, WAF, Log4Shell, Информационная безопасность, Уязвимость, IPA
«Мы проверяем подпись JWT, значит идентификатору пользователя можно доверять.»
Это утверждение верно лишь наполовину.
Вопрос 1 послеобеденной сессии (PM) экзамена Registered Information Security Specialist весны 2024 (Рэйва 6) построен вокруг API, которое вызывает смартфонное приложение.1 При успешной аутентификации выдаётся JWT, и этот JWT прикладывается к вызовам API, которое получает и обновляет сведения о пользователе. На первый взгляд это совершенно обычная схема.
Однако диагностика выявляет следующие четыре проблемы.
- Смена
algв заголовке JWT наnoneпропускает неподписанный JWT. - Сохранив действительный JWT и сменив
midна чужой идентификатор, можно прочитать или обновить чужие сведения. - Добавление не описанного в спецификации
status=paidпревращает бесплатного пользователя в платящего. - Четырёхзначный код аутентификации, доставленный по почте, можно перебрать без ограничения числа попыток.
Все четыре выглядят как «уязвимости вокруг аутентификации», но причины разные. Ломаются отдельные границы: целостность токена, авторизация на уровне объекта, авторизация на уровне свойства и ограничение частоты попыток.
Во второй половине вопроса появляется ещё одна тема. В широко используемой открытой библиотеке раскрывают критическую уязвимость, которая позволяет злоумышленнику удалённо выполнить код, злоупотребив JNDI Lookup. Ни исправления, ни готового правила WAF ещё нет. Пока что спрашивают, как подтвердить воздействие, куда должна смотреть WAF и почему начальный режим WAF должен быть «обнаружение», а не «блокировка».
Статья опирается на официальные образцовые ответы2 и комментарий к оцениванию3 и разбирает не только ответ на каждый вопрос, но и почему это ответ и насколько строже это следует проектировать на практике.
flowchart TB
accTitle: Общая картина вопроса
accDescr: Показывает границу доверия, сломанную на каждом этапе - код аутентификации, JWT, авторизация API и уязвимость библиотеки
user[Приложение пользователя]
auth[4-значный код]
jwt[Выдача JWT]
api[Пользовательское API]
log[Вывод журнала]
vuln[Уязвимая библиотека]
ext[Удалённое выполнение кода]
user --> auth
auth -->|Нет лимита попыток| jwt
jwt -->|Допускает alg=none| api
api -->|Доверяет mid| db1[Чтение/обновление чужих данных]
api -->|status=paid| db2[Смена платёжного статуса]
api --> log
log --> vuln
vuln -->|JNDI/LDAP/HTTP| ext
Рис. 1: Общая картина вопроса. На каждом этапе ломается другая граница доверия.
1. Сначала вывод
- Свойство RESTful API не держать состояние сессии называют отсутствием состояния (stateless). Это не значит, что у сервера вообще нет базы данных или пользовательского состояния
- У четырёхзначного кода аутентификации 10 000 возможных значений. При 10 попытках в секунду злоумышленник в среднем успевает после 5 000 попыток, то есть за 500 секунд — меньше 10-минутного срока действия, поэтому одно истечение его не останавливает
- Минимальная мера против
alg=none— подтвердить, чтоalgв заголовке JWT не равенNONE. На практике же следует зафиксировать множество допустимых алгоритмов на стороне сервера - Даже при действительном JWT
midзапроса нельзя доверять. Либо сверяйте идентификатор пользователя внутри JWT сmid, либо — безопаснее — вовсе не принимайтеmidот клиента и определяйте цель из JWT - Добавление
status=paid— это проблема Mass Assignment, когда свойства вне спецификации сразу связываются с внутренним объектом. Используйте DTO обновления как белый список и никогда не давайте пользователю менять платёжный статус - Образцовый ответ на перебор — логика, которая блокирует учётную запись, когда число подряд идущих неудач превышает порог. На практике добавьте ещё ступенчатые задержки и контроль по источнику
- Чтобы подтвердить воздействие только что раскрытой критической уязвимости, вместо разрушительной команды записывайте обращения к
index.htmlтестового сервера и так подтверждайте, что удалённое выполнение кода действительно доходит - Поскольку строка атаки несётся в HTTP-заголовке, цель проверки WAF —
Header. Как регулярное выражение, учитывающее смену регистра, подойдёт что-то вроде\W[jJ][nN][dD][iI]\W - Польза стартовать WAF в режиме «обнаружение» в том, что это предотвращает блокировку законного делового трафика из-за ложного срабатывания. Когда приходит оповещение, проверяют, настоящая ли это атака, и после настройки правила переходят в режим блокировки
- WAF — лишь временная мера; корневое исправление — обновить затронутую библиотеку до исправленной версии
2. Как сценарий ложится на вопросы
Действие происходит в компании G, которая запускает новый сервис здравоохранения. Пользователи вводят через смартфонное приложение данные вроде приёмов пищи и веса и получают оценку риска для здоровья и советы по питанию. Система построена в облаке и сочетает API-шлюз, событийную обработку и управляемую базу данных.
В билете конкретные имена продуктов и сервисов абстрагированы. Эта статья тоже не воспроизводит схемы и текст IPA, а пересказывает только структуру, нужную, чтобы понять вопросы.
| Вопрос | Тема | Глава этой статьи |
|---|---|---|
| Вопрос 1 | Природа RESTful API | Глава 4 |
| Вопрос 2(1) | Время перебора 4-значного кода | Глава 5 |
| Вопрос 2(2) | JWT alg=none |
Глава 6 |
| Вопрос 2(3) | Доступ к другому пользователю через mid |
Глава 7 |
| Вопрос 2(4) | Дыра, которая принимает status вне спецификации |
Глава 8 |
| Вопрос 2(5) | Меры против перебора | Глава 9 |
| Вопрос 3(1) | Безопасно подтвердить, что уязвимость есть | Глава 11 |
| Вопрос 3(2)(3) | Куда смотрит WAF, и регулярное выражение | Глава 12 |
| Вопрос 3(4) | Польза режима обнаружения и как им пользоваться | Глава 13 |
Комментарий к оцениванию отмечает, что общая доля верных ответов была примерно средней. Он также указывает, что доля верных ответов была несколько ниже у меры против подделки JWT в вопросе 2(2) и у механизма, нужного на проверочном сервере в вопросе 3(1). Ни то ни другое не решается одним словарём. Нужно проследить, какое значение сменил злоумышленник, в какую обработку оно попало и где ему в итоге доверились.
3. Это не одна «проблема аутентификации»
Если разложить весь вопрос по границам доверия, получается следующее.
[Идентификатор / пароль]
|
v
[Проверка 4-значного кода] ---- нет лимита попыток ----> перебор
|
v
[Выдать JWT]
|
v
[Библиотека JWT] ------- допускает alg=none ------> подделка идентификатора
|
v
[Пользовательское API]
| |
| +-- пропускает status целиком ----> дыра авторизации на уровне свойства
|
+-- доверяет mid -----------------------> дыра авторизации на уровне объекта
[Журналировать внешний ввод]
|
v
[Уязвимая библиотека] ---- JNDI/LDAP/HTTP ------> удалённое выполнение кода
Самое важное различение здесь такое.
| Проверка | Какой вопрос задаёт | Сломанный пример в этом сценарии |
|---|---|---|
| Аутентификация | Кто вы | Перебор 4-значного кода |
| Проверка токена | Не подделаны ли эти сведения о личности | alg=none |
| Авторизация на уровне объекта | Можно ли этому пользователю обращаться к данным этого пользователя | Подмена mid |
| Авторизация на уровне свойства | Можно ли менять это поле | status=paid |
| Граница от ввода к исполнению | Не интерпретируется ли внешний ввод как команда | JNDI Lookup |
Успех одной проверки никогда не причина пропускать следующую. Пользователь с действительным JWT не обязательно вправе читать чужие данные. Пользователь, которому можно обновлять свои данные, не обязательно вправе менять ещё и платёжный статус.
Когда эти стадии удаётся разделить, ответ на каждый вопрос перестаёт быть тем, что заучивают.
flowchart LR
accTitle: Разница между аутентификацией и авторизацией
accDescr: Аутентификация подтверждает субъект, авторизация подтверждает, что этому субъекту разрешено делать
auth[Аутентификация<br/>кто вы]
authz[Авторизация<br/>что вам можно]
auth --> authz
Рис. 2: Разница между аутентификацией и авторизацией. Сначала аутентификация; авторизация — отдельная проверка.
4. Вопрос 1 — что значит «без состояния»
Вопрос 1 спрашивает об одном из принципов проектирования RESTful API: свойстве не вести управление сессией.
Ответ — отсутствие состояния (stateless).
Отсутствие состояния значит, что серверу не нужно помнить разговорное состояние предыдущего запроса, потому что каждый запрос сам по себе несёт всё нужное для обработки. В этом вопросе смартфонное приложение прикладывает JWT к заголовку Authorization в каждом запросе. Сервер проверяет этот JWT и по нему определяет пользователя этого запроса.
Частое неверное чтение — понимать «без состояния» как «сервер вообще не держит состояния». На деле он обычно держит следующее.
- Базу данных со сведениями о пользователях и данными о здоровье
- Платёжный статус
- Значение кода аутентификации, срок действия и счётчик неудач
- Ключ подписи JWT
- Сведения об отзыве, если в проекте есть список отзыва
- Журналы и аудиторские записи
Чего он не держит — это серверное состояние сессии, которое существует только чтобы продолжать разговор и от которого зависит каждый вызов API.
Отсутствие состояния само по себе также не повышает безопасность. Отправка JWT с каждым запросом облегчает горизонтальное масштабирование, но если проверка JWT неверна, эта ошибка так же равномерно растекается по всем узлам. Архитектурное свойство и корректность безопасности — разные вещи.
5. Вопрос 2(1) — четырёхзначный код в среднем ломают за 500 секунд
API аутентификации отправляет четырёхзначное число по почте, когда идентификатор и пароль совпали. Затем выдаёт JWT, когда совпали идентификатор и четырёхзначный код. Код действителен 10 минут с момента генерации.
В диагностике было возможно 10 попыток в секунду. Спрашивают, за сколько секунд в среднем удаётся прорваться.
Счёт — «половина пространства кандидатов»
У четырёхзначного числа, включая ведущий ноль, следующие 10 000 возможностей.
0000, 0001, 0002, ... , 9999
Если правильный ответ выбран равномерно случайно, злоумышленник, который перебирает кандидатов по порядку без повторов, в среднем доходит до верного после половины пространства кандидатов.
Среднее число попыток = 10 000 / 2 = 5 000
Среднее время = 5 000 / 10 попыток в секунду = 500 секунд
Значит пропуск b — 500.
В худшем случае уходит до 1 000 секунд, но вопрос спрашивает среднее. А срок действия кода — 600 секунд, что длиннее среднего времени взлома в 500 секунд. Поэтому и судят, что «скорее всего будет взломан».
flowchart LR
accTitle: Масштаб для 4-значного кода аутентификации
accDescr: Перебор 10 000 кандидатов по 10 в секунду в среднем даёт 5 000 попыток и 500 секунд, что меньше срока действия 600 секунд
A[10 000 кандидатов] -->|В среднем 10 000 / 2 = 5 000 попыток| B[Среднее время взлома 500 секунд]
C[Срок действия 600 секунд] -->|500 секунд меньше 600 секунд| D[Можно взломать в срок действия]
Рис. 9: Масштаб для четырёхзначного кода аутентификации. Перебор в среднем половины пространства кандидатов ломает его в срок действия.
Одно сокращение срока истечения проигрывает, если пространство кандидатов мало
Сила кода аутентификации не определяется ни одной только разрядностью, ни одним только сроком действия.
Число попыток, возможных за срок действия
= попытки в секунду × срок действия
= 10 × 600
= 6 000 попыток
Перебирая неповторяющиеся значения по порядку, злоумышленник может проверить 60% из 10 000 возможностей в срок действия. Задать время истечения само по себе недостаточно, если число попыток не ограничено.
Действующий NIST SP 800-63B требует не менее шести цифр для краткосрочных секретов во внеполосной аутентификации и предписывает лимит попыток, когда у секрета меньше 64 бит энтропии. Он также требует не использовать почту для внеполосной аутентификации.4 Ответ экзамена работает внутри данной спецификации четырёхзначного кода, отправляемого по почте, но для нового проекта на практике саму эту предпосылку стоит пересмотреть.
6. Вопрос 2(2) — alg=none это проблема «дать злоумышленнику выбрать метод проверки»
JWT в этом вопросе состоит из трёх частей: заголовок, полезная нагрузка и подпись.
base64url(header).base64url(payload).base64url(signature)
В заголовке как алгоритм подписи был записан RS256. В полезной нагрузке — идентификатор пользователя, время выдачи и срок действия.
Проверяющий изменил следующее.
- Сменить
algзаголовка сRS256наNONE. - Сменить идентификатор пользователя в полезной нагрузке на другого пользователя.
Отправив этот JWT, проверка прошла и позволила выдать себя за другого.
flowchart LR
accTitle: Ход атаки JWT alg=none
accDescr: Смена alg на none в действительном JWT и перепись идентификатора пропускают запрос
A[Действительный JWT<br/>alg=RS256<br/>user=user01] -->|Сменить alg заголовка на none| B[Подделанный JWT<br/>alg=none<br/>user=user02]
B -->|Пропускает проверку подписи| C[Сервер принимает его<br/>как user02]
Рис. 3: Ход атаки JWT alg=none. Злоумышленник сам выбирает алгоритм проверки.
none — не опечатка
RFC 7519 определяет «Unsecured JWT» — JWT без подписи и без шифрования, у которого alg равен none.5 Значит значение none — не то, чего в спецификации попросту нет.
Проблема в том, что API, которое должно принимать только подписанные JWT, приняло указанный злоумышленником none.
Концептуально уязвимая обработка выглядит так.
1. Прочитать заголовок JWT.
2. Посмотреть alg, записанный в заголовке, и выбрать метод проверки.
3. Если alg равен none, подпись не проверять.
4. Доверить идентификатору пользователя в полезной нагрузке.
Сама сила защиты выбирается из ввода, который контролирует злоумышленник.
Ответ экзамена
Вопрос спрашивает, не более чем в 20 знаках каждое, какие данные должна проверять исправленная библиотека Q и что эта проверка должна подтвердить.
Образцовый ответ таков.
| Пункт | Суть ответа |
|---|---|
| Данные для проверки | Значение, указанное в alg заголовка JWT |
| Что проверить | Что оно не равно NONE |
Как прямое исправление уязвимости, описанной в вопросе, это верно.
На практике не останавливаться на «всё, кроме NONE»
Здесь нужно отделить ответ экзамена от практической рекомендации.
RFC 8725 говорит, что библиотека JWT должна дать вызывающему указать множество разрешённых алгоритмов и что ничего вне этого множества использовать нельзя.6 Иными словами, идея такая.
Плохой подход:
Принять, если token.header.alg != "none"
Хороший подход:
Принять только если входит в serverConfig.allowedAlgorithms
например allowedAlgorithms = ["RS256"]
Отклонять только none всё равно может оставить другие слабые алгоритмы или путаницу алгоритмов, когда схему с открытым ключом принимают за симметричную. Принцип — не копить отрицательные условия того, что принимать, а узко зафиксировать положительное множество того, что разрешено.
Проверка JWT должна подтверждать не только алгоритм, но, в зависимости от сценария, как минимум ещё следующее.
| Пункт | Что подтвердить |
|---|---|
| Подпись | Проверяется ли ожидаемым ключом и алгоритмом |
iss |
Доверенный ли это издатель |
aud |
Выдан ли токен для этого API |
exp |
Внутри ли срока действия |
nbf |
Не раньше ли времени «не действителен до» |
sub или идентификатор |
Действительный ли это субъект в приложении |
| Тип токена | Не путают ли ID-токен с токеном доступа и т. п. |
В этом вопросе ключ полезной нагрузки называется user, но на практике следует либо использовать стандартный sub, либо ясно определить смысл собственного утверждения.
flowchart TB
accTitle: Безопасная и небезопасная проверка JWT
accDescr: Небезопасная проверка зависит от alg, безопасная использует серверный белый список
subgraph "Небезопасная проверка"
D1[Прочитать alg из заголовка JWT]
D2[Принять, если alg равен none]
D1 --> D2
end
subgraph "Безопасная проверка"
S1[Разрешённые алгоритмы в конфиге сервера<br/>например RS256]
S2[Подтвердить, что alg заголовка JWT<br/>в белом списке]
S3[Проверить подпись, iss, aud, exp]
S1 --> S2 --> S3
end
Рис. 4: Безопасная и небезопасная проверка. На практике узко фиксируют множество допустимых алгоритмов.
Base64url — не шифрование
Ещё одно частое недоразумение про JWT. Заголовок и полезная нагрузка представлены в base64url, но это не шифрование. Их может декодировать и прочитать кто угодно.
Что гарантирует подпись — и только когда она проверяется верно — это то, что содержимое не подделывали после выдачи. Это не значит, что личные сведения, которые хотят сохранить в тайне, можно класть в полезную нагрузку подписанного JWT.
7. Вопрос 2(3) — даже с действительным JWT смена mid прочитала чужие данные
Дальше — атака, которая сам JWT не подделывает.
Пользовательское API принимает идентификатор пользователя под именем mid на GET или PUT. Общий модуль P получает или обновляет в базе сведения о пользователе, связанные с этим mid.
Устройство атаки простое.
Идентификатор в JWT: user01 <- корректно подписанный JWT
mid запроса: user02 <- сменил злоумышленник
Подпись JWT действительна, поэтому аутентификация проходит. Но API доверяет mid=user02 как есть и возвращает сведения user02.
Это учебный случай того, что OWASP API Security Top 10 2023 называет Broken Object Level Authorization (BOLA). Всякий раз, когда к данным обращаются по идентификатору объекта, который указал пользователь, авторизацию именно для этого объекта нужно проверять каждый раз.7
flowchart LR
accTitle: Атака BOLA
accDescr: Действительный JWT при смене mid запроса на чужой идентификатор
A[Злоумышленник] -->|JWT user01<br/>mid user02| B[Пользовательское API]
B -->|Доверяет mid| C[Возвращает данные user02 из БД]
Рис. 5: Атака BOLA. Аутентификация проходит, авторизацию не проверяли.
Ответ вопроса
Подчёркивание 2 в таблице 5 спрашивает, не более чем в 40 знаках, какую обработку добавить к вызову общего модуля P.
Образцовый ответ:
Логика, которая проверяет, совпадает ли идентификатор пользователя в JWT со значением
mid
Польза проверять внутри общего модуля P в том, что одну и ту же проверку авторизации легко применить и к GET, и к PUT, и к любому будущему API, которое тоже использует P. Копировать одно и то же сравнение в каждый экран или конечную точку значит, что где-то его не будет.
Более безопасный проект — вовсе не принимать mid
Для API, которое только получает или обновляет собственные сведения вызывающего, идентификатор пользователя от клиента принимать вообще не нужно.
GET /users/me
Authorization: Bearer <JWT>
На стороне сервера субъект извлекают из уже проверенного JWT.
principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)
То же для обновлений.
principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
userId = principal.subject,
name = input.name,
age = input.age
)
Сравнительная проверка защищает, если её написать. Но проект, который вовсе не принимает целевой идентификатор снаружи, сокращает само существование класса ошибок, в котором это сравнение просто забыли написать.
Если администратору нужно работать со сведениями другого пользователя, разделите так.
PUT /users/me для обычных пользователей
PUT /admin/users/{userId} для администраторов
Для административного маршрута требуйте отдельные права, аудиторский журнал и при необходимости повторную аутентификацию. Это делает границы политики авторизации гораздо виднее, чем «добавить исключение в API обычного пользователя только для администраторов».
flowchart LR
accTitle: Как предотвратить BOLA
accDescr: Вместо mid запроса решать или сверять цель по субъекту JWT
A[Пользователь] -->|GET /users/me + JWT| B[API]
B -->|Взять sub из JWT| C{Если mid есть,<br/>совпадает ли с sub}
C -->|Совпадает| D[Вернуть свои данные]
C -->|Не совпадает| E[Отклонить 403]
B -->|Нет mid| F[Поиск в БД по JWT sub]
Рис. 6: Как предотвратить BOLA. Либо не принимать mid, либо сверять его с субъектом JWT.
Аутентификацию и авторизацию — в одном предложении
И на экзамене, и на практике помогает такая формулировка.
- Аутентификация: кто вы
- Авторизация: что этому человеку можно делать
Успешная проверка подписи JWT доводит вас только до «субъекту, которого представляет этот токен, можно доверять». Можно ли «этому субъекту читать user02» нужно подтверждать отдельно.
8. Вопрос 2(4) — status=paid это дыра авторизации на уровне свойства
Спецификация пользовательского API определяет следующие параметры обновления.
mid идентификатор пользователя
name имя
age возраст
Однако проверяющий добавил следующее значение, которого нет в спецификации.
status=paid
После этого статус бесплатного пользователя сменился на статус платящего.
Согласно вопросу, сервис L не проверял полученные параметры, а передавал их все напрямую общему модулю P, который был сделан так, чтобы уметь обновлять базу напрямую.
Ответ для пропуска c — общий модуль P.
flowchart TB
accTitle: Mass Assignment
accDescr: Внеспецификационный status=paid добавляют и целиком накладывают на внутренний объект
A[Спецификация API<br/>mid / name / age] -->|Злоумышленник добавляет status=paid| B[Тело запроса]
B -->|Автопривязка| C[Общий модуль P]
C -->|Сохранено в БД| D[Платёжный статус сменён на paid]
Рис. 7: Mass Assignment. Свойство вне спецификации целиком накладывается на внутренний объект.
Отличие от BOLA
Подмена mid из предыдущей главы и это добавление status выглядят похоже, но зерно защиты разное.
| Уязвимость | Что меняет злоумышленник | Что на самом деле нужно проверять |
|---|---|---|
Подмена mid |
Целевой объект | Можно ли этому пользователю обращаться к этой записи |
Добавление status |
Свойство внутри объекта | Можно ли этому пользователю менять это поле |
OWASP API Security Top 10 2023 относит последнее к Broken Object Property Level Authorization, включая сюда то, что раньше называли Mass Assignment.8
«Класть JSON прямо в сущность» опасно
Уязвимая реализация концептуально выглядит так.
entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)
Даже если на экране есть поля ввода только для name и age, злоумышленник может собрать HTTP-запрос напрямую. Отсутствие поля в интерфейсе — не граница безопасности.
Безопасная реализация делает обновляемые поля явными.
input = parseExactSchema(body, fields = ["name", "age"])
entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age = input.age
repository.save(entity)
Здесь важны две вещи.
- Тип ввода для обновления должен содержать только поля, которые пользователю разрешено менять.
- Неизвестные поля вне спецификации лучше не молча игнорировать, а по возможности отклонять как ошибку.
Молча игнорировать неизвестные поля скрывает, что атака не удалась, но также позволяет пропустить ошибки реализации клиента и признаки атаки. Если нет причины совместимости, отказ по строгой схеме упрощает расследование.
flowchart TB
accTitle: Авторизация на уровне свойства
accDescr: DTO обновления держит только белый список, неизвестные свойства отклоняются
subgraph "DTO обновления - белый список"
D1["name"]
D2["age"]
end
A[Тело запроса] -->|Проверка схемы| B{Только разрешённые<br/>поля присутствуют}
B -->|Да| C[Обновить name/age сущности]
B -->|Нет| D[Вернуть ошибку]
E[Платёжный сервис<br/>проверенное уведомление] -->|Отдельный путь| F[Обновить status=paid]
Рис. 8: Авторизация на уровне свойства. Ограничьте обновляемые поля белым списком и меняйте платёжный статус только отдельным путём.
status менять только из результата платежа
status=paid — не часть профиля пользователя. Это состояние, выведенное из серверного факта: что платёж прошёл успешно.
Обновление профиля пользователя
-> можно менять только name / age
Проверенное уведомление от платёжного сервиса
-> сверить paymentId
-> предотвратить повторную обработку
-> сменить status на paid
Даже если это хранится в одном и том же столбце базы, право менять и путь, которым меняют, — разные вещи. Использовать внутреннюю сущность напрямую как тип ввода внешнего API стирает эту границу.
9. Вопрос 2(5) — меры против перебора держат счётчик неудач как состояние
Против перебора четырёхзначного кода пропуск d в таблице 5 спрашивает, не более чем в 30 знаках, какую обработку туда поставить. Порог равен 10.
Образцовый ответ:
Логика, которая блокирует учётную запись, когда число подряд идущих неудач превышает порог
Это не противоречит отсутствию состояния из вопроса 1. Не держать разговорное состояние вызова API как серверную сессию и сохранять счётчик неудач, нужный для решения о безопасности, — разные вещи.
flowchart LR
accTitle: С лимитом попыток и без него
accDescr: Без лимита код ломают в среднем за 500 секунд, лимит счётчика неудач резко замедляет атаку
subgraph "Без лимита"
A1[10 попыток в секунду] -->|Около 500 секунд| B1[Аутентификация успешна]
end
subgraph "С лимитом"
A2[Блокировка после 10 неудач] -->|Скорость атаки падает| B2[Учётная запись заблокирована]
C2[Ступенчатая задержка] --> B2
end
Рис. 10: С лимитом попыток и без него. Лимит счётчика неудач практически останавливает перебор.
На практике не опираться только на постоянную блокировку
Лимит попыток по учётной записи нужен, но если злоумышленник знает чужой идентификатор, он может намеренно провалить 10 раз и запереть законного пользователя. Поэтому на практике сочетают следующее.
| Контроль | Роль |
|---|---|
| Счётчик неудач по учётной записи | Останавливает перебор против одной учётной записи |
| Ступенчатые ожидания | Терпят опечатки законного пользователя и одновременно замедляют атаку |
| Контроль по IP источника, устройству, ASN и т. д. | Сдерживает атаки, которые пробуют мало раз против многих учётных записей |
| Решения на основе риска | Жёстче ограничивают необычные регионы, устройства или скорости |
| Уведомление пользователя | Даёт заметить атаку или собственную ошибку |
| Безопасная процедура восстановления | Не даёт каналу разблокировки самому стать путём атаки |
Кроме того, при повторной отправке кода счётчик неудач нельзя сбрасывать в ноль — иначе злоумышленник пополняет бюджет попыток каждым вызовом API повторной отправки. Действующий NIST SP 800-63B также требует не сбрасывать счётчик неудач даже при генерации нового секрета аутентификации.4
Сделать код аутентификации одноразовым
Вопрос сосредоточен на сроке истечения, но на практике нужно ещё следующее.
- Сразу делать успешный код недействительным.
- Отклонять повторное использование того же кода.
- Не оставлять сам код в журналах.
- Делать ответ таким, чтобы по успеху или неудаче проверки кода нельзя было вывести, существует ли пользователь.
- Ставить лимит попыток и на API отправки кода.
Пока используется короткий секрет, безопасность нельзя оставлять одному случайному генерированию.
flowchart TB
accTitle: Меры для кода аутентификации
accDescr: Помимо разрядности и срока защищают лимитом попыток, отказом от повторного использования, уведомлением и прочим
A[Код аутентификации] --> B[Увеличить разрядность]
A --> C[Сократить срок действия]
A --> D[Лимит попыток]
A --> E[Сделать недействительным после успеха]
A --> F[Не сбрасывать счётчик при повторной отправке]
A --> G[Не оставлять код в журналах]
A --> H[Контроль по источнику]
Рис. 11: Меры для кода аутентификации. Сочетайте разрядность и срок с контролем попыток и эксплуатационной практикой.
10. Четыре части вопроса 2 — различить на одной странице
Точки вопроса 2, которые легко смешать, упорядочены по значению, которое контролировал злоумышленник.
| Атака | Значение, которое сменил злоумышленник | Чему не следовало доверять | Корневое исправление |
|---|---|---|---|
| Подделка JWT | alg заголовка JWT, идентификатор в полезной нагрузке |
Алгоритм проверки, который объявляет сам токен | Зафиксировать допустимые алгоритмы на стороне сервера |
| Чтение чужих сведений | mid запроса |
Целевой идентификатор, указанный клиентом | Сверять с субъектом JWT или определять целевой идентификатор из JWT |
| Повышение до платящего | status вне спецификации |
Все автоматически связанные свойства | Сделать обновляемые свойства белым списком |
| Взлом 4-значного кода | Кандидаты otp |
Неограниченные попытки аутентификации | Добавить лимиты попыток, задержки и решения о риске |
Важно не сваливать всё это в «проверить ввод».
alg— криптографическая политика.mid— авторизация на уровне объекта.status— авторизация на уровне свойства.otp— устойчивость к онлайн-угадыванию.
Даже внутри одного HTTP-запроса причина защищать каждое из них разная.
11. Вопрос 3(1) — подтвердить удалённое выполнение кода, не нанося вреда
После запуска сервиса в библиотеке H, широко используемой открытой библиотеке, раскрывают критическую уязвимость V. Последовательность событий в вопросе такова.
- Злоумышленник кладёт строку с JNDI Lookup в HTTP-заголовок и отправляет её.
- Целевой сервер записывает это значение в журнал.
- Уязвимая библиотека вычисляет JNDI Lookup и запрашивает LDAP-сервер злоумышленника.
- Ответ LDAP возвращает URL HTTP-сервера злоумышленника.
- Целевой сервер получает файл класса и выполняет команду.
С скрытым именем конкретного продукта это читается как атака типа Log4Shell (CVE-2021-44228). Собственное описание Apache тоже представляет уязвимость так: если злоумышленник контролирует сообщения журнала или параметры, он может выполнить произвольный код, загруженный с LDAP-сервера.9
flowchart LR
accTitle: Поток подтверждения уязвимости типа Log4Shell
accDescr: Безвредный обратный вызов подтверждает, проходит ли цепочка от JNDI до удалённого выполнения кода
A[Злоумышленник] -->|Внедрить полезную нагрузку jndi/ldap<br/>в x-api-version| B[Уязвимый сервер]
B --> C[Обработка журнала]
C -->|JNDI Lookup| D[Вредоносный LDAP-сервер]
D -->|Ответ с HTTP URL| E[Вредоносный HTTP-сервер<br/>index.html]
E -->|Записать GET| F[Тестовый сервер<br/>журнал доступа]
F -->|Подтвердить достижимость| G[Уязвимость подтверждена]
Рис. 12: Поток подтверждения уязвимости типа Log4Shell. Достижимость подтверждают записью HTTP-обращения, а не разрушительной командой.
Проверочный код вызывает только безвредное HTTP-обращение
Компания G запускает проверочный код, который не влияет на систему, чтобы подтвердить, можно ли эксплуатировать уязвимость V извне. Единственная команда, которую выдаёт проверочный код, — получить index.html тестового сервера.
Вопрос 3(1) спрашивает, что нужно реализовать на тестовом сервере, чтобы подтвердить, что команда выполнена.
Образцовый ответ:
Механизм, который записывает обращения к index.html тестового сервера и позволяет их подтвердить
Если журнал доступа веб-сервера фиксирует GET с целевого сервера, это как минимум подтверждает, что прошла следующая цепочка.
Внешний HTTP-запрос
-> обработка журнала
-> JNDI Lookup
-> ответ LDAP
-> получение класса
-> выполнение проверочной команды
-> HTTP-обращение к тестовому серверу
Почему «показать текст на экране» недостаточно
Цель атаки — сервер. Нет гарантии, что на экране браузера пользователя что-то изменится. Кроме того, даже там, где уязвимость есть, исходящую связь на полпути может остановить межсетевой экран.
Запись обращения на стороне тестового сервера даёт наблюдаемое доказательство, что целевой сервер действительно вышел во внешний мир.
Выполняя такую проверку на практике, всегда соблюдайте следующее.
- Получите явное разрешение владельца целевой системы.
- Используйте метод проверки без влияния на продакшен или с приемлемо малым влиянием.
- Не используйте разрушительные команды вроде записи, удаления или смены конфигурации.
- Управляйте проверочным доменом и сервером сами.
- Записывайте время проверки, источник, цель и ожидаемый обратный вызов.
- После проверки разберите временные LDAP- или HTTP-серверы и учётные данные.
«Подтвердить, что произвольное выполнение кода возможно» и «выполнить произвольный опасный код» — не одно и то же. Держите побочные эффекты на минимуме, нужном для цели.
12. Вопрос 3(2)(3) — WAF проверяет HTTP-заголовок
WAF сервиса N позволяет выбрать целью проверки GET, POST, PUT, ANY, Header, COOKIE или Multipart.
Код атаки попадает в значение HTTP-заголовка x-api-version. Поэтому пропуски e и f в таблице 6 оба равны Header.
Место, данное в тексте, прямо сопоставить с целью проверки WAF
Здесь меньше общих знаний и больше чтения потока данных в тексте вопроса.
Где лежит строка атаки:
заголовок x-api-version
|
v
Цель проверки WAF:
Header
Это не параметр GET и не тело POST. Вместо того чтобы смотреть на список возможностей WAF и выбирать ANY, потому что «похоже на атаку», отвечайте местом, куда, по тексту вопроса, злоумышленник положил значение.
Учёт смены регистра
Первое предложение концептуально следовало такому правилу.
Header \Wjndi\W блокировать
Header \Wldap\W блокировать
Но смена регистра, как в jNdI, обходит шаблон, который совпадает только с нижним регистром.
Образцовый ответ на вопрос 3(3) — любой из следующих.
\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W
В билете обратная косая черта в японском начертании может выглядеть как знак иены, но как регулярное выражение это \W. \W совпадает с любым символом, кроме букв, цифр и подчёркивания. В синтаксисе JNDI Lookup сразу до и после jndi появляются несловесные символы вроде ${ и :, и шаблон написан так, чтобы ловить и их.
Ту же идею можно применить, чтобы сделать сторону ldap тоже нечувствительной к регистру.
\W[lL][dD][aA][pP]\W
Не считать это регулярное выражение «полной защитой от Log4Shell»
Экзамен просит регулярное выражение, которое закрывает приём обхода, показанный в тексте вопроса. Реальные атаки могут включать разбиение строк, альтернативные Lookup, кодирование, другие протоколы и вариации, которые одними сигнатурами покрыть трудно.
Практическое место поэтому такое.
- Временно блокировать уже известные шаблоны атак с помощью WAF.
- Выяснить, действительно ли затронутая библиотека присутствует.
- Ограничить исходящие LDAP, RMI и ненужный HTTP-трафик.
- Обновить до исправленной версии.
- После обновления всё равно проверить журналы и расследовать, был ли взлом.
WAF — слой, который покупает время до появления исправленной версии.
flowchart TB
accTitle: Место WAF
accDescr: WAF - слой временного смягчения, корневое исправление - обновить библиотеку до исправленной версии
A[Раскрыта критическая уязвимость] --> B[Подтвердить воздействие]
B --> C[Временное смягчение]
C -->|Правило WAF<br/>обнаружение/блокировка| D[Временно остановить шаблон атаки]
C -->|Ограничить исходящий трафик| E[Закрыть путь злоупотребления]
D --> F[Обновить до исправленной библиотеки]
E --> F
F --> G[Последующий разбор и предотвращение]
Рис. 13: Место WAF. WAF только покупает время до патча; корневое исправление — обновление.
13. Вопрос 3(4) — почему начинать с «обнаружения»
По обновлённому правилу WAF зарегистрированный специалист по безопасности Z советует на фиксированный срок после выхода в продакшен поставить режим «обнаружение», а не «блокировка».
Вопрос спрашивает, не более чем в 25 знаках каждое, о пользе режима обнаружения и о том, что делать, чтобы минимизировать ущерб.
Образцовый ответ:
| Пункт | Суть ответа |
|---|---|
| Польза | Может предотвратить блокировку из-за ложного срабатывания |
| Что делать | При получении оповещения проверять, атака ли это |
Режим обнаружения — не режим «ничего не делать»
В режиме обнаружения трафик, совпавший с правилом, всё равно пропускают, но записывают в журнал и поднимают оповещение. Даже если строка jndi или ldap случайно появится внутри законного вызова API, дело сразу не останавливается.
Взамен эксплуатационной стороне нужно следующее.
Получено оповещение
|
v
Проверить соответствующий запрос
|
+-- Законный трафик -> сузить правило, рассмотреть исключение
|
+-- Атака -> изолировать цель, сохранить журналы, расследовать воздействие, перейти к блокировке
Если оповещения никто не смотрит, у режима обнаружения нет защитного эффекта вовсе. Обнаружение работает только в паре с эксплуатационным процессом, который наблюдает и судит.
Путь от обнаружения к блокировке
Типичная процедура внедрения такова.
- Прогнать режим обнаружения по реальному трафику.
- Классифицировать срабатывания как ложные или истинные.
- Настроить целевой заголовок, путь, API, границы слов и так далее.
- Подтвердить, что влияние на законный трафик приемлемо.
- Переключиться в режим блокировки.
- Следить за числом блокировок и деловым воздействием.
Это, однако, принцип обычного времени. Когда уязвимость критична, её уже активно эксплуатируют и альтернативы нет, простой от взлома могут счесть тяжелее простоя от ложного срабатывания и сразу выбрать блокировку. В сценарии экзамена сначала выбирают режим обнаружения, чтобы подтвердить, что сервис может работать как прежде.
flowchart LR
accTitle: От режима обнаружения WAF к режиму блокировки
accDescr: Наблюдать оповещения в режиме обнаружения, настроить ложные срабатывания, затем перейти к блокировке
A[Режим обнаружения] -->|Реальный трафик| B[Поднято оповещение]
B --> C{Атака или<br/>ложное срабатывание}
C -->|Ложное| D[Настроить правило]
D --> A
C -->|Атака| E[Перейти в режим блокировки]
E --> F[Следить за числом блокировок и деловым воздействием]
Рис. 14: От обнаружения к блокировке. Сначала наблюдать и настраивать, подтвердить приемлемое влияние, затем перейти к блокировке.
14. WAF — временная мера, обновление — корневое исправление
В вопросе на официальном сайте библиотеки H ещё не было ни исправления, ни временного обхода, и даже комплексное правило WAF облачного провайдера должно было занять до 72 часов. Поэтому компания G сама подтверждает воздействие и временно блокирует хотя бы уже выявленные шаблоны.
Эта последовательность — базовая форма реагирования на инцидент.
| Этап | Цель | Ответ в этом вопросе |
|---|---|---|
| Подтвердить воздействие | Решить, действительно ли организация в опасности | Подтвердить эксплуатируемость извне безвредным обратным вызовом |
| Временное смягчение | Купить время до исправления | Правила WAF, обнаружение/блокировка, ограничения исходящего трафика |
| Корневое исправление | Убрать уязвимую причину | Обновить до исправленной библиотеки |
| Последующий разбор | Проверить, не эксплуатировали ли уже | Исследовать журналы WAF, приложения, DNS, прокси и другие |
| Предотвратить повтор | Ускорить следующее решение | Инвентарь зависимостей, SBOM, процедура обновления, канал связи |
«Не знаем, используем ли» — главный источник задержки
В вопросе даже когда компания G спрашивает компанию F, использует ли та библиотеку H, ответ занимает время, потому что нужен подробный разбор конфигурации.
На практике, если искать JAR-файлы начинают только после раскрытия критической уязвимости, реакция запаздывает. Как минимум следующее должно быть на месте в мирное время.
- Инвентарь прямых и транзитивных зависимостей.
- Компоненты и версии, реально входящие в артефакты.
- На какие сервисы, контейнеры и устройства они развёрнуты.
- Процедура обновления зависимой библиотеки и пересборки/перераспространения.
- Канал связи для утверждения экстренных изменений.
- Разрешённые назначения исходящего трафика и влияние их блокировки.
- Где хранятся журналы и как по ним искать.
SBOM — не цель сама по себе. Это указатель, чтобы за короткое время ответить: «на какие работающие системы влияет эта уязвимость».
Не прекращать расследование после обновления
Вас могли уже атаковать около времени раскрытия уязвимости. Обновление до исправленной версии останавливает будущую эксплуатацию, но не стирает уже скомпрометированные учётные данные и уже посаженный бэкдор.
Для уязвимости типа Log4Shell исследуйте как минимум следующие углы.
- HTTP-запросы с подозрительными строками, указывающими на JNDI или LDAP.
- Связь с сервера приложений наружу к LDAP, RMI или HTTP.
- Запуск необычных дочерних процессов.
- Создание подозрительных JAR, классов, скриптов или исполняемых файлов.
- Доступ к облачным учётным данным или переменным окружения.
- Аутентификацию, смену прав и исходящие передачи около времени обновления.
Важно не заключать «нас не атаковали» только по журналам WAF. Есть внутренние пути, которые никогда не проходят через WAF, и журналы, которые раньше не сохраняли.
15. Способ чтения, который облегчает набор баллов на экзамене
Этот вопрос меньше проверка знаний и больше упражнение в чтении разрыва между спецификацией и реализацией.
15.1 Отделить «спецификацию» от «реализации» в таблицах
В проблеме status значение, которого нет в спецификации API, проходит в реализации.
Спецификация:
mid / name / age
Реализация:
отправить все полученные параметры в P
Когда этот разрыв виден, становится ясно, что пропуск c — общий модуль P.
15.2 Подчеркнуть значение, которое сменил злоумышленник
Значение, сменённое в каждой атаке, таково.
algзаголовка JWT- Идентификатор пользователя в полезной нагрузке JWT
- Параметр API
mid statusвне спецификацииotpAPI аутентификации- HTTP-заголовок
x-api-version
Почти каждый вопрос спрашивает «где это значение следует проверять».
15.3 Вернуть ответ к терминам самого текста вопроса
На практике это можно назвать «BOLA», «Mass Assignment» и «rate limiting». Но вопрос просит конкретную обработку, сопоставленную со структурой текста.
Плохой пример:
Выполнять авторизацию надлежащим образом.
Хороший пример:
Проверить, совпадает ли идентификатор пользователя в JWT со значением mid.
Плохой пример:
Принять меры против перебора.
Хороший пример:
Заблокировать учётную запись, когда число подряд идущих неудач превышает порог.
Одно знание абстрактного имени не даёт ответа, который можно оценить в пределах лимита знаков.
15.4 Для WAF проследить «куда положили»
Цель проверки WAF решается не угадыванием по типу атаки, а по месту, куда положили строку атаки.
Положили в заголовок x-api-version
↓
Цель проверки — Header
Замечание комментария к оцениванию, что доля верных ответов в вопросе 3(1) была несколько ниже, тоже потому, что многие ответы не совпали с потоком атаки на рисунке 6. Уже одно перерисовать последовательность атаки стрелками показывает, что нужно наблюдать.
16. Чек-лист для реальных ревью API
Чек-лист, чтобы унести этот вопрос в реальные ревью дизайна и кода.
Проверка JWT
- Допустимый алгоритм подписи зафиксирован в конфигурации сервера.
noneи неожиданные алгоритмы отклоняются.- Подпись,
iss,aud,expиnbfпроверяются в соответствии со сценарием. - ID-токены, токены доступа и токены обновления не путают друг с другом.
- Есть процедура ротации ключей и отзыва.
- Сведения, которые должны оставаться конфиденциальными, не кладут в полезную нагрузку JWT.
Авторизация на уровне объекта
- Смена идентификатора внутри запроса не даёт дойти до чужих данных.
- Авторизация применяется одинаково для списка, карточки, обновления, удаления и скачивания.
- Авторизация применяется в общем слое, который доходит до данных, а не на экране.
- Для API только о себе рассматривали вывод целевого идентификатора из токена.
- Операции администратора используют отдельную политику от API обычного пользователя.
Авторизация на уровне свойства
- Внешний тип ввода и сущность базы разделены.
- Обновляемые поля перечислены белым списком.
- Свойства вне спецификации отклоняются или аудируются.
- Состояния вроде прав, оплаты, согласования и владения нельзя менять из пользовательского ввода.
- Ответы тоже исключают ненужные конфиденциальные свойства.
Попытки аутентификации
- Есть лимит счётчика неудач по учётной записи.
- Есть ступенчатая задержка и контроль по источнику.
- Счётчик неудач не сбрасывается при повторной выдаче кода.
- Код аутентификации можно использовать только один раз.
- Коды аутентификации и пароли не оставляют в журналах.
- Процедура разблокировки/восстановления сама не является более слабым путём аутентификации.
Критические уязвимости зависимых библиотек
- Работающие сервисы можно сопоставить с версиями зависимостей.
- Есть процедура проверки воздействия безвредным методом.
- Можно применить временные меры вроде правил WAF и ограничений исходящего трафика.
- Есть эксплуатационный процесс, в котором ответственный просматривает оповещения обнаружения.
- Есть аварийный путь выпуска для обновления до исправленной версии.
- Журналы исследуют на возможную эксплуатацию до обновления.
17. Два лица общих компонентов, как их показывает этот вопрос
В этом вопросе два общих компонента: библиотека управления JWT Q и общий модуль P.
У общих компонентов большие плюсы.
- Исправление в одном месте распространяется на все API, которые его используют.
- Логику авторизации и проверки не нужно дублировать в каждую функцию.
- Цели тестирования можно собрать вместе.
- Форматы журналов и аудита можно унифицировать.
С другой стороны, ошибки тоже растекаются по всей системе.
- Если библиотека Q принимает
alg=none, уязвимым становится каждое API, которое использует JWT. - Если общий модуль P принимает произвольный
midилиstatus, уязвимыми становятся и GET, и PUT. - Если уязвимая библиотека H используется в основании, каждый путь, который журналирует HTTP-заголовки, становится частью поверхности атаки.
Значит обобщать нужно не просто доступ к данным. Нужно обобщать сами инварианты безопасности и строго проверять этот общий компонент изолированно.
Например, сделайте контракт P таким.
P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})
Безопаснее не выставлять низкоуровневое API вроде следующего напрямую обычным вызывающим.
P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)
Последнее нужно только ограниченному набору маршрутов, например административной обработке. Раздать эту низкоуровневую свободу всем API даёт проект, который зависит от того, что каждый вызывающий каждый раз использует её правильно.
18. Итог
Вопрос 1 послеобеденной сессии весны 2024 (Рэйва 6) — вопрос, который разбирает темы безопасности API по одной.
Использовать JWT не значит, что аутентификация безопасна. Если злоумышленник выбирает алгоритм подписи, идентификатор пользователя можно переписать.
Действительная подпись JWT не значит, что авторизация верна. Доверие mid запроса позволяет законному пользователю обратиться к чужим сведениям.
Уметь обновлять свой объект не значит, что можно менять каждое свойство. Автопривязка внутреннего состояния вроде status позволяет переписать права или платёжный статус.
Срок истечения у кода аутентификации не значит, что он устойчив к перебору. Нужно посчитать пространство кандидатов и скорость попыток и ограничить число неудач.
Положить правило в WAF не значит, что уязвимость исправлена. Обнаружение и блокировка только покупают время; подтверждают воздействие и в итоге обновляют библиотеку.
flowchart TB
accTitle: Соответствие уязвимостей мерам
accDescr: Сопоставляет каждую уязвимость с её границей доверия и мерой
A[Подделка JWT] -->|Проверка токена| B[Зафиксировать допустимый алгоритм]
C[Подмена mid] -->|Авторизация на уровне объекта| D[Сверить с субъектом JWT / mid не нужен]
E[status=paid] -->|Авторизация на уровне свойства| F[Сделать DTO обновления белым списком]
G[Перебор 4-значного кода] -->|Контроль попыток аутентификации| H[Лимит неудач / задержка]
I[Уязвимость типа Log4Shell] -->|От ввода к исполнению| J[Обновление библиотеки / WAF]
Рис. 15: Соответствие уязвимостей мерам. Разделяйте исправление по тому, какая граница сломана.
Через весь этот вопрос проходит один принцип.
Никогда не делать успех предыдущей проверки причиной пропустить следующую границу доверия.
Предыдущие статьи этой серии разбирают уязвимость хранимого XSS в вопросе 1 послеобеденной сессии осени 2023 (Рэйва 5) и утечку данных через гостевой Wi-Fi в вопросе 2 послеобеденной сессии осени 2023 (Рэйва 5). О том, что проверять на сайте в целом, см. также Используем «Руководство по созданию безопасного веб-сайта» IPA как чек-лист.
flowchart TB
accTitle: Итоговый вывод
accDescr: Показывает, что успех предыдущей проверки никогда не причина пропустить следующую границу доверия
A[Аутентификация успешна] --> B[Проверка подписи JWT]
B --> C[Авторизация на уровне объекта]
C --> D[Авторизация на уровне свойства]
D --> E[Лимит попыток]
E --> F[Граница от ввода к исполнению]
F --> G[WAF / обновление библиотеки]
Рис. 16: Итоговый вывод. Границы доверия проверяют поэтапно, ни одну нельзя пропустить.
Справочные ссылки
-
IPA, Билет послеобеденных вопросов экзамена Registered Information Security Specialist, весна 2024 (Рэйва 6). Текст вопроса, на котором основана эта статья. ↩
-
IPA, Образцовые ответы послеобеденной сессии экзамена Registered Information Security Specialist, весна 2024 (Рэйва 6). Официальный образцовый ответ на каждый вопрос. ↩
-
IPA, Комментарий к оцениванию послеобеденной сессии экзамена Registered Information Security Specialist, весна 2024 (Рэйва 6). Объяснение долей верных ответов и типичных ошибок. ↩
-
NIST, SP 800-63B: Authentication and Authenticator Management. Задаёт разрядность краткосрочных секретов, лимиты попыток, счётчик неудач при повторной выдаче и отказ от почты для внеполосной аутентификации, среди прочих требований. ↩ ↩2
-
RFC Editor, RFC 7519: JSON Web Token (JWT). Спецификация JWT, включая Unsecured JWT и
alg=none. ↩ -
RFC Editor, RFC 8725: JSON Web Token Best Current Practices. BCP, которое задаёт фиксацию множества допустимых алгоритмов и проверку издателя, субъекта и аудитории, среди прочего. ↩
-
OWASP, API1:2023 Broken Object Level Authorization. Объясняет необходимость проверять авторизацию для каждого идентификатора объекта, который указывает пользователь. ↩
-
OWASP, API3:2023 Broken Object Property Level Authorization. Объясняет дыры авторизации на уровне свойства, включая Mass Assignment, и меры против них. ↩
-
Apache Logging Services, Security. Объясняет воздействие CVE-2021-44228, выполнение кода через JNDI и LDAP и исправленные версии. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
10 главных угроз информационной безопасности 2026 — как читать рейтинг и от чего малому и среднему бизнесу действительно стоит защищаться
В «10 главных угрозах информационной безопасности 2026» IPA атаки программ-вымогателей заняли 1-е место 11-й год подряд, атаки на цепочку...
Что стоит знать и заказчику сайта — используем «Руководство по созданию безопасного веб-сайта» IPA как чек-лист
По какому критерию проверять безопасность корпоративного сайта? Разбираем 11 уязвимостей и мер противодействия из «Руководства по создани...
С чего начать защиту информации малому и среднему бизнесу — как пользоваться 4-й редакцией «Руководства IPA по информационной безопасности для МСБ»
С чего малому и среднему бизнесу начинать меры по информационной безопасности? На основе 4-й редакции «Руководства по мерам информационно...
Чтобы не забыть решить, «за сколько секунд это должно работать» — упорядочиваем нефункциональные требования с помощью «Градации нефункциональных требований» IPA
Причина многих споров вроде «слишком медленно» или «мы не ожидали такого поведения при сбое» — забытые нефункциональные требования. Разби...
Как правильно оформить договор на разработку по заказу и на сопровождение — выбор между договором доверительного поручения и договором подряда по «Модельному договору» IPA
Как правильно оформить договор при передаче разработки системы на аутсорсинг? На основе «Модельного договора и сделки для информационных ...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка веб-сайтов
Потому что в API для участников и в связке со смартфоном проверка JWT, авторизация на уровне объекта и ограничение обновляемых свойств напрямую определяют безопасность самой веб-системы.
Технические консультации и ревью дизайна
Потому что выявлять пробелы авторизации в существующих API, оценивать радиус поражения зависимых библиотек и вырабатывать временные правила WAF через ревью дизайна — всё это входит в область технических консультаций.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Почему злоумышленник может выдать себя за другого, хотя проверка подписи JWT прошла успешно?
- В этом вопросе библиотека управления JWT принимала alg в заголовке JWT ровно так, как указал злоумышленник, и считала JWT с alg=none действительным вообще без подписи. Поэтому даже переписанный идентификатор пользователя в полезной нагрузке проходит проверку. Ответ экзамена — проверить alg в заголовке JWT и убедиться, что его значение не NONE. На практике, однако, отклонять только NONE недостаточно. Зафиксируйте допустимые алгоритмы — например RS256 — в серверной конфигурации, чтобы алгоритм, который объявляет сам токен, никогда не использовался напрямую для выбора. Также следует проверять издателя, аудиторию, срок действия, субъект и так далее — в зависимости от сценария.
- Достаточно ли для авторизации сравнить идентификатор пользователя внутри JWT с mid запроса?
- Для ответа этого экзамена — достаточно. Проверка внутри общего модуля P, что идентификатор пользователя в JWT совпадает с mid, останавливает атаку, которая указывает чужой mid. Однако для API, которое обрабатывает только собственные сведения вызывающего, на практике безопаснее вообще не принимать mid от клиента и определять идентификатор пользователя из субъекта уже проверенного JWT. Использование вроде GET /users/me или PUT /users/me снижает риск, что логика сравнения просто не будет написана. API, через которое администратор работает с другим пользователем, следует вынести на отдельную конечную точку со своей политикой авторизации.
- Почему обычная проверка входных значений сама по себе не останавливает атаку, которая добавляет status?
- Потому что даже проверка длины name или диапазона age ничего не даёт, если status — поле, которое вообще не следовало принимать, — автоматически связывается и передаётся во внутренний объект. Проблема не в формате значения, а в авторизации на уровне свойства: разрешено ли пользователю менять это свойство вообще. Определите в типе ввода для обновления только name и age и отклоняйте неизвестные свойства. Платёжный статус должен меняться только из событий, которым сервер доверяет, например из успешного результата платёжного сервиса.
- Четырёхзначный код аутентификации истекает через 10 минут — почему он всё равно опасен?
- Потому что кандидатов от 0000 до 9999 всего 10 000, и при 10 попытках в секунду злоумышленник в среднем успевает после 5 000 попыток — то есть за 500 секунд. Срок действия 10 минут равен 600 секундам, поэтому, перебирая неповторяющиеся кандидаты по порядку, можно проверить 6 000 из них в этом окне. Одно только время истечения не останавливает перебор. Пространство кандидатов, скорость попыток и потолок числа попыток нужно проектировать вместе.
- Мера экзамена — блокировка учётной записи. Достаточно ли на практике одной немедленной блокировки?
- Нет. В пропуск вопроса входит логика, которая блокирует учётную запись, когда число подряд идущих неудач превышает порог, но одна фиксированная постоянная блокировка позволяет злоумышленнику намеренно заблокировать чужую учётную запись как отказ в обслуживании. На практике сочетают счётчик неудач по учётной записи с ступенчатыми ожиданиями, оценкой риска источника и устройства, уведомлениями и процедурой восстановления. Важно также, чтобы выдача нового кода не сбрасывала счётчик неудач в ноль.
- В чём смысл ставить WAF на обнаружение, а не на блокировку?
- В том, что законный деловой трафик не останавливается даже тогда, когда обычная строка ошибочно признана атакой. В образцовом ответе преимущество в том, что можно предотвратить блокировку из-за ложного срабатывания, а делать нужно проверять, атака ли это, всякий раз, когда приходит оповещение. Режим обнаружения — не настройка, которую оставляют без присмотра. Это период наблюдения, в течение которого смотрят журналы, отсеивают ложные срабатывания, настраивают правило и затем переходят к блокировке. В чрезвычайной ситуации, когда известная критическая уязвимость уже реально эксплуатируется, разумно взвесить это против риска доступности и вместо этого сразу выбрать блокировку.
- Библиотека H в этом вопросе — это Log4j?
- Вопрос скрывает имя продукта, но последовательность атаки — JNDI Lookup, LDAP-сервер, получение класса с HTTP-сервера, строка, встроенная в HTTP-заголовок, и высокий базовый балл CVSS v3.1 — естественно читается как абстракция CVE-2021-44228, известной как Log4Shell. Статья объясняет это соответствие, но экзамен не требует называть конкретный продукт. Ответить можно только по данной процедуре атаки и спецификации WAF.
- Что стоит унести из этого вопроса в практику?
- Что успешная аутентификация, то, что JWT не подделан, разрешение обратиться к целевому объекту и разрешение изменить целевое свойство — всё это отдельные проверки. Кроме того, короткому коду аутентификации нужен лимит попыток, а при критической уязвимости библиотеки подтверждение воздействия, временную защиту и корневое исправление ведут параллельно. Практическое ядро: собрать авторизацию в общих компонентах, сделать схему ввода белым списком, зафиксировать условия проверки JWT на стороне сервера и вести учёт зависимых библиотек, чтобы их можно было обновить.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.