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

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

\n

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

\n

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

\n

Карта активов не равна списку URL

\n

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

\n

У каждого участка должна быть карточка. В ней достаточно пяти полей: идентификатор, тип актива, граница ответственности, владелец и класс данных. Шестое поле задаёт вопрос проверки. Формулировка «проверить API» слишком широкая. Формулировка «подтвердить, что API проверяет роль до чтения чужого документа» уже задаёт наблюдаемое условие.

\n
Минимальная карточка актива
ПолеУчебный примерЧто уточняетЧего не доказывает
Идентификаторweb-api-profileСвязывает карту и результат проверкиЧто такой актив существует в production
ГраницаБраузер → APIПоказывает переход ответственностиЧто переход защищён
ВладелецКоманда профиляДаёт адрес для уточнения контрактаЧто владелец разрешил любой тест
ДанныеИдентификатор пользователяПомогает оценить последствия ошибкиЧто поле действительно хранится именно здесь
ВопросРоль проверяется до чтенияЗадаёт проверяемое утверждениеЧто нарушение уже найдено
\n

Карта должна описывать переходы, а не только узлы. Для загрузки файла отдельно отметьте браузер, API, хранилище и выдачу. Один и тот же файл проходит разные решения: кто принимает байты, кто назначает серверное имя, кто определяет право чтения и кто выдаёт ответ. Если на карте есть только endpoint загрузки, доступ к уже сохранённому файлу выпадает из scope.

\n
\"Карта
Иллюстрация показывает учебную карту активов и границ. Она не содержит адресов реальной сети и не является разрешением на тестирование.
\n

Граница аудита состоит из двух решений

\n

Первое решение отвечает на вопрос «какой объект обсуждаем». Это scope: домен, приложение, API, хранилище, среда и пользовательский путь. Второе отвечает на вопрос «что разрешено делать». Это authorization: допустимый метод, время, учётная запись, нагрузка, запретные действия, контакт для остановки и владелец результата.

\n

Публичность объекта не создаёт разрешение. Доступная из браузера форма не разрешает перебор параметров. Ссылка на документацию не разрешает отправлять нагрузку. Файл security.txt задаёт канал для сообщений о проблемах, но не превращает любой запрос в согласованный тест. Если владелец и допустимое действие не названы, остановитесь на инвентаризации и чтении документации.

\n

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

\n

Механизм: от актива к проверяемому утверждению

\n

Хороший вопрос аудита связывает действие, субъект и ресурс. Например: «пользователь с ролью reader не может получить профиль другого пользователя по изменённому идентификатору». Вопрос содержит субъект, объект и ожидаемый отказ. Его можно проверить в тестовой среде с двумя учебными аккаунтами. Он не требует сразу проверять все endpoints и роли.

\n

Код ниже только показывает форму записи. Имена, значения и адреса вымышлены. Пример не обращается к сети и не сообщает о состоянии какого-либо проекта. Функция блокирует проверку, если владелец не подтвердил границу или метод.

\n
const auditBoundary = {\n  asset: 'web-api-profile',\n  environment: 'staging',\n  owner: 'profile-team',\n  dataClass: 'user-profile',\n  method: 'two-account-read-check',\n  permission: 'approved-by-owner',\n  stopContact: 'on-call-profile',\n};\n\nfunction assertReady(boundary) {\n  const required = ['asset', 'environment', 'owner', 'method',\n    'permission', 'stopContact'];\n\n  for (const field of required) {\n    if (!boundary[field]) {\n      throw new Error(`audit boundary is incomplete: ${field}`);\n    }\n  }\n\n  if (boundary.permission !== 'approved-by-owner') {\n    throw new Error('active testing is not authorized');\n  }\n}\n\nassertReady(auditBoundary);
\n

В реальном проекте проверка должна также учитывать срок действия согласования, область аккаунтов, допустимую частоту запросов и способ удаления тестовых данных. Строка approved-by-owner не заменяет документ. Она показывает, что без явного разрешения код не должен переходить к активному действию.

\n

Симптом → причина → проверка → действие

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

Отрицательный путь важнее красивого отчёта

\n

Аудит должен описывать не только успешную проверку, но и отказ от действия. Если актив найден, но владелец неизвестен, его можно записать в карту и не трогать. Если разрешение относится к staging, production нужно исключить. Если тест требует массовой нагрузки, а в согласовании указан один запрос, нагрузку нельзя «добавить по ходу».

\n

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

\n

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

\n

Порядок действий

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

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

\n

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

\n

Проверка в staging не доказывает поведение production. Два учебных аккаунта не покрывают все роли. Ответ 403 не доказывает отсутствие утечки через кеш, экспорт или другой endpoint. Автоматический сканер полезен для поиска кандидатов, но его предупреждение требует проверки входа, ответа, контекста и влияния.

\n

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

\n

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

\n

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

\n

Он также должен назвать среду, аккаунты, метод, лимит, период и точку остановки. Наконец, он должен показать запрос, ожидаемый ответ и границу результата.

\n

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

\n

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

\n" }