8 lines
21 KiB
JSON
8 lines
21 KiB
JSON
{
|
||
"index": 147,
|
||
"slug": "editorial-2023-12-practice-security-audit",
|
||
"title": "Аудит веб-проекта начинается с границ: как не проверять чужую систему",
|
||
"excerpt": "Как зафиксировать активы, владельцев и разрешённые действия до проверки веб-проекта. С примером scope-карты, диагностической таблицей и критерием готовности.",
|
||
"contentHtml": "<p>В задаче написано: «провести аудит веб-проекта». У команды есть домен, несколько учётных записей и длинный список проверок. Через день один инженер проверяет форму входа, другой смотрит CDN, а внешний identity provider и хранилище файлов никто не включил в разговор. Команда получает аккуратный отчёт, но не знает, какую часть системы он покрывает.</p>\n<p>Цена ошибки двойная. Пропущенная граница оставляет риск без владельца. Лишняя проверка может задеть подрядчика, чужой контур или данные настоящих пользователей. Аудит нельзя начинать с запуска сканера. Сначала нужно описать, что проверяем, кто отвечает за объект, какие данные пересекают границу и какие действия разрешены.</p>\n<p><strong>Тезис.</strong> Практический аудит веб-проекта — это управляемая цепочка: пользовательский путь → активы → границы → разрешение → проверяемое утверждение. Если один элемент неизвестен, результатом становится не «низкий риск», а зафиксированный пробел. Такой порядок сокращает область случайного воздействия и делает следующий шаг воспроизводимым.</p>\n<h2>Карта активов не равна списку URL</h2>\n<p>URL показывает точку входа. Он не показывает, где приложение принимает решение об авторизации, где хранится сессия, кто принимает файл и кто отдаёт его пользователю. Для первого scope лучше выбрать один ценный путь: вход, смену адреса, оплату или загрузку документа. Затем пройти по нему от действия пользователя до конечного эффекта.</p>\n<p>У каждого участка должна быть карточка. В ней достаточно пяти полей: идентификатор, тип актива, граница ответственности, владелец и класс данных. Шестое поле задаёт вопрос проверки. Формулировка «проверить API» слишком широкая. Формулировка «подтвердить, что API проверяет роль до чтения чужого документа» уже задаёт наблюдаемое условие.</p>\n<table><caption>Минимальная карточка актива</caption><thead><tr><th scope=\"col\">Поле</th><th scope=\"col\">Учебный пример</th><th scope=\"col\">Что уточняет</th><th scope=\"col\">Чего не доказывает</th></tr></thead><tbody><tr><td>Идентификатор</td><td><code>web-api-profile</code></td><td>Связывает карту и результат проверки</td><td>Что такой актив существует в production</td></tr><tr><td>Граница</td><td>Браузер → API</td><td>Показывает переход ответственности</td><td>Что переход защищён</td></tr><tr><td>Владелец</td><td>Команда профиля</td><td>Даёт адрес для уточнения контракта</td><td>Что владелец разрешил любой тест</td></tr><tr><td>Данные</td><td>Идентификатор пользователя</td><td>Помогает оценить последствия ошибки</td><td>Что поле действительно хранится именно здесь</td></tr><tr><td>Вопрос</td><td>Роль проверяется до чтения</td><td>Задаёт проверяемое утверждение</td><td>Что нарушение уже найдено</td></tr></tbody></table>\n<p>Карта должна описывать переходы, а не только узлы. Для загрузки файла отдельно отметьте браузер, API, хранилище и выдачу. Один и тот же файл проходит разные решения: кто принимает байты, кто назначает серверное имя, кто определяет право чтения и кто выдаёт ответ. Если на карте есть только endpoint загрузки, доступ к уже сохранённому файлу выпадает из scope.</p>\n<figure><img src=\"/assets/editorial/2023/security-audit-2023-scope-map.svg\" alt=\"Карта границ аудита веб-проекта: браузер, веб-API, граница идентификации и выдача файла; у переходов указаны владелец и вопрос проверки\" loading=\"lazy\" /><figcaption>Иллюстрация показывает учебную карту активов и границ. Она не содержит адресов реальной сети и не является разрешением на тестирование.</figcaption></figure>\n<h2>Граница аудита состоит из двух решений</h2>\n<p>Первое решение отвечает на вопрос «какой объект обсуждаем». Это scope: домен, приложение, API, хранилище, среда и пользовательский путь. Второе отвечает на вопрос «что разрешено делать». Это authorization: допустимый метод, время, учётная запись, нагрузка, запретные действия, контакт для остановки и владелец результата.</p>\n<p>Публичность объекта не создаёт разрешение. Доступная из браузера форма не разрешает перебор параметров. Ссылка на документацию не разрешает отправлять нагрузку. Файл <code>security.txt</code> задаёт канал для сообщений о проблемах, но не превращает любой запрос в согласованный тест. Если владелец и допустимое действие не названы, остановитесь на инвентаризации и чтении документации.</p>\n<p>Эти слои нужно хранить раздельно. Scope может быть согласован, а активная проверка ещё запрещена. Разрешение может действовать только для тестовой среды. Найденный симптом может быть воспроизводимым, но не доказывать причину. Раздельные записи не добавляют бюрократию. Они не дают одному факту подменить другой.</p>\n<h2>Механизм: от актива к проверяемому утверждению</h2>\n<p>Хороший вопрос аудита связывает действие, субъект и ресурс. Например: «пользователь с ролью reader не может получить профиль другого пользователя по изменённому идентификатору». Вопрос содержит субъект, объект и ожидаемый отказ. Его можно проверить в тестовой среде с двумя учебными аккаунтами. Он не требует сразу проверять все endpoints и роли.</p>\n<p>Код ниже только показывает форму записи. Имена, значения и адреса вымышлены. Пример не обращается к сети и не сообщает о состоянии какого-либо проекта. Функция блокирует проверку, если владелец не подтвердил границу или метод.</p>\n<pre><code>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);</code></pre>\n<p>В реальном проекте проверка должна также учитывать срок действия согласования, область аккаунтов, допустимую частоту запросов и способ удаления тестовых данных. Строка <code>approved-by-owner</code> не заменяет документ. Она показывает, что без явного разрешения код не должен переходить к активному действию.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностика незрелого scope</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>В задаче указан только основной домен</td><td>Точку входа приняли за систему</td><td>Пройти один пользовательский путь до хранилища и внешних сервисов</td><td>Добавить активы и владельцев каждого перехода</td></tr><tr><td>Сканер нашёл десятки предупреждений</td><td>Нет приоритета и вопроса проверки</td><td>Для каждого сигнала назвать актив, данные и воспроизводимый запрос</td><td>Отделить подтверждённый симптом от непроверенной гипотезы</td></tr><tr><td>Проверяющий не знает, где остановиться</td><td>Не записаны лимит, окно и контакт</td><td>Попросить владельца подтвердить метод и стоп-условие</td><td>Не начинать активную проверку до согласования</td></tr><tr><td>Форма входа проверена, а файл выдан без обсуждения</td><td>Карту строили по экрану, а не по данным</td><td>Найти путь файла от приёма до ответа</td><td>Добавить границу выдачи и отдельный вопрос о праве чтения</td></tr><tr><td>Отчёт говорит «уязвимость найдена»</td><td>Наблюдение смешали с выводом о причине</td><td>Повторить запрос, сохранить вход, ответ и версию среды</td><td>Назвать факт, гипотезу и следующий безопасный тест отдельно</td></tr></tbody></table>\n<h2>Отрицательный путь важнее красивого отчёта</h2>\n<p>Аудит должен описывать не только успешную проверку, но и отказ от действия. Если актив найден, но владелец неизвестен, его можно записать в карту и не трогать. Если разрешение относится к staging, production нужно исключить. Если тест требует массовой нагрузки, а в согласовании указан один запрос, нагрузку нельзя «добавить по ходу».</p>\n<p>Есть и технический отрицательный путь. Сервер вернул <code>403</code> для одного запроса. Это наблюдение не доказывает, что все варианты доступа закрыты. Нужно проверить, какой субъект отправил запрос, какой ресурс запрошен, где сервер принял решение и не изменил ли ответ прокси. Если часть контекста неизвестна, запись должна содержать пробел, а не уверенный вывод.</p>\n<p>Такая осторожность не делает аудит бесполезным. Она делает его переносимым. Другой инженер сможет повторить разрешённую проверку, понять границу результата и не расширить действие случайно. Для безопасности непроверенное «всё закрыто» опаснее короткого «этот путь не проверен».</p>\n<h2>Порядок действий</h2>\n<ol><li>Выберите один пользовательский путь с понятной ценой ошибки: вход, изменение профиля, платёж или файл.</li><li>Нарисуйте путь от браузера до конечного эффекта. Добавьте API, identity provider, очередь, хранилище и внешний сервис, если они участвуют.</li><li>Для каждого узла укажите идентификатор, границу ответственности, владельца, класс данных и вопрос проверки.</li><li>Отдельно запишите разрешённую среду, аккаунты, метод, период, лимит запросов, запретные действия и контакт остановки.</li><li>Сформулируйте один положительный и один отрицательный пример. Положительный показывает штатный доступ, отрицательный — ожидаемый отказ.</li><li>Проверьте, что метод отвечает вопросу и не расширяет scope. Если нужно изменить ресурс, отправить нагрузку или выйти на внешний сервис, получите отдельное согласование.</li><li>Сохраните вход, ответ, время, версию среды и идентификатор владельца. Не прикладывайте секреты и персональные данные, если они не нужны для доказательства.</li><li>Разделите результат на факт, гипотезу и действие. Назначьте владельца исправления только после воспроизведения наблюдаемого симптома.</li><li>Повторите карту перед следующим классом проверки. Новый актив или новая граница требуют нового вопроса и проверки разрешения.</li></ol>\n<h2>Ограничения метода</h2>\n<p>Карта активов не заменяет threat model, код-ревью, тесты или внешний penetration test. Она решает более узкую задачу: не потерять границы и не начать действие без понятного контракта. Полная карта невозможна, если архитектура меняется быстрее, чем её документация. В этом случае помечайте неизвестное поле и назначайте владельца, а не заполняйте его догадкой.</p>\n<p>Проверка в staging не доказывает поведение production. Два учебных аккаунта не покрывают все роли. Ответ <code>403</code> не доказывает отсутствие утечки через кеш, экспорт или другой endpoint. Автоматический сканер полезен для поиска кандидатов, но его предупреждение требует проверки входа, ответа, контекста и влияния.</p>\n<p>Нельзя обещать отсутствие уязвимостей по итогам одного маршрута. Нельзя переносить разрешение с одного домена на соседний. Нельзя считать список активов доказательством покрытия. Ограничения должны идти рядом с выводом, иначе читатель примет его за более сильный результат.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Scope готов к первой разрешённой проверке, если другой инженер без устного объяснения может показать выбранный путь, входящие в него активы и переходы, владельца каждого участка и данные, пересекающие границы.</p>\n<p>Он также должен назвать среду, аккаунты, метод, лимит, период и точку остановки. Наконец, он должен показать запрос, ожидаемый ответ и границу результата.</p>\n<p>Проверьте это на одном учебном запросе в согласованной среде. Если команда не может назвать владельца, разрешённый метод или ожидаемый отрицательный ответ, готовность не доказана. Следующее действие — закрыть конкретный пробел в карте. Запускать более широкий тест нельзя.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://owasp.org/www-project-web-security-testing-guide/latest/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Web Security Testing Guide, latest</a> — официальная методика тестирования веб-приложений, включая сбор сведений, проверку контролей и анализ результатов. Версия guide меняется, поэтому в рабочей записи фиксируйте использованный раздел и дату.</li><li><a href=\"https://owasp.org/www-project-application-security-verification-standard/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Application Security Verification Standard</a> — официальный набор проверяемых требований к контролям безопасности приложения. Он помогает формулировать утверждения, но не выдаёт разрешение на тест и не доказывает покрытие конкретной системы.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9116.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9116: A File Format to Aid in Security Vulnerability Disclosure</a> — официальный RFC о формате <code>security.txt</code>. Документ описывает disclosure-контакт и не создаёт подразумеваемого разрешения на активное тестирование.</li></ul>"
|
||
}
|