Предотвращение CSRF-атак в ASP.NET Core

Хакеры используют метод подделки межсайтовых запросов, чтобы получить привилегии законных аутентифицированных пользователей приложения, а затем выполнить любое действие, на которое у жертв есть права. Без способов обнаружить это у приложения не будет возможности различить поддельный и законный запрос, ASP.NET имеет надежную защиту, если её правильно настроить, и Core предлагает некоторые способы, чтобы подделка межсайтовых запросов оказалась безуспешной.

Подделка межсайтовых запросов (CSRF или XSRF или один клик) - это атака, которая, в отличие от сценария или внедрения SQL, на самом деле не зависит от того, что разработчики могли сделать явно неправильно. Ваше приложение ASP.NET может иметь обычную форму, которая публикует данные в рамках проверки подлинности cookie, и оно также может использовать привязку модели и проверку данных и запросов для предотвращения внедрения потенциально вредоносных данных: несмотря на это, форма и всё приложение еще имеет риск быть скомпрометированным. Точнее говоря, риск состоит не в том, что набор данных приложения скомпрометирован, а в том, что одна конкретная учетная запись пользователя взломана и принадлежит другому пользователю. Фактический ущерб для приложения зависит от типа и набора привилегий у скомпрометированной учетной записи. CSRF входит в список основных угроз организации OWASP.

Хорошей новостью является то, что ASP.NET MVC и ASP.NET Core предоставляют эффективные инструменты, которые могут защитить ваши формы от CSRF-атак. Плохая новость заключается в том, что вы должны явно включить эти инструменты. Однако в ASP.NET Core дела пойдут лучше, поскольку защита CSRF по умолчанию почти полностью включена. Давайте узнаем больше о механике атаки и стратегии защиты.

Механика CSRF

Основной жертвой атаки является пользователь приложения, но поскольку злоумышленник может выдать себя за пользователя только на короткий промежуток времени - некоторое неправильное поведение может также возникнуть на уровне приложения, особенно если роль взломанного пользователя включает несколько разрешений.

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

<body onload="postForm()">
   <!-- Some attractive content -->
</body>

Скрипт запускается при загрузке страницы и может создать форму «на лету» для отправки данных на определенный URL. Cookie-файл аутентификации отправляется, когда выполняется сабмит формы, потому что все это происходит на компьютере жертвы, а домен cookie-файла совпадает с доменом целевого сервера. Однако, чтобы быть вредоносной, атака должна быть нацелена на URL-адрес, который выполняет чувствительную операцию, такую как изменение пароля или удаление некоторых данных. Кроме того, хакер, должно быть, обнаружил довольно много информации о внутренней структуре сайта. Однако атака определенно возможна независимо от того, существует ли тогда какая-либо возможность причинить вред или нет.

Чтобы защититься от CSRF, вам нужно добавить дополнительную и специфичную для пользователя информацию в каждую форму, которую хакер не может узнать. Излишне говорить, что код на стороне сервера должен проверять, что эта дополнительная информация не была подделана.

Предотвращение CSRF-атак в ASP.NET

ASP.NET MVC уже давно предлагает сильную линию защиты от CSRF-атак, но разработчики слишком часто забывают включить ее. Причина в том, что защита требует некоторых усилий, и все слишком заняты и спешат, чтобы иметь возможность остановиться и подумать об основных фактах безопасности.

Любая HTML-форма, созданная из страниц Razor, должна включать вызов HTML-хелпера AntiForgeryToken. Хелпер добавляет скрытое поле и cookie.

<form ...>
   @Html.AntiForgeryToken()
   <!-- Content of the form -->
</form>

Скрытое поле содержит случайно сгенерированный двоичный двоичный объект длиной 128 байт. Файл cookie содержит тот же двоичный объект, но зашифрованный с использованием API защиты данных, причем ключ хранится в локальном центре безопасности операционной системы Windows.

<input name="__<a id="post-71688-_Hlk487132269"></a>RequestVerificationToken" 
       type="hidden" 
       value="saTFWpk...c4YbZAm" />

Скрытое поле не передается, когда пользователь щелкает ссылку на внешнем веб-сайте, тогда как cookie-файл может попасть на сайт злоумышленника. Однако, поскольку содержимое cookie-файла зашифровано, хакер не может выяснить значение для скрытого поля и не может подделать действительный POST запрос.

Эта стратегия защиты работает так же долго, как код контроллера, который обрабатывает POST, дважды проверяет, что он получает скрытое поле с именем __RequestVerificationToken и файл cookie с тем же именем. Если оба отправлены, то код должен расшифровать содержимое куки и сопоставить его с содержимым скрытого поля. Если два значения не совпадают, то должно быть выдано исключение безопасности. ASP.NET MVC. Далее рассмотрим атрибут ValidateAntiForgeryToken.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(...)
{
   ...
}

Атрибут работает только с запросами POST. Это связано с тем, что запросы GET никогда не должны выполнять никаких задач, которые могут изменить состояние системы.

Предотвращение CSRF-атак в ASP.NET Core

В ASP.NET Core ядро стратегии защиты такое же. Арсенал инструментов, напротив, немного более мощный. В частности, Microsoft пытается избавить разработчиков от необходимости заботиться защитой приложений от CSRF. HTML-помощник AntiForgeryToken все еще там и работает как обычно. Атрибут ValidateAntiForgeryToken все еще существует и работает так же, как и в классическом ASP.NET MVC. Другими словами, то же решение, которое выполняло эту работу в более старых версиях ASP.NET MVC, все еще можно использовать как есть в ASP.NET Core. Кроме того, ASP.NET Core предлагает несколько других решений, чтобы сделать процесс добавления токена проверки запроса немного более удобным.

Движок Razor в ASP.NET Core поддерживает серверный компонент нового типа, который называется «помощник по тегам». Парсер Razor вызывает помощник по тегам для преобразования некоторых пользовательских элементов разметки и атрибутов в стандартные элементы и атрибуты HTML. В конце концов, исходящий вывод - это тот же HTML, который вы могли бы кодировать самостоятельно, за исключением того, что синтаксис, необходимый для его выражения, является более лаконичным и читабельным. Давайте рассмотрим следующий синтаксис для элемента FORM.

<form class="form-horizontal" 
      method="post"
      asp-controller="Account" 
      asp-action="Register">
   ...
</form>

Ни asp-action, ни asp-controller не являются стандартными атрибутами HTML. Однако они распознаются как вспомогательные атрибуты тегов Visual Studio и IntelliSense и, что наиболее важно, анализатором Razor ASP.NET Core поставляется с целой библиотекой вспомогательных компонентов тегов, на которые представление может ссылаться с помощью следующей новой директивы @addTagHelper:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Все классы в библиотеке, которые являются хелперами тегов, имеют специальный атрибут и являются производными от общего базового класса. Когда синтаксический анализатор встречает эти элементы, он возвращается к хелперу по тегам, и хелпер по тегам имеет возможность проверить структуру элемента и может редактировать его содержимое. В частности, атрибуты *asp- , связанные с элементом FORM, изменяют атрибут action в FORM, устанавливая для него действие, которое определяется результатом сочетания имени контроллера (атрибута asp-controller) и имени действия (атрибута asp-action), Применяя эти атрибуты, вы также указываете вспомогательному тегу формы отправлять скрытое поле защиты от подделки и файл cookie. В ASP.NET Core имя токена подтверждения запроса отличается, но роль и содержимое точно такие же, как в классическом ASP.NET MVC. Другими словами, помощники тегов автоматически генерируют токен только за счет использования атрибутов помощника тега для определения URL-адреса действия формы.

Особенности атрибутов защиты от подделки токенов

Атрибут ValidateAntiForgeryToken не является единственным в ASP.NET Core. Он немного схож с атрибутом AutoValidateAntiForgeryToken, который выполняет ту же работу, за исключением того, что он охватывает все потенциально небезопасные типы HTTP запросов, а не только POST. Это также охватывает, фактически, PUT, DELETE и PATCH. Он не распространяется на другие типы запросов, которые должны использоваться только для чтения. Если вы регистрируете атрибут AutoValidateAntiForgeryToken в качестве глобального фильтра и используете атрибуты asp- во всех элементах FORM, то вы будете надежно защищены от возможных CSRF-атак без необходимости явного указания данного аттрибута для каждого действия.

services.AddMvc(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}); 

Если атрибут AutoValidateAntiForgeryToken зарегистрирован как глобальный фильтр, он все равно будет срабатывать каждый раз, когда элемент FORM, созданный без атрибутов *asp- , публикует свое содержимое. Возникающее исключение приводит к коду HTTP 400 Bad Request.

Это тип исключения, своего рода сигнал тревоги, который напоминает вам об использовании хелпера проверки токена в элементе FORM. Однако если у вас есть причины отключить проверку токена для определенного метода в небезопасном типе запроса HTTP, вы можете вместо этого использовать атрибут IgnoreValidateAntiforgeryToken.

Работа с заголовком Referrer

Заголовок HTTP Referrer указывает URL-адрес, который запросил обслуживаемый ресурс. Другими словами, он связывает абонента текущей страницы с предыдущей страницей. В контексте CSRF реферер будет содержать URL-адрес сайта, который фактически выполняет вызов. На первый взгляд может показаться, что легко отразить любые атаки CSRF, просто проверив содержимое HTTP-заголовка реферера. Фактически все, что нужно сделать, это проверить, что форма была размещена на том же сайте, если не на определенной странице. Однако ниже описывается почему защиты от CSRF одной лишь проверкой Referrer может быть недостаточно

[HttpPost]
public ActionResult Save(...)
{
   // Check referrer content here
   ...
}

Проблема с HTTP-заголовком реферера состоит в том, что он считается необязательной информацией и не гарантируется, что он будет передан всегда. Фактически, некоторые браузеры позволяют пользователям отключать рефереры, и иногда эта информация может быть удалена прокси-серверами. Кроме того, стандарт HTML5 ввел атрибут noreferrer для тегов привязки, который инструктирует браузер не устанавливать заголовок Referrer.

<a href="..." rel="noreferrer" />

Также следует обратить внимание что Referrer может быть подделан, особенно просто это сделать вне браузера с использованием стороннего HTTP-клиента.

Суть в том, что для защиты веб-сайта от атак CSRF недостаточно проверить HTTP-заголовок URL-адреса Referrer. Если вы ищете полную линию защиты, тогда ваш лучший вариант - использовать токен проверки, о чем было написано выше; Особенно в ASP.NET Core, где требуется меньше усилий для написания кода, чтобы использовать токены проверки.

Когда использовать проверку Referrer

HTTP-заголовок реферера, однако, также может быть весьма полезен в немного других обстоятельствах. Предположим, у вас есть набор конечных точек HTTP - да, давайте назовем его веб-API - который вы вызываете через Ajax/JavaScript со стороны клиента. Поскольку они являются внутренним ресурсом, вы иногда просто вызываете их через контроллеры ASP.NET при условии соблюдения правил аутентификации и авторизации. Другими словами, эти конечные точки будут вызваны, если будет найден допустимый файл cookie аутентификации.

Тем не менее, это может быть полезно при использовании с другими мерами усиления безопасности.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class RequireReferrerAttribute : ActionMethodSelectorAttribute
{
   public RequireReferrerAttribute(params string[] trustedServers)
   {
      TrustedServers = trustedServers;
   }

   public string[] TrustedServers { get; }

   public override bool IsValidForRequest(
              ControllerContext controllerContext, MethodInfo methodInfo)
   {
       var referrer = controllerContext.HttpContext.Request.UrlReferrer;
       if (referrer == null)
           return false;
       var list = new List<string>(TrustedServers);
       var uri = referrer.AbsoluteUri.ToLower();
       return list.Any(ts => uri.StartsWith(ts.ToLower()));
   } 
}

Если реферер не найден, метод, защищенный этим атрибутом, отклоняется. Вот как вы можете использовать атрибут.

[HttpPost]
[RequireReferrer("http://yourserver.com", "http://www.yourserver.com")]
public ActionResult Save( ... )
{
    ...
}

Метод Save будет вызываться только в том случае, если запрос поступает с любого из перечисленных серверов.

Немного об IP-адресах

К сожалению, проверка IP-адреса не является надежной, потому что они могут быть скрыты в случае серьезной атаки. Кроме того, может быть трудно найти точный IP-адрес и сопоставить его с авторизованным пользователем. При работе за прокси-сервером или маршрутизатором контекст HTTP сообщает только адрес маршрутизатора: не говоря уже о том, что легитимный пользователь может использовать сайт из множества разных мест. В любом случае, хотя проверка IP-адресов не может считаться общепринятой мерой защиты для веб-API, она по-прежнему остается опцией для некоторых конкретных ситуаций. Вот пример того, как получить IP-адрес.

public string GetIP(HttpContext context)
{
    var ip = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
    if (String.IsNullOrEmpty(ip))
     return context.Request.ServerVariables["REMOTE_ADDR"];
      return ip;
}