{ "index": 147, "slug": "editorial-2023-12-practice-security-audit", "title": "Аудит веб-проекта: scope, разрешение и воспроизводимая проверка", "excerpt": "Как очертить путь данных, разделить scope и разрешение и подготовить одну безопасную проверку до активного тестирования. С картой границ, локальным fail-closed примером и диагностической таблицей.", "contentHtml": "

Проблема аудита веб-проекта обычно появляется ещё до первого запроса. В задаче есть домен и слово «проверить», но не указано, входит ли в работу identity provider, CDN, файловое хранилище и тестовая база. Один инженер начинает с формы входа, другой — с заголовков прокси. В конце получается длинный отчёт без ответа на главный вопрос: какую систему и с каким разрешением действительно проверили.

\n

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

\n

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

\n

Сначала карта пути, потом список URL

\n

URL обозначает точку входа, но не весь поток данных. При загрузке документа браузер отправляет байты в API, API проверяет сессию и тип файла, сервис хранения назначает ключ, а отдельный endpoint позже решает, можно ли этот ключ выдать. Если записать только URL загрузки, право чтения останется за пределами карты.

\n

Для первого прохода возьмите один путь с понятной ценой ошибки: изменение профиля, платёж или получение документа. Идите по нему от действия пользователя до конечного эффекта. На каждом переходе задайте четыре вопроса: какой объект передаётся, кто принимает решение, кто владеет участком и какое утверждение можно проверить без расширения scope.

\n
Минимальная карточка участка аудита
ПолеУчебное значениеЧто проверяемГраница вывода
Активprofile-apiAPI участвует в выбранном путиНе доказывает, что API доступно из production
ПереходБраузер → APIГде запрос меняет владельца решенияНе доказывает наличие защиты на переходе
ВладелецКоманда профиляКому подтвердить контракт и остановкуНе означает разрешение на любой метод
ДанныеИдентификатор пользователяКакие последствия у ошибочного чтенияНе доказывает место хранения поля
Утверждениеreader не читает чужой профильСубъект, ресурс и ожидаемый отказНе является найденной уязвимостью
\n
\"Учебная
Карта показывает не сеть, а способ разговора об ответственности. В ней нет адресов, секретов и разрешения на активное тестирование.
\n

Карта должна быть графом переходов, а не перечнем компонентов. Для каждого перехода зафиксируйте вход, выход и решение. Например, «API получил идентификатор файла» — это факт о входе. «API проверил владельца до чтения» — проверяемое утверждение. «Файл нельзя получить» — слишком сильный вывод, пока не указаны роль, ресурс, среда и способ проверки.

\n

Scope и разрешение — разные записи

\n

Scope отвечает на вопрос «что обсуждаем»: домен, приложение, среда, endpoint, хранилище, учётные записи и путь пользователя. Authorization отвечает на вопрос «что можно делать»: метод, период, лимит запросов, тестовые данные, запретные действия, контакт для остановки и допустимый результат.

\n

Публичность не равна разрешению. Доступная форма не разрешает менять чужой идентификатор. Документация API не разрешает отправлять нагрузку. Запись security.txt задаёт канал для сообщений о проблемах; RFC 9116 отдельно предупреждает, что наличие или отсутствие файла не следует трактовать как разрешение или запрет тестирования.

\n

Эти записи полезно разделять даже внутри одной задачи. Scope может быть согласован для production, а активные запросы — только для staging. Владелец может разрешить чтение с двумя учебными аккаунтами, но не изменение данных. Наблюдение может быть воспроизводимым, а причина — ещё гипотезой. Одна строка «аудит разрешён» стирает эти различия.

\n
Что должно быть подтверждено до действия
СлойМинимальный вопросЕсли ответа нет
ОбъектКакой домен, среда и путь входят в работу?Остановиться на инвентаризации
ВладелецКто отвечает за актив и принимает результат?Назначить контакт до теста
ДействиеКакой метод разрешён: чтение, запись или только анализ?Не расширять метод по ходу
ОграничениеКакие аккаунты, данные, лимит и стоп-условие действуют?Сформулировать явное «не проверяем»
\n

Превращаем тему в проверяемое утверждение

\n

Фраза «проверить авторизацию» не задаёт воспроизводимость. Утверждение «учебный пользователь с ролью reader получает отказ при чтении профиля второго учебного пользователя по изменённому идентификатору» уже содержит субъект, ресурс, действие и ожидаемый результат. Его можно проверить одним сценарием и не выдавать этот сценарий за покрытие всех ролей.

\n

Ниже — самодостаточный пример на Node.js. Он не открывает URL, не сканирует сеть и не имитирует разрешение. Запустите его на Node.js 18 или новее: сохраните блок как audit-boundary.mjs и выполните командой node audit-boundary.mjs. В рабочей системе значения владельца, среды и согласования должны прийти из вашего процесса, а не из этого примера.

\n
const boundary = {\n  asset: 'profile-api',\n  environment: 'staging',\n  owner: 'profile-team',\n  subject: 'reader-test-account',\n  resource: 'second-test-profile',\n  method: 'read-only-two-account-check',\n  permission: 'approved-by-owner',\n  rateLimit: '1 request',\n  stopContact: 'profile-on-call',\n};\n\nconst required = [\n  'asset', 'environment', 'owner', 'subject', 'resource',\n  'method', 'permission', 'rateLimit', 'stopContact',\n];\n\nfor (const field of required) {\n  if (!boundary[field]) {\n    throw new Error('incomplete audit boundary: ' + field);\n  }\n}\n\nif (boundary.permission !== 'approved-by-owner') {\n  throw new Error('active testing is not authorized');\n}\n\nif (boundary.method !== 'read-only-two-account-check') {\n  throw new Error('method is outside the example scope');\n}\n\nconsole.log('PASS: boundary is ready for one read-only check');
\n

Ожидаемый вывод — одна строка PASS. Если удалить permission или заменить метод на запись, скрипт завершится ошибкой до обращения к системе. Это полезный fail-closed барьер, но не контроль доступа: он не видит письмо владельца, не проверяет права в API и не доказывает, что сервер вернёт 403.

\n

Разделяем наблюдение, гипотезу и вывод

\n

У каждого результата должна быть короткая цепочка: симптом → причина → проверка → действие. Симптом — то, что можно повторить. Причина — объяснение, которое ещё нужно подтвердить. Проверка — минимальный эксперимент в разрешённой среде. Действие — изменение или следующий безопасный шаг, а не автоматическая реакция на слово «критический».

\n
Диагностика незрелого аудита
СимптомРабочая гипотезаПроверкаДействие
В задаче указан только доменТочку входа приняли за системуПройти путь до identity provider, API и храненияДобавить узлы, переходы и владельцев
Сканер дал много предупрежденийНет вопроса и приоритетаДля сигнала записать вход, ответ, актив и версию средыОтделить кандидат от подтверждённого факта
Проверяющий не знает, где остановитьсяНе заданы лимит и стоп-контактСверить метод с разрешением владельцаНе начинать активное действие
После входа доступен файлПроверили экран, но не выдачуПроверить право чтения отдельным учебным аккаунтомДобавить выдачу в карту и вопрос
Один запрос вернул 403Отказ принимают за полное покрытиеПовторить с указанным субъектом и ресурсомОписать границу результата, не обещать «всё закрыто»
\n

В отчёте храните входные условия и ответ, но не прикладывайте секреты и лишние персональные данные. Для HTTP-проверки это обычно метод, нормализованный путь без токена, код ответа, существенные заголовки, время, версия среды и идентификатор тестовой учётной записи. Такой набор позволяет повторить наблюдение и не превращает отчёт в новый источник утечки.

\n

Отрицательный путь — часть результата

\n

Хорошая карта описывает, когда инженер обязан остановиться. Актив найден, но владелец неизвестен — записываем пробел и не отправляем запрос. Разрешение относится к staging — production исключаем. Разрешён один запрос чтения — не добавляем перебор идентификаторов и не проверяем запись. Это не отказ от аудита, а контроль радиуса действия.

\n

Технический отказ тоже нужно называть точно. Ответ 403 на один запрос подтверждает отказ для конкретного субъекта, ресурса, метода и контекста. Он не доказывает, что тот же объект не выдаётся через экспорт, кеш, другой endpoint или другой слой прокси. Если эти ветки не проверялись, пишите «не проверено», а не «отсутствует».

\n

OWASP WSTG v4.2 предлагает сначала собрать сведения и карту архитектуры, а затем связывать её с тестами. Это помогает не потерять reverse proxy, сервер приложения и внешнюю службу идентификации. Но методика не заменяет согласование доступа: она описывает подход к тестированию, а не выдаёт право тестировать конкретный домен.

\n

Порядок работы, который можно повторить

\n
  1. Выберите один пользовательский путь и запишите его конечный эффект.
  2. Нарисуйте переходы: браузер, прокси, API, identity provider, очередь, хранилище и выдача — только если они участвуют в пути.
  3. Для каждого участка укажите актив, владельца, класс данных, вход, выход и проверяемое утверждение.
  4. Отдельно зафиксируйте среду, учебные аккаунты, метод, лимит, окно времени, запретные действия и контакт остановки.
  5. Сформулируйте положительный и отрицательный сценарии. Первый показывает разрешённый доступ, второй — ожидаемый отказ.
  6. Запустите сначала проверку контракта локально, как в примере. Только после этого выполняйте согласованный запрос.
  7. Сохраните минимальный evidence: условия, запрос, ответ, время, версию среды и границу результата.
  8. Разделите запись на факт, гипотезу, ограничение и следующее действие. Владельца исправления назначайте после подтверждения симптома.
  9. Перед следующим активом или новым методом повторите проверку scope и разрешения.
\n

Ограничения применимости

\n

Карта активов решает узкую задачу: не потерять границы и не начать активное действие без понятного контракта. Она не заменяет threat model, анализ исходного кода, тестирование инфраструктуры, privacy review, внешний penetration test или план реагирования.

\n

Staging не доказывает поведение production. Два учебных аккаунта не покрывают все роли и tenant-связи. Локальный fail-closed скрипт не контролирует сервер. Сканер помогает находить кандидатов, но предупреждение не является finding до проверки входа, ответа, воспроизводимости и влияния.

\n

Нельзя переносить разрешение с одного домена на соседний. Нельзя объявлять покрытие по числу URL. Нельзя обещать отсутствие уязвимостей по одному маршруту. Если архитектура меняется быстрее документации, оставьте поле неизвестным, назначьте владельца и ограничьте действие до безопасной инвентаризации.

\n

Критерий готовности к первой проверке

\n

Scope готов, когда другой инженер без устного пояснения может показать выбранный путь, активы и переходы, владельца каждого участка, данные и границы ответственности. Он также называет разрешённую среду, аккаунты, метод, лимит, окно времени, стоп-контакт, запрос и ожидаемый отрицательный ответ.

\n

Проверить готовность можно на одном учебном сценарии. Если команда не может объяснить, почему конкретный запрос разрешён, какой результат считается отказом и что произойдёт при отклонении от метода, активное тестирование не начинается. Следующий шаг — закрыть один конкретный пробел в карте. После этого критерий проверяется заново.

\n

Проверяемые источники

\n" }