{ "index": 145, "slug": "editorial-2023-12-field-security-audit", "title": "Аудит веб-проекта: как превратить список рисков в безопасный порядок исправлений", "excerpt": "Высокий score не говорит, что менять первым. Разбираем security-triage через evidence, границы системы, владельца, обратимый шаг и проверяемый критерий результата.", "contentHtml": "

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

\n

Безопасный triage начинается не со score. Он отвечает на четыре вопроса: что наблюдается, какая граница затронута, кто подтверждает контракт и какой следующий шаг можно отменить. Severity помогает описывать риск. Она не назначает владельца, не даёт разрешение на проверку и не доказывает наличие уязвимости.

\n

Тезис: порядок исправлений строит evidence

\n

Разделите карточку риска на пять частей: evidence, exposure, owner, reversibility и decision. Evidence связывает утверждение с источником. Exposure показывает путь от внешнего входа к данным или действию. Owner подтверждает границу и принимает изменение. Reversibility описывает возврат. Decision объясняет, почему пункт идёт сейчас.

\n

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

\n

Как score вводит в заблуждение

\n

CVSS описывает характеристики уязвимости и помогает сравнивать техническую тяжесть. Но score не видит карту продукта. Он не знает, какой путь критичен для бизнеса, согласован ли тест, принадлежит ли endpoint вашей команде и что произойдёт после изменения. Поэтому высокий Base score может ждать уточнения, а менее громкий пункт с неизвестной публичной границей — получить первый безопасный gate.

\n

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

\n
СимптомПричинаПроверкаДействие
Самый высокий score всегда первыйSeverity подменяет контекстЗапросить claim, источник и ограничениеОбосновать порядок evidence и exposure
Есть домен, но нет границыАктив и third-party путь смешаныНарисовать один пользовательский маршрутУбрать неподтверждённый участок
«Добавим проверку доступа» без тестаРешение опередило критерийНазвать ожидаемый allow и denyНаписать validation до кода
Rollback означает «откатить»Не названы версия и ответственныйПроверить обратное действиеНазначить owner и stop condition
security.txt считают разрешениемDisclosure смешали с authorizationПроверить объект, метод и времяБез согласования ограничиться инвентаризацией
\n
\"Схема
Маршрут показывает порядок вопросов. Он не оценивает реальный риск, не находит уязвимость и не запускает remediation.
\n

Минимальная запись, которая выдерживает проверку

\n

Карточка не обязана быть большой. Поле claim описывает факт или вопрос, а не решение. source указывает журнал, запрос, конфигурацию, владельца или документ. status различает hypothesis, observed и confirmed. В limit записывают то, чего проверка не показывает.

\n
const card = {\n  id: 'SEC-042',\n  claim: 'роль reader получает чужой заказ',\n  scope: 'orders-api / GET /orders/:id',\n  owner: 'orders-team',\n  status: 'hypothesis',\n  source: 'reproduction-2026-08-02-01',\n  expected: { allow: 'свой заказ', deny: 'чужой заказ: 403' },\n  limit: 'тестовая среда; production не проверялся',\n  next: 'согласовать сценарий с владельцем API',\n  rollback: 'изменений в системе нет'\n};\n\nif (card.status === 'hypothesis') {\n  console.log('Не называть карточку подтверждённой уязвимостью');\n}
\n

Пример учебный. Он не отправляет запросы, не получает токены и не доказывает поведение API. Его задача — показать форму записи. В настоящем отчёте идентификатор источника должен вести к разрешённому материалу. Не вставляйте пароль, токен, персональные данные или полный ответ, если для вывода достаточно хеша, фрагмента и защищённой ссылки.

\n

Проверяемый маршрут от гипотезы к изменению

\n
  1. Опишите симптом. Запишите сценарий, время и наблюдаемый результат. Не называйте сигнал критической уязвимостью заранее.
  2. Назовите границу. Укажите приложение, endpoint, роль, данные и переход к внешней зависимости. Домен не является картой продукта.
  3. Проверьте разрешение. Зафиксируйте объект, среду, окно времени, допустимый метод, запретные действия, контакт и условие остановки. Документ disclosure не заменяет эту запись.
  4. Разделите allow и deny. Для авторизации назовите разрешённый результат и отказ. Для файла опишите приём, серверное имя, хранение и проверку доступа при выдаче.
  5. Назначьте владельца. Он подтверждает контракт и принимает решение.
  6. Выберите обратимый gate. Сначала добавьте чтение, тест, флаг или review контракта. Не меняйте необратимую схему, пока не закрыты evidence и rollback.
  7. Сформулируйте критерий. Назовите наблюдаемый результат, источник результата и момент проверки.
  8. Обновите карточку. Измените status, source, limit и rationale. Если evidence не подтвердился, верните hypothesis или закройте false positive с объяснением.
\n

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

\n

Если scope не подтверждён, активная проверка останавливается. Не стоит проверять путь на домене, который может принадлежать подрядчику. Если владелец неизвестен, назначьте вопрос и сохраните evidence gap. Если тест требует необратимой миграции, сначала проведите отдельный review изменения. Если после фикса нет безопасного способа проверить отказ, решение не готово.

\n

Та же логика работает для upload delivery. Нельзя считать файл защищённым только потому, что форма требует входа. Нужно проверить границу выдачи, серверное имя, место хранения, содержимое и авторизацию на чтении. Нельзя считать файл уязвимым только из-за расширения в URL. Нужны наблюдаемый сценарий и согласованный метод.

\n

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

\n

Матрица triage не заменяет penetration test, threat model, code review или incident response. Она не вычисляет business impact и не обещает срок исправления. OWASP ASVS задаёт проверяемые требования, но не знает архитектуру проекта. CVSS помогает описать тяжесть, но не выбирает владельца и не создаёт разрешение. RFC 9116 описывает канал раскрытия, а не право тестировать домен.

\n

Источники могут обновляться. Поэтому в отчёте фиксируйте версию стандарта и идентификатор требования. Не пишите «проверено по OWASP» без названия документа, версии, scope и метода. Не выдавайте учебный объект, синтетическую запись или локальный тест за production evidence.

\n

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

\n

Карточка готова к исправлению, когда другой инженер без устного контекста может ответить на пять вопросов: какой факт или вопрос проверяется; какая граница входит в scope; кто разрешил и принимает решение; какой результат подтвердит или опровергнет гипотезу; как вернуть изменение и кто это сделает. У карточки есть источник, версия метода, ограничение и дата следующей проверки.

\n

Если хотя бы одного ответа нет, готов не fix, а следующий gate. Это проверяемый результат аудита. Он снижает риск ошибочной правки и сохраняет отрицательный путь: команда знает, когда остановиться.

\n

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

" }