{ "index": 147, "slug": "editorial-2023-12-practice-security-audit", "title": "Аудит веб-проекта: scope, разрешение и воспроизводимая проверка", "excerpt": "Как очертить путь данных, разделить scope и разрешение и подготовить одну безопасную проверку до активного тестирования. С картой границ, локальным fail-closed примером и диагностической таблицей.", "contentHtml": "
Проблема аудита веб-проекта обычно появляется ещё до первого запроса. В задаче есть домен и слово «проверить», но не указано, входит ли в работу identity provider, CDN, файловое хранилище и тестовая база. Один инженер начинает с формы входа, другой — с заголовков прокси. В конце получается длинный отчёт без ответа на главный вопрос: какую систему и с каким разрешением действительно проверили.
\nЦена ошибки двойная. Пропущенный участок остаётся без владельца. Лишний запрос может попасть в контур подрядчика, изменить данные или затронуть настоящих пользователей. Поэтому безопасный аудит начинается не со сканера, а с короткого контракта: пользовательский путь, активы, границы, разрешённые действия и проверяемое утверждение.
\nТезис. Сначала нужно сделать результат ограниченным, а уже потом подробным. Если другой инженер может показать тот же путь, назвать владельца каждого перехода и повторить разрешённую проверку на учебных данных, scope готов к работе. Если хотя бы одно поле неизвестно, это пробел в покрытии, а не доказательство низкого риска.
\nURL обозначает точку входа, но не весь поток данных. При загрузке документа браузер отправляет байты в API, API проверяет сессию и тип файла, сервис хранения назначает ключ, а отдельный endpoint позже решает, можно ли этот ключ выдать. Если записать только URL загрузки, право чтения останется за пределами карты.
\nДля первого прохода возьмите один путь с понятной ценой ошибки: изменение профиля, платёж или получение документа. Идите по нему от действия пользователя до конечного эффекта. На каждом переходе задайте четыре вопроса: какой объект передаётся, кто принимает решение, кто владеет участком и какое утверждение можно проверить без расширения scope.
\n| Поле | Учебное значение | Что проверяем | Граница вывода |
|---|---|---|---|
| Актив | profile-api | API участвует в выбранном пути | Не доказывает, что API доступно из production |
| Переход | Браузер → API | Где запрос меняет владельца решения | Не доказывает наличие защиты на переходе |
| Владелец | Команда профиля | Кому подтвердить контракт и остановку | Не означает разрешение на любой метод |
| Данные | Идентификатор пользователя | Какие последствия у ошибочного чтения | Не доказывает место хранения поля |
| Утверждение | reader не читает чужой профиль | Субъект, ресурс и ожидаемый отказ | Не является найденной уязвимостью |
Карта должна быть графом переходов, а не перечнем компонентов. Для каждого перехода зафиксируйте вход, выход и решение. Например, «API получил идентификатор файла» — это факт о входе. «API проверил владельца до чтения» — проверяемое утверждение. «Файл нельзя получить» — слишком сильный вывод, пока не указаны роль, ресурс, среда и способ проверки.
\nScope отвечает на вопрос «что обсуждаем»: домен, приложение, среда, endpoint, хранилище, учётные записи и путь пользователя. Authorization отвечает на вопрос «что можно делать»: метод, период, лимит запросов, тестовые данные, запретные действия, контакт для остановки и допустимый результат.
\nПубличность не равна разрешению. Доступная форма не разрешает менять чужой идентификатор. Документация API не разрешает отправлять нагрузку. Запись security.txt задаёт канал для сообщений о проблемах; RFC 9116 отдельно предупреждает, что наличие или отсутствие файла не следует трактовать как разрешение или запрет тестирования.
Эти записи полезно разделять даже внутри одной задачи. Scope может быть согласован для production, а активные запросы — только для staging. Владелец может разрешить чтение с двумя учебными аккаунтами, но не изменение данных. Наблюдение может быть воспроизводимым, а причина — ещё гипотезой. Одна строка «аудит разрешён» стирает эти различия.
\n| Слой | Минимальный вопрос | Если ответа нет |
|---|---|---|
| Объект | Какой домен, среда и путь входят в работу? | Остановиться на инвентаризации |
| Владелец | Кто отвечает за актив и принимает результат? | Назначить контакт до теста |
| Действие | Какой метод разрешён: чтение, запись или только анализ? | Не расширять метод по ходу |
| Ограничение | Какие аккаунты, данные, лимит и стоп-условие действуют? | Сформулировать явное «не проверяем» |
Фраза «проверить авторизацию» не задаёт воспроизводимость. Утверждение «учебный пользователь с ролью reader получает отказ при чтении профиля второго учебного пользователя по изменённому идентификатору» уже содержит субъект, ресурс, действие и ожидаемый результат. Его можно проверить одним сценарием и не выдавать этот сценарий за покрытие всех ролей.
\nНиже — самодостаточный пример на Node.js. Он не открывает URL, не сканирует сеть и не имитирует разрешение. Запустите его на Node.js 18 или новее: сохраните блок как audit-boundary.mjs и выполните командой node audit-boundary.mjs. В рабочей системе значения владельца, среды и согласования должны прийти из вашего процесса, а не из этого примера.
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| Симптом | Рабочая гипотеза | Проверка | Действие |
|---|---|---|---|
| В задаче указан только домен | Точку входа приняли за систему | Пройти путь до identity provider, API и хранения | Добавить узлы, переходы и владельцев |
| Сканер дал много предупреждений | Нет вопроса и приоритета | Для сигнала записать вход, ответ, актив и версию среды | Отделить кандидат от подтверждённого факта |
| Проверяющий не знает, где остановиться | Не заданы лимит и стоп-контакт | Сверить метод с разрешением владельца | Не начинать активное действие |
| После входа доступен файл | Проверили экран, но не выдачу | Проверить право чтения отдельным учебным аккаунтом | Добавить выдачу в карту и вопрос |
Один запрос вернул 403 | Отказ принимают за полное покрытие | Повторить с указанным субъектом и ресурсом | Описать границу результата, не обещать «всё закрыто» |
В отчёте храните входные условия и ответ, но не прикладывайте секреты и лишние персональные данные. Для HTTP-проверки это обычно метод, нормализованный путь без токена, код ответа, существенные заголовки, время, версия среды и идентификатор тестовой учётной записи. Такой набор позволяет повторить наблюдение и не превращает отчёт в новый источник утечки.
\nХорошая карта описывает, когда инженер обязан остановиться. Актив найден, но владелец неизвестен — записываем пробел и не отправляем запрос. Разрешение относится к staging — production исключаем. Разрешён один запрос чтения — не добавляем перебор идентификаторов и не проверяем запись. Это не отказ от аудита, а контроль радиуса действия.
\nТехнический отказ тоже нужно называть точно. Ответ 403 на один запрос подтверждает отказ для конкретного субъекта, ресурса, метода и контекста. Он не доказывает, что тот же объект не выдаётся через экспорт, кеш, другой endpoint или другой слой прокси. Если эти ветки не проверялись, пишите «не проверено», а не «отсутствует».
OWASP WSTG v4.2 предлагает сначала собрать сведения и карту архитектуры, а затем связывать её с тестами. Это помогает не потерять reverse proxy, сервер приложения и внешнюю службу идентификации. Но методика не заменяет согласование доступа: она описывает подход к тестированию, а не выдаёт право тестировать конкретный домен.
\nКарта активов решает узкую задачу: не потерять границы и не начать активное действие без понятного контракта. Она не заменяет threat model, анализ исходного кода, тестирование инфраструктуры, privacy review, внешний penetration test или план реагирования.
\nStaging не доказывает поведение production. Два учебных аккаунта не покрывают все роли и tenant-связи. Локальный fail-closed скрипт не контролирует сервер. Сканер помогает находить кандидатов, но предупреждение не является finding до проверки входа, ответа, воспроизводимости и влияния.
\nНельзя переносить разрешение с одного домена на соседний. Нельзя объявлять покрытие по числу URL. Нельзя обещать отсутствие уязвимостей по одному маршруту. Если архитектура меняется быстрее документации, оставьте поле неизвестным, назначьте владельца и ограничьте действие до безопасной инвентаризации.
\nScope готов, когда другой инженер без устного пояснения может показать выбранный путь, активы и переходы, владельца каждого участка, данные и границы ответственности. Он также называет разрешённую среду, аккаунты, метод, лимит, окно времени, стоп-контакт, запрос и ожидаемый отрицательный ответ.
\nПроверить готовность можно на одном учебном сценарии. Если команда не может объяснить, почему конкретный запрос разрешён, какой результат считается отказом и что произойдёт при отклонении от метода, активное тестирование не начинается. Следующий шаг — закрыть один конкретный пробел в карте. После этого критерий проверяется заново.
\nsecurity.txt. Раздел 5.5 прямо отделяет disclosure-канал от разрешения на тестирование; область файла также привязана к домену URI, с которого он получен.