CORS (cross-origin resource sharing)
CORS (cross-origin resource sharing, совместное использование ресурсов между источниками) это механизм на основе HTTP-заголовков, с помощью которого сервер указывает, каким другим источникам браузер может разрешить загрузку его ресурсов. Без нужных заголовков Access-Control браузер не даст странице прочитать кросс-доменный ответ. Это правило браузера, а не файрвол, и аутентификацию API оно не заменяет.
Источники, политика одного источника и роль CORS
Источник (origin) это сочетание схемы, хоста и порта. Страница на https://app.example.com и API на https://api.example.com это разные источники. По умолчанию, как объясняет руководство MDN по CORS, скрипты с fetch() подчиняются политике одного источника, и кросс-доменный ответ можно использовать, только если в нём есть правильные заголовки CORS. Через CORS сервер даёт на это согласие.
Простые запросы, например GET с безопасными заголовками, отправляются сразу, и сервер всё равно должен ответить заголовком Access-Control-Allow-Origin. Другие запросы, например с телом JSON или заголовком Authorization, вызывают preflight: браузер сначала посылает запрос OPTIONS с заголовками Access-Control-Request-Method и Access-Control-Request-Headers и отправляет настоящий запрос, только если ответ это разрешает. «Ошибка CORS» в консоли обычно значит, что в ответе не было заголовков, нужных браузеру.
Правильный ответ
Этот ответ на preflight разрешает вызывать API одному конкретному фронтенду:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Vary: Origin
MDN отмечает, что если сервер указывает один источник, а не *, он должен также отправлять Vary: Origin. Заголовок Access-Control-Max-Age говорит, как долго браузер может кэшировать результат preflight. Ответ на preflight лишь даёт разрешение, поэтому настоящий ответ, который идёт следом, тоже должен содержать Access-Control-Allow-Origin.
Если фронтенд отправляет cookie или другие учётные данные, правила строже. Сервер обязан ответить Access-Control-Allow-Credentials: true и указать конкретный источник. По MDN, подстановочный знак * для запросов с учётными данными использовать нельзя, а браузер заблокирует такой ответ. Для публичных данных, запрашиваемых без учётных данных, Access-Control-Allow-Origin: * допустим.
Типичные ошибки
Разработчики часто вставляют подстановочный знак, чтобы ошибка исчезла из консоли. Вот шаблоны, которые ослабляют CORS:
- Отражение любого Origin. PortSwigger описывает серверы, копирующие заголовок Origin запроса в ответ при включённых учётных данных, из-за чего любой сайт может читать чувствительные ответы.
- Доверие к
null. Злоумышленники могут получить источник null из iframe в песочнице, поэтому его разрешение обходит белый список. - Нестрогое сопоставление. Проверки по префиксу, суффиксу или регулярному выражению могут принять похожие домены, которые зарегистрировал атакующий.
- Дублирующиеся заголовки. MDN требует отправлять ровно один
Access-Control-Allow-Origin; прокси, CDN и промежуточное ПО приложения могут добавить по своему. - CORS как защита. Он работает в браузере. Скрипт или
curlон не остановит, а PortSwigger подчёркивает, что CORS не заменяет серверную защиту и не защищает от CSRF. Оставляйте аутентификацию на каждом эндпоинте.
Где настроить и как проверить
Настраивайте CORS только в одном месте: в промежуточном ПО приложения или на обратном прокси перед ним. В nginx это выглядит так:
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary Origin always;
В документации nginx сказано, что директивы add_header наследуются с верхнего уровня, только если на текущем их нет, а параметр always добавляет заголовок к любому коду ответа, включая ошибки. Поэтому location со своим add_header теряет унаследованные заголовки CORS.
Проверьте из командной строки, отправив Origin и прочитав заголовки ответа:
curl -i -H "Origin: https://app.example.com" https://api.example.com/items
Заголовки CORS отделены от заголовков безопасности, которые защищают саму страницу. Разрешайте только нужные источники, в духе политики «запрет по умолчанию», и включите проверку в свой чек-лист безопасности для вайб-кодинга.
Связанные термины: Обратный прокси, nginx, Заголовки безопасности, Политика «запрет по умолчанию».
Часто задаваемые вопросы
Что означает CORS и что он делает?
CORS расшифровывается как cross-origin resource sharing. Это механизм на основе HTTP-заголовков, с помощью которого сервер объявляет, каким другим источникам браузер может разрешить загрузку его ресурсов, смягчая политику одного источника.
Почему я получаю ошибку CORS?
Обычно в ответе сервера нет нужных браузеру заголовков Access-Control или они не совпадают с запросом. По MDN, большинство ошибок CORS можно исправить только на сервере, например вернув правильный разрешённый источник.
Безопасен ли Access-Control-Allow-Origin: *?
Для публичных данных, запрашиваемых без учётных данных, допустим. С cookie или другими учётными данными подстановочный знак запрещён, и браузер заблокирует ответ, поэтому укажите точный источник.
Защищает ли CORS мой API от атакующих?
Нет. CORS применяют браузеры, он не останавливает curl, скрипты и другие серверы. PortSwigger подчёркивает, что он не заменяет серверную защиту, поэтому оставляйте аутентификацию и авторизацию в своём API.
Почему браузер сначала отправляет запрос OPTIONS?
Это preflight. Для запросов, которые могут повлиять на данные пользователя, например с нестандартными заголовками, браузер сначала спрашивает сервер через OPTIONS, разрешён ли настоящий запрос.
Хотите увидеть, в каком состоянии ваш сервер?
Запустите бесплатную read-only проверку сервера или откройте Security Lab и посмотрите цикл обнаружение, сдерживание, восстановление, проверка в действии.
Получить бесплатную проверку сервераОткрыть Security Lab