Требования безопасности и abuse cases

Учимся записывать требования безопасности так же чётко, как функциональные, и заранее описывать, как функцию попытаются употребить во зло.

Abuse case (кейс злоупотребления) — сценарий намеренно враждебного использования функции: что сделает не добросовестный пользователь, а злоумышленник, и как система должна на это ответить.

Команды охотно пишут, что система должна делать: «пользователь загружает аватар», «менеджер видит отчёт». Гораздо реже фиксируют, чего она делать не должна: «нельзя загрузить исполняемый файл вместо картинки», «нельзя увидеть чужой отчёт по подобранному id». Эти «не должна» — и есть требования безопасности и кейсы злоупотребления. Если их не записать, они не попадут в реализацию, не попадут в тесты и всплывут уже как уязвимость. Цель урока — научиться вытаскивать такие требования из обычных функциональных и доводить их до «готово».

Зачем это знать защитнику

Большинство уязвимостей — это не «забыли поставить библиотеку», а «никто не сформулировал, что так делать нельзя». Use case описывает добросовестного пользователя; abuse case — противника с теми же входами, но злым намерением. Защитник, который мыслит кейсами злоупотребления, на этапе требований ловит классы проблем (доступ к чужим данным, инъекции, перебор), пока они стоят строчку в документе, а не инцидент. Это прямой способ закрыть «Insecure Design» из OWASP — спроектировать защиту до кода.

Требования безопасности рядом с функциональными

Каждое функциональное требование тянет за собой требования безопасности. Их пишут тем же языком, измеримо и проверяемо. Сравните функцию загрузки файла:

Функциональное:
  Пользователь может загрузить изображение профиля (JPG/PNG, до 5 МБ).

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

Полезный приём — связать требования безопасности с категориями STRIDE и с OWASP Top 10: тогда у каждого требования понятно, какую угрозу оно закрывает. Хорошее требование безопасности конкретно и проверяемо: не «сделать загрузку безопасной», а «отклонять файлы, чей реальный MIME-тип не входит в белый список image/jpeg, image/png».

Abuse cases: думаем как противник

Кейс злоупотребления берёт обычный сценарий и спрашивает: «а если пользователь враждебен?». Метод прост: для каждого use case придумайте «злого двойника» и опишите ожидаемую реакцию системы.

Use case:  пользователь смотрит свой заказ по ссылке /orders/42
Abuse case: атакующий меняет 42 на 43, 44, 45... — перебирает чужие заказы
Ожидаемая реакция: сервер проверяет, что заказ принадлежит текущему
                   пользователю; чужой → 404/403, а не выдача данных

Use case:  пользователь вводит промокод при оплате
Abuse case: атакующий шлёт тысячи кодов в секунду, подбирая валидные
Ожидаемая реакция: rate limiting + блокировка после N неудач

Use case:  форма обратной связи отправляет письмо
Abuse case: атакующий вставляет в поле управляющие символы/инъекцию
Ожидаемая реакция: ввод валидируется и экранируется при использовании

Первый пример — это IDOR (небезопасная прямая ссылка на объект) и «broken access control» из самого верха OWASP. Записанный как abuse case на этапе требований, он почти наверняка будет реализован и протестирован правильно; не записанный — станет типовой дырой.

Как это работает под капотом

Кейс злоупотребления естественно превращается в проверку доступа на сервере. Уязвимая реализация достаёт объект по id из URL и сразу его отдаёт; безопасная — сперва убеждается, что объект принадлежит текущему пользователю.

# УЯЗВИМО (IDOR): отдаём заказ по id из URL без проверки владельца.
def get_order(order_id, current_user):
    return db.orders.find(order_id)        # вернёт и чужой заказ

# БЕЗОПАСНО: проверяем, что заказ принадлежит текущему пользователю.
def get_order(order_id, current_user):
    order = db.orders.find(order_id)
    if order is None or order.owner_id != current_user.id:
        raise NotFound()                   # чужой → как будто не существует
    return order

Видно, что abuse case и требование безопасности — это две стороны одного: сценарий «перебор чужих id» рождает требование «проверять владельца», а оно — конкретную проверку в коде. Если этот путь пройден на этапе дизайна, разработчику остаётся аккуратно его реализовать.

Security в Definition of Done

Чтобы требования безопасности не терялись, их включают в Definition of Done — критерий, по которому задача считается завершённой. Тогда «готово» означает не только «функция работает», но и «требования безопасности выполнены и проверены». Типичные пункты: ввод валидируется; доступ к объектам проверяет владельца/права; ошибки не раскрывают внутренности; критичные действия логируются; abuse cases из тикета покрыты тестами; зависимости без известных уязвимостей. Юридическая рамка остаётся прежней: проверять реализацию этих требований можно на своих и согласованных системах (ст. 272 УК РФ), а сами кейсы злоупотребления — это проектная работа, а не нападение.

Как защититься

Пишите требования безопасности рядом с функциональными, конкретно и проверяемо, связывая их со STRIDE и OWASP. Для каждого use case заводите «злого двойника» — abuse case — и фиксируйте ожидаемую реакцию системы. Особое внимание — доступу к объектам по id (IDOR), перебору и недоверенному вводу. Включите безопасность в Definition of Done, чтобы задача без выполненных требований безопасности не считалась завершённой. Так защита оказывается спроектированной до кода, а не достроенной после инцидента.

Итоги

  • Требования безопасности пишутся рядом с функциональными — конкретно, измеримо, проверяемо.
  • Abuse case — «злой двойник» use case: что сделает противник и как система обязана ответить.
  • Классический abuse case — перебор чужих id (IDOR); ответ — проверка владельца на сервере.
  • Полезно связывать требования безопасности со STRIDE и OWASP Top 10 — видно, какую угрозу закрывает каждое.
  • Security в Definition of Done: «готово» = функция работает И требования безопасности выполнены и протестированы.
Проверьте себя
1. Что такое abuse case (кейс злоупотребления)?
AОписание самого частого сценария добросовестного пользователя
BСценарий намеренно враждебного использования функции: что сделает злоумышленник с теми же входами и как система должна на это ответить
CСписок багов, найденных в продакшене
DДокумент с требованиями к производительности
2. Пользователь смотрит свой заказ по ссылке /orders/42, и атакующий начинает перебирать /orders/43, /44... Как зовётся эта проблема и какова правильная реакция?
AЭто DoS; реакция — увеличить число серверов
BЭто IDOR / broken access control; сервер должен проверять, что заказ принадлежит текущему пользователю, и отдавать 404/403 для чужого
CЭто XSS; реакция — экранировать вывод в HTML
DЭто нормальное поведение, реакция не нужна
3. Зачем включать требования безопасности в Definition of Done?
AЧтобы задача дольше оставалась в работе
BЧтобы «готово» означало не только «функция работает», но и «требования безопасности выполнены и проверены» — иначе они теряются и всплывают как уязвимости
CЧтобы усложнить жизнь разработчикам без пользы
DЭто нужно только для документации, на код не влияет