Поделиться через


Предотвращение межсайтовых сценариев (XSS) в ASP.NET Core

Автор: Рик Андерсон (Rick Anderson)

Межсайтовый скрипт (XSS) — это уязвимость безопасности, которая позволяет кибератаке размещать клиентские скрипты (обычно JavaScript) на веб-страницах. Когда другие пользователи загружают затронутые страницы, скрипты кибератаки запускаются, позволяя кибератаке украсть файлы cookie и маркеры сеанса, изменить содержимое веб-страницы с помощью манипуляции DOM или перенаправить браузер на другую страницу. Уязвимости XSS обычно возникают, когда приложение принимает входные данные пользователя и выводит его на страницу без проверки, кодирования или экранирования.

Эта статья относится в основном к ASP.NET Core MVC с представлениями, Razor Pages и другими приложениями, которые возвращают HTML, которые могут быть уязвимы для XSS. Веб-API, возвращающие данные в виде HTML, XML или JSON, могут активировать атаки XSS в клиентских приложениях, если они не правильно очищают входные данные пользователей в зависимости от того, сколько доверия клиентскому приложению помещает в API. Например, если API принимает содержимое, созданное пользователем, и возвращает его в HTML-ответе, кибератака может внедрить вредоносные скрипты в содержимое, которое выполняется при отрисовке ответа в браузере пользователя.

Чтобы предотвратить атаки XSS, веб-API должны реализовать кодирование входных и выходных данных. Проверка входных данных гарантирует, что входные данные пользователя соответствуют ожидаемым критериям и не включают вредоносный код. Кодирование выходных данных гарантирует правильность очистки любых данных, возвращаемых API, чтобы его нельзя было выполнять в виде кода браузером пользователя. Дополнительные сведения см. здесь на GitHub.

Защита приложения от XSS

На базовом уровне XSS работает, обманув <script> приложение в вставке тега на отрисованную страницу или вставив On* событие в элемент. Разработчики должны использовать следующие действия по предотвращению, чтобы избежать внедрения XSS в свои приложения:

  1. Никогда не помещайте ненадежные данные в входные данные HTML, если вы не выполните rest указанные ниже действия. Ненадежные данные — это любые данные, которые могут контролироваться кибератакой, например входными данными html-формы, строками запросов, заголовками HTTP или даже источниками данных из базы данных, так как кибератака может иметь возможность в breach базе данных даже в том случае, если они не breach могут приложения.

  2. Прежде чем помещает ненадежные данные в элемент HTML, убедитесь, что он закодирован в ФОРМАТЕ HTML. Кодировка HTML принимает такие символы, как < и изменяет их в безопасную форму, например <

  3. Прежде чем помещает ненадежные данные в атрибут HTML, убедитесь, что он закодирован в ФОРМАТЕ HTML. Кодировка атрибутов HTML — это супермножество кодировки HTML и кодирует дополнительные символы, такие как "и".

  4. Перед размещением ненадежных данных в JavaScript поместите данные в HTML-элемент, содержимое которого извлекается во время выполнения. Если это невозможно, убедитесь, что данные закодированы в JavaScript. Кодировка JavaScript принимает опасные символы для JavaScript и заменяет их шестнадцатеричными символами, например, < кодируется как \u003C.

  5. Прежде чем поместить ненадежные данные в строку запроса URL-адреса, убедитесь, что он закодирован.

Кодировка HTML с помощью Razor

Модуль Razor , используемый в MVC, автоматически кодирует все выходные данные, полученные из переменных, если вы не работаете очень трудно, чтобы предотвратить это. Он использует правила кодирования атрибутов HTML всякий раз, когда используется директива @ . Так как кодировка атрибутов HTML является супермножеством кодировки HTML, это означает, что вам не нужно беспокоиться о том, следует ли использовать кодировку HTML или кодировку атрибутов HTML. Необходимо убедиться, что вы используете только @в контексте HTML, а не при попытке вставки ненадежных входных данных непосредственно в JavaScript. Вспомогательные функции тегов также кодируют входные данные, используемые в параметрах тегов.

Просмотрите следующее Razor представление:

@{
    var untrustedInput = "<\"123\">";
}

@untrustedInput

Это представление выводит содержимое ненадежной переменнойInput . Эта переменная содержит некоторые символы, которые используются в атаках XSS, а именно <" и >. При проверке источника отображаются отрисованные выходные данные, закодированные следующим образом:

&lt;&quot;123&quot;&gt;

Предупреждение

ASP.NET Core MVC предоставляет HtmlString класс, который не кодируется автоматически при выходных данных. Это никогда не следует использовать в сочетании с ненадежными входными данными, так как это приведет к уязвимости XSS.

Кодировка JavaScript с помощью Razor

Иногда может потребоваться вставить значение в JavaScript для обработки в представлении. Это можно сделать двумя способами. Самый безопасный способ вставки значений — поместить значение в атрибут данных тега и получить его в JavaScript. Например:

@{
    var untrustedInput = "<script>alert(1)</script>";
}

<div id="injectedData"
     data-untrustedinput="@untrustedInput" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it
    // can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or you can use createElement() to dynamically create document elements
    // This time we're using textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

Предыдущая разметка создает следующий HTML-код:

<div id="injectedData"
     data-untrustedinput="&lt;script&gt;alert(1)&lt;/script&gt;" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can
    // lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or you can use createElement() to dynamically create document elements
    // This time we're using textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

Приведенный выше код создает следующие выходные данные:

<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>

Предупреждение

Не сцепляйте ненадежные входные данные в JavaScript для создания элементов DOM или использования document.write() в динамически созданном содержимом.

Используйте один из следующих подходов, чтобы предотвратить доступ кода к XSS на основе DOM:

  • createElement() и присваивайте значения свойств соответствующим методам или свойствам, таким как node.textContent= или node.InnerText=.
  • document.CreateTextNode() и добавьте его в соответствующее расположение DOM.
  • element.SetAttribute()
  • element[attribute]=

Доступ к кодировщикам в коде

Кодировщики HTML, JavaScript и URL-адреса доступны для кода двумя способами:

  • Внедрение их с помощью внедрения зависимостей.
  • Используйте кодировщики по умолчанию, содержащиеся в System.Text.Encodings.Web пространстве имен.

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

Чтобы использовать настраиваемые кодировщики с помощью di конструкторов, следует принять параметр HtmlEncoder, JavaScriptEncoder и UrlEncoder соответствующим образом. Например;

public class HomeController : Controller
{
    HtmlEncoder _htmlEncoder;
    JavaScriptEncoder _javaScriptEncoder;
    UrlEncoder _urlEncoder;

    public HomeController(HtmlEncoder htmlEncoder,
                          JavaScriptEncoder javascriptEncoder,
                          UrlEncoder urlEncoder)
    {
        _htmlEncoder = htmlEncoder;
        _javaScriptEncoder = javascriptEncoder;
        _urlEncoder = urlEncoder;
    }
}

Параметры URL-адреса кодирования

Если вы хотите создать строку запроса URL-адреса с ненадежными входными данными в качестве значения, используйте UrlEncoder кодирование значения. Например,

var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);

После кодирования переменной закодированного значения содержится %22Quoted%20Value%20with%20spaces%20and%20%26%22. Пробелы, кавычки, знаки препинания и другие небезопасные символы кодируются в шестнадцатеричное значение, например символ пробела станет %20.

Предупреждение

Не используйте ненадежные входные данные в рамках пути URL-адреса. Всегда передавать ненадежные входные данные в виде значения строки запроса.

Настройка кодировщиков

По умолчанию кодировщики используют безопасный список, ограниченный диапазоном Basic Latin Юникод, и кодируют все символы за пределами этого диапазона в качестве эквивалентов кода символов. Это поведение также влияет на Razor отрисовку TagHelper и HtmlHelper, так как кодировщики используются для вывода строк.

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

Безопасные списки кодировщика можно настроить для включения диапазонов Юникода, соответствующих приложению во время запуска, в Program.cs:

Например, используя конфигурацию по умолчанию с помощью Razor HtmlHelper, аналогичной следующему:

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Предыдущая разметка отображается в кодировке китайского текста:

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Чтобы расширить символы, которые считаются безопасными кодировщиком, вставьте следующую строку в Program.cs.:

builder.Services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

Вы можете настроить безопасные списки кодировщика, чтобы включить диапазоны Юникода, соответствующие приложению во время запуска, в ConfigureServices().

Например, используя конфигурацию по умолчанию, можно использовать Razor HtmlHelper, как показано ниже.

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

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

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Чтобы расширить символы, которые обрабатываются как безопасные кодировщиком, вставить следующую строку в ConfigureServices() метод в startup.cs;

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

В этом примере расширяется безопасный список, включаемый диапазон Юникода CjkUnifiedIdeographs. Отрисованные выходные данные теперь станут

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

Диапазоны безопасных списков указываются как кодовые диаграммы Юникода, а не языки. Стандарт Юникода содержит список кодовых диаграмм, которые можно использовать для поиска диаграммы, содержащей символы. Каждый кодировщик, Html, JavaScript и URL-адрес должны быть настроены отдельно.

Примечание.

Настройка безопасного списка влияет только на кодировщиков, исходных с помощью DI. Если вы напрямую обращаетесь к кодировщику по System.Text.Encodings.Web.*Encoder.Default умолчанию, будет использоваться только безопасный список базовых латиниц.

Где должно происходить кодировка?

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

Проверка как метод предотвращения XSS

Проверка может быть полезным средством ограничения атак XSS. Например, числовая строка, содержащая только символы 0-9, не активирует атаку XSS. Проверка становится более сложной при приеме HTML-кода в пользовательском вводе. Анализ входных данных HTML является трудным, если это невозможно. Markdown, в сочетании с анализатором, который полосит внедренный HTML, является более безопасным вариантом принятия расширенных входных данных. Никогда не полагаться только на проверку. Всегда кодируйте ненадежные входные данные перед выходными данными независимо от того, какая проверка или очистка была выполнена.