Files
progcode/editorial/agent-rewrites/061.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 61,
"slug": "editorial-2026-04-field-modern-web-security",
"title": "Почему CORS не защищает POST: как проверить границы веб-безопасности",
"excerpt": "Чужой сайт может отправить запрос с cookie даже после настройки CORS. Разбираем механизм, связываем угрозу с контролем и evidence, а затем получаем проверяемый stop или ограниченный hand-off.",
"contentHtml": "<p>Симптом выглядит обнадёживающе: API отвечает на запросы только с нужным <code>Origin</code>, а в DevTools чужой сайт получает ошибку CORS. Команда помечает проблему закрытой. Но endpoint всё ещё принимает cross-site POST с cookie. Браузер может отправить запрос и не показать ответ атакующему. Если запрос меняет адрес доставки, пароль или лимит, ошибки CORS не возвращают деньги и не отменяют изменение.</p>\n<p>Цена такой подмены — ложное чувство защиты. CORS управляет чтением ответа из браузера. CSRF-защита управляет тем, может ли чужой сайт заставить браузер выполнить действие от имени пользователя. Эти механизмы стоят рядом, но решают разные задачи. Без явного пути атаки, контрольной точки и наблюдаемого evidence слово «проверено» слишком сильное.</p>\n<p>Тезис статьи простой: security review должен связывать asset, путь атаки, interruption и границу доказательства. Положительный результат подтверждает только эту связь. Он не разрешает deploy, не доказывает защиту production и не заменяет проверку входа, сессии, заголовков или поведения браузера.</p>\n<h2>Один запрос показывает разницу между CORS и CSRF</h2>\n<p>Пусть пользователь вошёл в <code>bank.example</code>. Браузер хранит cookie сессии и автоматически прикладывает её к запросу на этот origin. На странице <code>evil.example</code> размещена форма. Форма отправляет POST на банковский endpoint. Для простой формы браузер не обязан сначала выполнить CORS preflight. Сервер может получить cookie и изменить состояние.</p>\n<pre><code>&lt;form action=\"https://bank.example/profile/email\" method=\"POST\"&gt;\n &lt;input name=\"email\" value=\"attacker@example.net\"&gt;\n&lt;/form&gt;\n&lt;script&gt;document.forms[0].submit()&lt;/script&gt;\n\n// Учебный пример. Он не отправляется и не доказывает поведение\n// конкретного браузера или endpoint.</code></pre>\n<p>Если сервер принимает такой POST только по cookie, запрос остаётся опасным. Проверка <code>Origin</code> или <code>Referer</code> может добавить условие. Synchronizer token или signed double-submit token связывает действие с формой приложения. Cookie с подходящим <code>SameSite</code> уменьшает поверхность, но его режим зависит от контекста браузера и схемы запроса. Без проверки на сервере нельзя считать один флаг достаточным.</p>\n<p>Тот же endpoint может иметь корректный CORS и всё равно быть уязвимым к изменению состояния. И наоборот: endpoint может не разрешать чтение ответа чужому origin, но нуждаться в CSRF-токене для опасного действия. Сначала назовите действие. Потом проверьте, какой контроль его прерывает.</p>\n<h2>Механизм проверки: путь, контроль, evidence</h2>\n<p>Начните с одного asset. Для примера это email пользователя. Путь атаки имеет порядок: чужая страница создаёт запрос, браузер добавляет cookie, endpoint принимает изменение, сервер сохраняет новый email. CORS находится на границе чтения ответа. Он не обязан останавливать первые три шага. CSRF-токен и серверная проверка origin находятся ближе к операции изменения.</p>\n<p>У каждого контроля должна быть одна фраза с глаголом. «CORS включён» ничего не говорит о действии. «Сервер отклоняет state-changing POST без валидного токена» описывает interruption. Такая запись проверяема: можно назвать вход, ответ и правило отказа. Если token проверяется только в JavaScript, контроль не стоит на серверной границе. Если endpoint разрешает запрос без cookie, нужно отдельно оценить анонимную операцию.</p>\n<figure><img src=\"/assets/editorial/2026/modern-web-security-2026-evidence-review-loop.svg\" alt=\"Цикл проверки веб-безопасности: путь атаки, контроль, ограниченное evidence, остаточный риск и следующий review\" loading=\"lazy\" /><figcaption>Граница hand-off должна быть видна. Учебный цикл передаёт только названный scope проверки и не превращается в решение о выпуске.</figcaption></figure>\n<p>Evidence тоже имеет границу. Заголовок <code>Access-Control-Allow-Origin</code> показывает настройку чтения ответа. Он не показывает, что сервер отверг чужой POST. Ответ <code>403</code> на запрос без токена показывает одну отрицательную ветку. Он не доказывает, что все state-changing endpoints используют тот же middleware. Эти два наблюдения нельзя склеить в общий verdict.</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>Чужой origin видит CORS error</td><td>Браузер не отдаёт ему response body</td><td>Отправить отдельный state-changing POST и проверить запись</td><td>Добавить серверную CSRF-защиту, если действие использует cookie</td></tr><tr><td>POST проходит без token</td><td>Endpoint доверяет cookie без дополнительного доказательства намерения</td><td>Повторить запрос без token в изолированной учебной среде</td><td>Отклонять запрос до изменения состояния; сохранить ответ и correlation id</td></tr><tr><td>Token есть в форме, но не проверяется</td><td>Контроль остался на клиенте</td><td>Вызвать endpoint напрямую без выполнения UI</td><td>Перенести проверку на сервер и покрыть отрицательным тестом</td></tr><tr><td>Один endpoint защищён, другие нет</td><td>Проверка привязана к странице, а не к классу операции</td><td>Составить список state-changing routes и найти общий middleware</td><td>Назначить владельца непокрытых маршрутов; не выдавать общий verdict</td></tr><tr><td>После изменения появился 403</td><td>Изменился контракт запроса или cookie policy</td><td>Сверить token, origin, cookie и права в позитивном сценарии</td><td>Исправить конкретный контракт; не ослаблять правило глобально</td></tr></tbody></table>\n<h2>Пример серверной границы</h2>\n<p>Ниже псевдокод для учебного review. Он показывает порядок условий, но не является готовым middleware. Реальный фреймворк должен сам определить, как извлекать cookie, хранить token, сравнивать origin и формировать ответ.</p>\n<pre><code>function updateEmail(request) {\n if (request.method !== 'POST') return allowMethod();\n if (!sameOrigin(request.headers.origin)) return reject(403);\n if (!validCsrfToken(request.cookie, request.body.csrf)) {\n return reject(403);\n }\n if (!validEmail(request.body.email)) return reject(400);\n return saveEmail(request.session.userId, request.body.email);\n}\n\n// Учебный пример: не содержит production storage, logging\n// или конкретную реализацию token.</code></pre>\n<p>Важен не синтаксис, а место проверки. Запрос отклоняется до записи. Позитивная ветка требует действующую сессию и корректный token. Негативная ветка проверяет запрос без token, с чужим origin и с повторно использованным token. Если тест вызывает только функцию в памяти, он подтверждает порядок условий в примере. Он не подтверждает маршрутизацию, cookie flags, proxy и реальную базу.</p>\n<p>Для CORS правило другое. Разрешайте конкретные origins, не отражайте произвольный заголовок <code>Origin</code>, не сочетайте wildcard с credentialed requests и проверяйте, нужен ли endpoint вообще для cross-origin чтения. Но даже строгий allowlist не заменяет CSRF-защиту. Это отрицательный путь статьи: исправление видимого CORS-симптома может не менять способность чужой формы отправить запрос.</p>\n<h2>Как передавать результат без ложного допуска</h2>\n<p>Передача результата должна содержать четыре поля: threat id, control id, observed evidence и остаток. Например, threat — «чужая форма меняет email в сессии пользователя». Control — «сервер отклоняет POST без token и при несоответствующем origin». Evidence — «в учебном тесте запрос без token вернул 403 до вызова сохранения». Остаток — «не проверены другие маршруты и поведение production proxy».</p>\n<p>Такой hand-off не означает, что система защищена. Он означает, что следующий человек видит, какую ветку повторить и где заканчивается наблюдение. Нельзя заменить остаток фразой «остальное стандартно». Нельзя перенести evidence с одного endpoint на весь API. Нельзя считать отсутствие ответа у атакующего доказательством отсутствия изменения в системе.</p>\n<h2>Порядок действий</h2>\n<ol><li><strong>Назовите asset и действие.</strong> Запишите, что может изменить чужой запрос: email, пароль, заказ или иной объект.</li><li><strong>Нарисуйте путь.</strong> Укажите страницу-источник, cookie, endpoint, проверку и запись. Не объединяйте чтение ответа с изменением состояния.</li><li><strong>Разделите controls.</strong> Отдельно опишите CORS, CSRF token, origin check, SameSite и авторизацию. Для каждого назовите interruption.</li><li><strong>Проверьте отрицательный запрос.</strong> В учебной или специально разрешённой среде уберите token, измените origin и убедитесь, что запись не произошла.</li><li><strong>Проверьте позитивный запрос.</strong> С действующей сессией и корректным token операция должна пройти. Сохраните только наблюдаемые поля.</li><li><strong>Сверьте покрытие.</strong> Найдите все state-changing маршруты и убедитесь, что правило применяет общий серверный слой, а не одну форму.</li><li><strong>Запишите остаток.</strong> Назовите непроверенные proxy, браузеры, cookie-режимы, фоновые операции и endpoints.</li><li><strong>Передайте ограниченный результат.</strong> Если связка неполна, верните stop с точным следующим вопросом. Не превращайте учебный output в release approval.</li></ol>\n<h2>Ограничения и критерий готовности</h2>\n<p>Эта проверка не измеряет вероятность атаки, размер ущерба, покрытие всех маршрутов, устойчивость к обходу proxy или состояние системы после deploy. Пример не обращается к реальному API, не запускает браузер и не использует production cookie. Официальные стандарты помогают выбрать вопросы, но не подтверждают конкретную конфигурацию. CSP, например, полезна как дополнительная граница для content injection, но не отменяет безопасную обработку данных. ASVS задаёт требования для верификации web-контролей, а не автоматический verdict. NIST описывает наборы техник проверки, а не единый сертификат.</p>\n<p>Критерий готовности должен быть узким и воспроизводимым: для каждого state-changing endpoint существует один записанный attack path, серверная проверка стоит до изменения состояния, позитивный запрос проходит с корректным token, отрицательный запрос не создаёт запись, а evidence содержит маршрут, вход, статус и границу применимости. Непокрытый маршрут остаётся stop. Если нельзя показать, какой контроль прервал путь, review не завершён.</p>\n<p>Если после проверки требуется изменить код или конфигурацию, это отдельная уполномоченная работа. Hand-off только указывает следующий шаг. Он не включает заголовки, не меняет cookie policy и не разрешает выпуск. Такое ограничение делает вывод уже, но честнее: команда знает, что проверила, чего не проверила и какую работу нельзя считать выполненной.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/CSP/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C: Content Security Policy Level 3</a> — описывает CSP как дополнительный defence-in-depth механизм, а не замену безопасной обработке данных.</li><li><a href=\"https://owasp.org/www-project-application-security-verification-standard/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP: Application Security Verification Standard 5.0.0</a> — задаёт основу для проверки технических security controls веб-приложения.</li><li><a href=\"https://doi.org/10.6028/NIST.IR.8397\" target=\"_blank\" rel=\"noopener noreferrer\">NISTIR 8397: Guidelines on Minimum Standards for Developer Verification of Software</a> — перечисляет threat modeling, тестирование, code review и другие техники верификации; ни одна техника сама по себе не подтверждает весь security posture.</li></ul>"
}