Cors и безопасность междоменных запросов: уязвимости и правильная конфигурация

7 минут чтения

CORS — механизм браузера, который разрешает или ограничивает доступ страницы одного источника к ответам другого. Безопасная настройка CORS для API требует явного списка доверенных origin, корректной обработки preflight-запросов, осторожной работы с cookies и запрета широких политик для приватных данных. CORS не заменяет аутентификацию, авторизацию и защиту от CSRF.

Главные риски и принципы настройки CORS

  • Разрешайте только необходимые origin, методы и заголовки.
  • Не сочетайте Access-Control-Allow-Origin: * с Access-Control-Allow-Credentials: true.
  • Проверяйте значение Origin по точному списку, а не по небезопасному совпадению строк.
  • Разделяйте публичные ресурсы и API, работающие с учётными данными.
  • Считайте CORS браузерным ограничителем доступа, а не самостоятельной системой авторизации.

Как работает механизм CORS: коротко и технически

CORS расшифровывается как Cross-Origin Resource Sharing — обмен ресурсами между разными origin. Origin определяется схемой, хостом и портом. Например, https://app.example.ru и https://api.example.ru имеют разные origin, даже если принадлежат одному домену.

Браузер добавляет к запросу заголовок Origin. Сервер отвечает заголовками политики, например:

Access-Control-Allow-Origin: https://app.example.ru
Vary: Origin

Если ответ не содержит подходящего разрешения, браузер может получить данные от сервера, но не передаст их JavaScript-коду страницы. CORS не блокирует сам серверный запрос и не защищает API от прямых вызовов через серверные клиенты, curl или мобильные приложения.

Для простых запросов браузер отправляет запрос сразу. Для потенциально небезопасных или нестандартных запросов сначала выполняется preflight с методом OPTIONS:

OPTIONS /users HTTP/1.1
Origin: https://app.example.ru
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
  • Проверьте, какие origin считаются доверенными.
  • Разделите простые запросы и preflight-сценарии.
  • Не воспринимайте CORS как замену контролю доступа.

Частые уязвимости при неправильной настройке заголовков

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

  1. Отражение произвольного origin. Сервер возвращает значение Origin без проверки. Злоумышленник размещает страницу на своём сайте и получает доступ к ответу API.
  2. Разрешение credentials для широкого списка. При cookies или других учётных данных компрометация доверенного origin может привести к чтению приватных ответов.
  3. Неполная проверка домена. Проверка по подстроке может принять https://example.ru.attacker.test за доверенный адрес.
  4. Отсутствие Vary: Origin. Кэш может выдать ответ, сформированный для одного origin, другому клиенту.
  5. Слишком широкие методы и заголовки. Разрешение * для методов и заголовков усложняет контроль изменений API.
  6. Ошибочная надежда на CORS. API остаётся уязвимым, если сервер не проверяет токен, права пользователя и состояние запроса.

При настройке безопасности CORS анализируйте не только заголовки ответа, но и фактический сценарий: какие данные возвращаются, используются ли cookies, как работает кэш и кто может отправить запрос.

  • Проверьте обработку неизвестного Origin.
  • Протестируйте origin с похожими доменными именами.
  • Добавьте Vary: Origin при динамическом выборе разрешённого origin.
  • Сопоставьте разрешённые методы с реальными маршрутами API.

Опасные практики: что никогда не делать с Access-Control-Allow-Origin

Заголовок Access-Control-Allow-Origin определяет, какой origin может читать ответ в браузере. Он не должен формироваться на основе непроверенного пользовательского ввода и не заменяет серверную авторизацию.

  1. Не возвращайте входящий origin без белого списка. Конструкция, эквивалентная отражению Origin, превращает CORS в разрешение для любого сайта.
  2. Не используйте wildcard для приватного API. Политика Access-Control-Allow-Origin: * подходит только для действительно публичных ресурсов, не требующих учётных данных.
  3. Не доверяйте всем поддоменам через простое окончание строки. Разрешение должно учитывать точную схему, хост и при необходимости порт.
  4. Не разрешайте credentials без необходимости. Cookies и HTTP-аутентификация увеличивают последствия ошибки в списке origin.
  5. Не скрывайте проблему через отключение проверок в браузере. Это маскирует серверную ошибку и не создаёт рабочую защиту для пользователей.

Если ресурсов мало, применяйте упрощённую, но безопасную схему: отделите публичные файлы от приватного API, используйте один фиксированный frontend-origin и не включайте credentials там, где достаточно токена в явно разрешённом заголовке. Для внутренних стендов задайте отдельный список origin, а не глобальное разрешение.

  • Запретите отражение непроверенного Origin.
  • Оставьте wildcard только для публичного контента.
  • Проверяйте схему, хост и порт целиком.
  • Документируйте каждый origin в конфигурации.

Корректная конфигурация: шаги и политики для разных сценариев

Надёжная настройка CORS для API начинается с инвентаризации клиентов. Для каждого маршрута определите frontend-origin, методы, заголовки, необходимость cookies и допустимый срок кэширования preflight-ответа.

Последовательность настройки

  1. Составьте точный список production- и staging-origin.
  2. Разрешите только используемые методы, например GET, POST и OPTIONS.
  3. Перечислите необходимые заголовки вместо безусловного разрешения всех.
  4. Настройте корректный ответ на OPTIONS с кодом успешного выполнения.
  5. Добавьте Vary: Origin, если разрешённый origin выбирается динамически.
  6. Проверьте политику отдельно для публичных и авторизованных маршрутов.

Политики для разных ресурсов

  • Публичные изображения, шрифты или документы: допустима широкая политика, если содержимое не является приватным.
  • Публичный API без пользовательских данных: ограничьте методы и заголовки; wildcard применяйте только после проверки модели угроз.
  • Личный кабинет: используйте точные origin и включайте credentials только при реальной необходимости.
  • Внутренний сервис: ограничьте доступ корпоративными origin, но всё равно проверяйте аутентификацию на сервере.
  • Ограниченные ресурсы: кэшируйте заранее подготовленные ответы, уменьшайте число разрешённых методов и исключайте cookies из cross-origin-сценария.

Пример минимальной логики на сервере:

allowedOrigins = [
  "https://app.example.ru",
  "https://admin.example.ru"
]

if request.origin in allowedOrigins:
    response.headers["Access-Control-Allow-Origin"] = request.origin
    response.headers["Vary"] = "Origin"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Authorization, Content-Type"
  • Сформируйте белый список origin.
  • Ограничьте методы и заголовки фактическими потребностями.
  • Разделите публичную и приватную политики.
  • Проверьте preflight до выпуска клиента.

Аутентификация, куки и заголовки: безопасная передача учётных данных

CORS и безопасность междоменных запросов: типичные уязвимости и правильная конфигурация - иллюстрация

Куки браузер отправляет cross-origin только при соответствующей политике клиента и сервера. Для такого сценария сервер должен указать конкретный origin и Access-Control-Allow-Credentials: true; wildcard для origin здесь неприменим.

  1. Ошибка: считать CORS авторизацией. Сервер обязан самостоятельно проверять сессию, токен и права доступа.
  2. Ошибка: разрешать credentials всем origin. При компрометации разрешённого сайта последствия могут включать чтение чувствительных ответов.
  3. Ошибка: забывать о CSRF. CORS не отменяет CSRF-защиту для cookie-аутентификации; используйте подходящие SameSite-настройки и CSRF-токены по модели приложения.
  4. Ошибка: разрешать лишние заголовки. Authorization и пользовательские заголовки добавляйте только при необходимости.
  5. Миф: запрет CORS остановит любой вредный запрос. CORS ограничивает чтение ответа JavaScript-кодом, но не является универсальным фильтром сетевого трафика.
Access-Control-Allow-Origin: https://app.example.ru
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Authorization, Content-Type
Vary: Origin
  • Включайте credentials только для cookie-сценариев.
  • Не используйте wildcard вместе с credentials.
  • Добавьте CSRF-защиту для cookie-аутентификации.
  • Проверяйте авторизацию независимо от CORS.

Тестирование и мониторинг CORS в продакшн-среде

Проверяйте не только успешный запрос из рабочего frontend. Негативные тесты должны подтверждать, что неизвестный origin не получает разрешающие заголовки, preflight ограничивает методы, а кэш не смешивает ответы для разных origin.

Минимальный сценарий проверки

curl -i https://api.example.ru/profile 
  -H "Origin: https://app.example.ru"

curl -i -X OPTIONS https://api.example.ru/profile 
  -H "Origin: https://app.example.ru" 
  -H "Access-Control-Request-Method: GET" 
  -H "Access-Control-Request-Headers: Authorization"

Повторите тест с неизвестным origin, HTTP-вариантом домена, соседним поддоменом и origin с другим портом. В журналах фиксируйте маршрут, origin, результат проверки политики и факт preflight; не записывайте токены и содержимое cookies.

  • Проверьте разрешённый и запрещённый origin.
  • Проверьте preflight для каждого нестандартного метода.
  • Проверьте кэширование при разных значениях Origin.
  • Настройте алерты на неожиданные origin и изменения политики.
  • Повторите тесты после каждого изменения gateway или reverse proxy.

Практические ответы по спорным кейсам CORS

Можно ли разрешить Access-Control-Allow-Origin: * для REST API?

Только если API действительно публичен и не обрабатывает пользовательские данные или учётные данные браузера. Для приватного API используйте точный список origin.

Защищает ли CORS API от запросов через curl?

Нет. CORS применяется браузером, поэтому сервер должен отдельно проверять аутентификацию, авторизацию, лимиты и валидацию входных данных.

Почему браузер отправляет OPTIONS перед POST?

CORS и безопасность междоменных запросов: типичные уязвимости и правильная конфигурация - иллюстрация

Запрос предварительной проверки нужен, когда метод, заголовки или параметры не относятся к простому запросу. Сервер должен явно подтвердить допустимый origin, метод и заголовки.

Нужно ли разрешать credentials для JWT в заголовке Authorization?

Обычно нет: credentials относится прежде всего к браузерным cookies, TLS-сертификатам и HTTP-аутентификации. Для JWT в Authorization всё равно нужно явно разрешить этот заголовок.

Почему недостаточно разрешить весь домен через подстановку?

Небезопасная проверка может принять вредный домен, содержащий доверенную строку. Сравнивайте нормализованные scheme, host и port с точными разрешёнными значениями.

Что выбрать при ограниченных ресурсах и небольшом бюджете на сопровождение?

Используйте один фиксированный frontend-origin, минимум разрешённых методов и заголовков, отсутствие cookies в cross-origin-сценарии и отдельную политику для staging. Это проще тестировать и мониторить, чем динамически разрешать широкий набор источников.

Почему после изменения CORS часть клиентов получает старый ответ?

Причиной может быть кэширование ответа без Vary: Origin или кэширование preflight. Проверьте reverse proxy, CDN и заголовки кэширования на всех промежуточных слоях.