Как не сломать прод: настройка CORS и заголовков безопасности в ASP.NET
Как избежать критических уязвимостей и не уронить продакшен? Разбираемся с настройкой CORS и заголовков безопасности в ASP.NET: защищаем API от нежелательных запросов, предотвращаем утечки данных и настраиваем безопасные заголовки без боли и хаоса.
263 открытий708 показов
Когда речь идет о безопасности веб-приложений, настройка CORS (Cross-Origin Resource Sharing) и заголовков безопасности — один из первых шагов к защите данных и предотвращению атак. Однако ошибки в конфигурации могут привести к неприятным последствиям: от блокировки легитимных запросов до уязвимостей. Рассказываем, как грамотно настроить CORS и заголовки безопасности в ASP.NET, чтобы избежать проблем и сохранить баланс между защитой и функциональностью.
Что такое CORS?
Браузеры строго следят за безопасностью и запрещают кросс-доменные запросы, если они не разрешены сервером. Это называется политикой одного источника (Same-Origin Policy). Она защищает пользователей от кражи данных, но иногда мешает законным сценариям.
Например, ваше веб-приложение загружено с siteA.com
, но данные хранятся на api.siteB.com
. По умолчанию браузер блокирует такие запросы. Как же обойти это ограничение? Использовать CORS (Cross-Origin Resource Sharing).
CORS — механизм, который позволяет серверу явно указывать, какие домены, схемы и порты могут запрашивать у него данные. Работает через специальные HTTP-заголовки.
Если браузер видит нестандартный запрос (например, PUT
или DELETE
), он сначала отправляет предварительный (preflight) запрос. Это проверка: серверу сообщают о запрашиваемом методе и заголовках, а тот решает — разрешить или запретить. Если сервер даёт добро, основной запрос выполняется.
CORS — не обходной путь, а стандарт, утверждённый W3C. Без него современные веб-приложения не могли бы безопасно взаимодействовать друг с другом.
Какие есть источники: same-origin – different-origin
Два адреса (URL) имеют одинаковый источник (same-origin), если они оба принадлежат одному домену.
Здесь у адресов один источник:
- https://test.com/index.html
- https://test.com/about.html
А здесь — разные:
- https://hello.net
- https://www.hello.com/foo.html
Так, если приложение обратится с адреса https://hello.net
к странице https://www.hello.com/foo.html
без настройки политики, то CORS запрос завершится ошибкой. Чтобы запрос обработался, мы должны сказать браузеру, что обращение к источнику https://www.hello.com
разрешено.
Пример кросс-доменного запроса: JavaScript фронтенд-код, загруженный с URL https://domain-a.com, использует метод fetch() для запроса JSON-файла с URL https://domain-b.com/data.json.
CORS поддерживает безопасные кросс-доменные запросы — это снижает риски при использовании fetch()
и XMLHttpRequest
. Однако CORS лишь управляет разрешёнными междоменными запросами, но не гарантирует безопасность.
Включение CORS в ASP.Net Core приложении
Чтобы добавить механизм CORS в ASP.NET приложение, нужно:
- Добавить сервисы CORS в контейнер сервисов приложения;
- Включить промежуточное ПО CORS в конвейер обработки HTTP-запросов.
В первом шаге необходимо вызвать метод расширения AddCors
для интерфейса IServiceCollection
. Он добавляет в контейнер две сущности: ICorsService
и ICorsPolicyProvider
.
Существует также перегруженная версия этого метода расширения, позволяющая сконфигурировать политику CORS:
Во втором шаге вызывается метод расширения UseCors
определенного для интерфейса IApplicationBuilder
.
Этот метод добавляет промежуточное ПО CorsMiddleware
в конвейер обработки HTTP-запросов. Для UseCors
есть также перегруженная версия:
Есть три способа подключения CORS:
- В ПО промежуточного слоя с помощью именованной политики или политики по умолчанию.
- Использование маршрутизации конечных точек.
- С атрибутом
[EnableCors]
.
[EnableCors]
с именованной политикой обеспечивает лучший контроль в ограничении конечных точек, поддерживающих CORS.
CORS с именованной политикой и ПО промежуточного слоя
ПО промежуточного слоя CORS обрабатывает запросы между источниками. Следующий код применяет политику CORS ко всем эндпоинтам с указанными источниками:
Пример кода выше — часть Program.cs, показывающая инициализацию WebApplication. В ней объявляется константа политики CORS — MyAllowedOrigins
. Далее вызывается AddCors
, в который через параметры передается имя политики и набор адресов разрешенных источников. Ниже есть вызов метода расширения UseCors
, в который передано то же самое имя политики CORS.
Важно:
- URL в настройках CORS не должны заканчиваться на '/', иначе заголовки не вернутся.
- При использовании ПО промежуточного слоя кэширования ответов (ResponseCaching) UseCors должен вызываться перед UseResponseCaching.
- При использовании Endpoints настройте CORS для выполнения между вызовами UseRouting и UseEndpoints.
- Обычно UseStaticFiles вызывается раньше UseCors, но если JavaScript загружает файлы, UseCors следует вызвать раньше.
CORS с политикой по умолчанию и ПО промежуточного слоя
Следующий выделенный код включает политику CORS по умолчанию:
Разница по сравнению с предыдущим фрагментом кода в том, что здесь явно не указывается имя политики CORS. Предполагается, что она будет единственной, в то время как в предыдущем примере можно объявить несколько политик CORS, каждую со своими настройками.
Включение CORS с маршрутизацией конечных точек
Когда используется вызов UseEndpoints, можно подключить политику CORS к каждой отдельно взятой конечной точке, при этом можно указать имя применяемой политики в методе RequireCors
:
В предыдущем коде:
app.UseCors
включает ПО промежуточного слоя CORS. Политики CORS привязываются к конечным точкам внутри методаUseEndpoints
.- Конечные /echo точки и точки контроллера разрешают запросы между источниками с помощью указанной политики –
MyAllowSpecificOrigins
, переданной в RequireCors. - Конечные /echo2 точки и Razor страницы не разрешают запросы между источниками, так как политика по умолчанию не указана.
Включение CORS с помощью атрибутов
Атрибуты [EnableCors]
позволяют подключить CORS только к конечным точкам, вместо глобальной настройки через промежуточное ПО. Плюс [EnableCors]
без параметров включает политику CORS по умолчанию, а [EnableCors("{cors_policy_name}")]
— именованную политику.
Атрибут [EnableCors]
можно применить к:
- Странице Razor Page;
- Контроллеру целиком;
- Методам действия контроллера (Action-methods).
Если CORS включён одновременно через атрибут и промежуточное ПО, применяются обе политики.
Microsoft не рекомендует смешивать способы подключения CORS. Рекомендуется в одном приложении либо применять атрибуты [EnableCors], либо использовать ПО промежуточного слоя.
Вот пример, как применить специфическую политику к каждому экшн-методу контроллера:
Здесь показано, как создать две именованные политики CORS:
Чтобы обеспечить лучшее управление ограничением CORS-запросов, стоит:
- применять атрибуты
[EnbleCors(“{policy_name}”)]
с именованной политикой; - не объявлять CORS-политику по умолчанию;
- не использовать маршрутизацию конечных точек.
Как запретить CORS
Запретить CORS для отдельных экшн-методов контроллера можно с помощью атрибута [DisableCors]
.
Примечание: атрибут [DisableCors] не запрещает CORS, которая была подключена при помощи методов расширения RequireCors в настройках маршрутизации конечных точек.
Пример, как запретить CORS для экшн-метода GetValues2
:
Обратите внимание, что политика CORS разрешена для всего контроллера, так как контроллер декорирован атрибутом [EnableCors(“MyPolicy”)]
, а экшн-метод GetValues2 — [DisableCors]
, значит, CORS-политика не будет применена к этому методу.
Параметры политики CORS
Настройка разрешенных источников
Есть два метода расширения для указания разрешенных источников:
WithOrigins
— позволяет указать список разрешенных источников;AllowAnyOrigin
— разрешает CORS-запросы к любым источникам.
Они влияют на заголовок Access-Control-Allow-Origin предварительных запросов.
Настройка разрешенных HTTP методов
Для указания разрешенных HTTP-методов есть похожая на предыдущие пара методов:
WithMethods
— позволяет указать список разрешенных HTTP-методов;AllowAnyMethod
— разрешает использование любых HTTP-методов в CORS-запросах.
Они тоже влияют на заголовок Access-Control-Allow-Methods preflight-запросов.
Настройка разрешенных HTTP-заголовков
Для указания разрешённых HTTP-заголовков используются:
WithHeaders
— задаёт список разрешённых заголовков,AllowAnyHeader
— разрешает любые заголовки.
AllowAnyHeader
влияет на preflight-запросы и заголовок Access-Control-Request-Headers. Если заголовок не разрешён в WithHeaders, запрос будет отклонён. Если сервер вернёт 200 OK, но без CORS-заголовков, браузер заблокирует cross-origin запрос.
Настройка доступных заголовков ответов
По умолчанию браузер не предоставляет все заголовки ответов приложениям. По умолчанию предоставляются только:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
В спецификации CORS эти заголовки называются simple response headers. Чтобы сделать другие заголовки доступными для приложения, вызовите метод расширения WithExposedHeaders, передав массив имен заголовков в качестве параметра:
Передача учетных данных в запросах между источниками
В CORS-запросах учетные данные нужно специально обрабатывать. Речь идет о cookie и схемах аутентификации HTTP. Чтобы отправить учетные данные в CORS-запросе, клиент должен выставить свойство XMLHttpRequest.withCredentials
в значение true.
Пример использования XMLHttpRequest напрямую в коде JavaScript:
Пример использования Fetch API:
Сервер должен разрешить передачу учетных данных. Чтобы это сделать, вызовите метод расширения AllowCredentials
:
В результате появится заголовок Access-Control-Allow-Credentials в ответе сервера, значит, он разрешил передачу данных. Если в ответе не будет этого заголовка, то браузер не предоставит ответ, и CORS-запрос завершится ошибкой.
Включение учетных данных в CORS-запросы — риск нарушения безопасности. Web-сайт из другого домена может отправить учетные данные аутентифицированного пользователя приложению от имени этого пользователя, но без его ведома.
Спецификация CORS также говорит, что при использовании заголовков Access-Control-Allow-Credentials нельзя применять вызов AllowAnyOrigin
при создании политики CORS.
Предварительные (preflight) запросы
Для некоторых CORS-запросов браузер отправляет preflight-запрос с HTTP-методом OPTIONS перед отправкой самого CORS. Браузер может не отправлять предварительный запрос в этих случаях:
- HTTP-метод основного CORS-запроса один из GET, HEAD или POST;
- Приложение не добавляет в запрос заголовки, кроме Accept, Accept-Language, Content-Language, Content-Type, Last-Event-ID;
- Запрос содержит заголовок Content-Type, то у него должно быть одно из значений: application/x-www-form-urlencoded, multipart/form-data, text/plain
Эти правила применяются к авторским заголовкам, которые задаются вызовом setRequestHeader
объекта XMLHttpRequest
. С User-Agent
, Host
, или Content-Length
(заголовками браузера) так не работает.
У Preflight-запроса могут быть такие заголовки:
- Access-Control-Request-Method — HTTP-метод ( в основном CORS-запросе);
- Access-Control-Request-Headers — набор заголовков, которые будут установлены приложением в основном запросе.
Если предварительный запрос отклоняется сервером, то вылезет код 200 ОК, и браузер не будет пытаться отправлять CORS. При отладке в браузере (F12 tools) в консоли приложения появятся ошибки, если preflight-запрос отклонится сервером.
Чтобы разрешить передачу только определенных заголовков, вызывайте WithHeaders
:
Чтобы разрешить передачу всех авторских заголовков, используйте вызов AllowAnyHeader.
Автоматическая обработка предварительных запросов
Если политика CORS применяется с помощью одного из приведенных ниже способов:
- глобально, с помощью вызова
app.UseCors
в модуле Program.cs; - с помощью атрибута
[EnableCors]
,
то ASP.NET будет автоматически обрабатывать запросы OPTIONS.
Обработка предварительных запросов с помощью атрибута [HttpOptions]
В ASP.NET предусмотрена возможность явного объявления методов для обработки предварительных запросов. Вот пример, как создать методы для обработки OPTIONS-запросов с [HttpOptions]
:
Установка срока действия ответов для предварительных запросов
Браузер может закэшировать preflight-запросы, чтобы снизить нагрузку на сервер и сократить время обработки основных CORS-запросов. Чтобы ограничить срок хранения ответов в кэше, используйте заголовок Access-Control-Max-Age в ответе prefligh-запроса. Для этого при инициализации CORS-политики вызовите метод SetPreflightMaxAge
:
CORS позволяет обращаться к ресурсам на других доменах, но требует аккуратной настройки, чтобы не сломать безопасность.
В примерах выше CORS-политики определялись в коде приложения, но в проде такой подход не всегда удобен. При переносе на другой хостинг или смене окружения настройки лучше вынести в конфигурацию сервера или прокси. Это упростит поддержку и адаптацию без правок в коде.
Полезные ссылки
Cross-Origin Resource Sharing (CORS) - HTTP | MDN
Enable Cross-Origin Requests (CORS) in ASP.NET Core | Microsoft Learn
Больше про .NET — в нашем тг канале!
263 открытий708 показов