Zapobieganie skryptom między witrynami (XSS) w ASP.NET Core

Autor: Rick Anderson

Skrypty między witrynami (XSS) to luka w zabezpieczeniach umożliwiająca osobie atakującej umieszczenie skryptów po stronie klienta (zazwyczaj JavaScript) na stronach internetowych. Gdy inni użytkownicy ładują strony, których dotyczy problem, skrypty osoby atakującej są uruchamiane, umożliwiając osobie atakującej kradzież cookietokenów i sesji, zmienianie zawartości strony internetowej za pomocą manipulacji DOM lub przekierowywanie przeglądarki do innej strony. Luki w zabezpieczeniach XSS zwykle występują, gdy aplikacja pobiera dane wejściowe użytkownika i wyprowadza je na stronę bez sprawdzania poprawności, kodowania lub ucieczki.

Ten artykuł dotyczy przede wszystkim ASP.NET Core MVC z widokami, Razor stronami i innymi aplikacjami, które zwracają kod HTML, który może być narażony na ataki XSS. Internetowe interfejsy API, które zwracają dane w postaci kodu HTML, XML lub JSON, mogą wyzwalać ataki XSS w aplikacjach klienckich, jeśli nie prawidłowo oczyszczają danych wejściowych użytkownika, w zależności od tego, ile zaufania aplikacja kliencka umieszcza w interfejsie API. Jeśli na przykład interfejs API akceptuje zawartość wygenerowaną przez użytkownika i zwraca ją w odpowiedzi HTML, osoba atakująca może wstrzyknąć złośliwe skrypty do zawartości wykonywanej po renderowaniu odpowiedzi w przeglądarce użytkownika.

Aby zapobiec atakom XSS, internetowe interfejsy API powinny implementować walidację danych wejściowych i kodowanie danych wyjściowych. Weryfikacja danych wejściowych gwarantuje, że dane wejściowe użytkownika spełniają oczekiwane kryteria i nie zawierają złośliwego kodu. Kodowanie danych wyjściowych gwarantuje, że wszystkie dane zwrócone przez interfejs API są prawidłowo oczyszczone, aby nie można było wykonać ich jako kodu w przeglądarce użytkownika. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub.

Ochrona aplikacji przed usługą XSS

Na poziomie podstawowym usługa XSS działa, łącząc aplikację <script> z wstawianiem tagu do renderowanej strony lub wstawiając On* zdarzenie do elementu. Deweloperzy powinni użyć następujących kroków zapobiegania, aby uniknąć wprowadzania XSS do swoich aplikacji:

  1. Nigdy nie umieszczaj niezaufanych danych do danych wejściowych HTML, chyba że wykonasz pozostałe poniższe kroki. Niezaufane dane to wszelkie dane, które mogą być kontrolowane przez osobę atakującą, takie jak dane wejściowe formularza HTML, ciągi zapytania, nagłówki HTTP, a nawet dane pochodzące z bazy danych, ponieważ osoba atakująca może mieć możliwość naruszenia bazy danych, nawet jeśli nie może naruszyć twojej aplikacji.

  2. Przed umieszczeniem niezaufanych danych wewnątrz elementu HTML upewnij się, że jest on zakodowany w formacie HTML. Kodowanie HTML pobiera znaki, takie jak < i zmienia je w bezpieczny formularz, taki jak <

  3. Przed umieszczeniem niezaufanych danych do atrybutu HTML upewnij się, że jest on zakodowany w formacie HTML. Kodowanie atrybutów HTML jest nadzbiorem kodowania HTML i koduje dodatkowe znaki, takie jak " i ".

  4. Przed umieszczeniem niezaufanych danych w języku JavaScript umieść dane w elemecie HTML, którego zawartość jest pobierana w czasie wykonywania. Jeśli nie jest to możliwe, upewnij się, że dane są kodowane w języku JavaScript. Kodowanie języka JavaScript przyjmuje niebezpieczne znaki dla języka JavaScript i zastępuje je na przykład < szesnastkowym kodem \u003C.

  5. Przed umieszczeniem niezaufanych danych w ciągu zapytania adresu URL upewnij się, że jest on zakodowany w adresie URL.

Kodowanie HTML przy użyciu polecenia Razor

Aparat Razor używany w mvC automatycznie koduje wszystkie dane wyjściowe pochodzące ze zmiennych, chyba że naprawdę trudno jest temu zapobiec. Używa reguł kodowania atrybutów HTML za każdym razem, gdy używasz @ dyrektywy. Ponieważ kodowanie atrybutów HTML jest nadzbiorem kodowania HTML, oznacza to, że nie musisz martwić się, czy należy używać kodowania HTML lub kodowania atrybutów HTML. Należy upewnić się, że używasz tylko znaku @ w kontekście HTML, a nie podczas próby wstawienia niezaufanych danych wejściowych bezpośrednio do języka JavaScript. Pomocnicy tagów będą również kodować dane wejściowe używane w parametrach tagu.

Zapoznaj się z następującym Razor widokiem:

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

@untrustedInput

Ten widok generuje zawartość niezaufanej zmiennejInput. Ta zmienna zawiera niektóre znaki, które są używane w atakach XSS, a mianowicie <, " i >. Badanie źródła pokazuje renderowane dane wyjściowe zakodowane jako:

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

Ostrzeżenie

ASP.NET Core MVC udostępnia klasę HtmlString , która nie jest automatycznie kodowana po danych wyjściowych. Nie należy jej używać w połączeniu z niezaufanymi danymi wejściowymi, ponieważ spowoduje to ujawnienie luki w zabezpieczeniach XSS.

Kodowanie Języka JavaScript przy użyciu polecenia Razor

Czasami warto wstawić wartość do języka JavaScript do przetworzenia w widoku. Istnieją dwa sposoby, aby to zrobić. Najbezpieczniejszym sposobem wstawiania wartości jest umieszczenie wartości w atrybucie danych tagu i pobranie go w języku JavaScript. Na przykład:

@{
    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>

Powyższy znacznik generuje następujący kod 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>

Powyższy kod generuje następujące dane wyjściowe:

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

Ostrzeżenie

Nie łączyj niezaufanych danych wejściowych w języku JavaScript, aby utworzyć elementy DOM lub używać document.write() ich na dynamicznie generowanej zawartości.

Użyj jednej z następujących metod, aby zapobiec uwidacznianiu kodu w modelu XSS opartym na modelu DOM:

  • createElement() i przypisz wartości właściwości z odpowiednimi metodami lub właściwościami, takimi jak node.textContent= lub node.InnerText=.
  • document.CreateTextNode() i dołącz go w odpowiedniej lokalizacji DOM.
  • element.SetAttribute()
  • element[attribute]=

Uzyskiwanie dostępu do koderów w kodzie

Kodery HTML, JavaScript i URL są dostępne dla kodu na dwa sposoby:

  • Wstrzykiwanie ich przez wstrzyknięcie zależności.
  • Użyj domyślnych koderów zawartych w System.Text.Encodings.Web przestrzeni nazw.

W przypadku korzystania z koderów domyślnych wszystkie dostosowania zastosowane do zakresów znaków, które mają być traktowane jako bezpieczne, nie zostaną zastosowane. Domyślne kodery używają najbezpieczniejszych reguł kodowania.

Aby użyć konfigurowalnych koderów za pośrednictwem di konstruktorów, należy odpowiednio użyć parametru HtmlEncoder, JavaScriptEncoder i UrlEncoder. Na przykład:

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

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

Parametry adresu URL kodowania

Jeśli chcesz utworzyć ciąg zapytania adresu URL z niezaufanymi danymi wejściowymi jako wartością, użyj elementu , UrlEncoder aby zakodować wartość. Przykład:

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

Po kodowaniu zakodowanej zmiennejValue zawiera wartość %22Quoted%20Value%20with%20spaces%20and%20%26%22. Spacje, cudzysłowy, znaki interpunkcyjne i inne niebezpieczne znaki są w procentach zakodowane w ich wartości szesnastkowej, na przykład znak spacji stanie się %20.

Ostrzeżenie

Nie używaj niezaufanych danych wejściowych w ramach ścieżki adresu URL. Zawsze przekazuj niezaufane dane wejściowe jako wartość ciągu zapytania.

Dostosowywanie koderów

Domyślnie kodery używają bezpiecznej listy ograniczonej do zakresu Basic Latin Unicode i kodują wszystkie znaki poza tym zakresem jako ich odpowiedniki kodu znaków. To zachowanie ma również wpływ na renderowanie Razor TagHelper i HtmlHelper, ponieważ używa koderów do wyprowadzania ciągów.

Przyczyną tego jest ochrona przed nieznanymi lub przyszłymi usterkami przeglądarki (poprzednie błędy przeglądarki potknęła się w oparciu o przetwarzanie znaków innych niż angielski). Jeśli witryna internetowa intensywnie używa znaków innych niż łacińskich, takich jak chiński, cyrylica lub inne, prawdopodobnie nie jest to zachowanie, które chcesz.

Listy bezpieczne kodera można dostosować tak, aby zawierały zakresy Unicode odpowiednie dla aplikacji podczas uruchamiania w programie Program.cs:

Na przykład użycie konfiguracji domyślnej przy użyciu elementu Razor HtmlHelper podobnego do następującego:

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

Powyższy znacznik jest renderowany za pomocą chińskiego tekstu zakodowanego:

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

Aby poszerzyć znaki traktowane jako bezpieczne przez koder, wstaw następujący wiersz do Program.cspliku .:

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

Listy bezpieczne kodera można dostosować, aby uwzględnić zakresy Unicode odpowiednie dla aplikacji podczas uruchamiania w programie ConfigureServices().

Na przykład przy użyciu konfiguracji domyślnej możesz użyć elementu Razor HtmlHelper w następujący sposób;

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

Po wyświetleniu źródła strony internetowej zobaczysz, że został on renderowany w następujący sposób, z zakodowanym tekstem chińskim;

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

Aby poszerzyć znaki traktowane jako bezpieczne przez koder, należy wstawić następujący wiersz do ConfigureServices() metody w startup.cspliku ;

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

Ten przykład rozszerza bezpieczną listę, aby uwzględnić zakres Unicode CjkUnifiedIdeographs. Renderowane dane wyjściowe staną się teraz

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

Sejf zakresy list są określane jako wykresy kodu Unicode, a nie języki. Standard Unicode zawiera listę wykresów kodowych , których można użyć do znalezienia wykresu zawierającego znaki. Każdy koder, Html, JavaScript i Url muszą być konfigurowane oddzielnie.

Uwaga

Dostosowanie listy bezpiecznych dotyczy tylko koderów źródłowych za pośrednictwem di. Jeśli uzyskujesz bezpośredni dostęp do kodera za pośrednictwem System.Text.Encodings.Web.*Encoder.Default domyślnego, będzie używana tylko lista bezpiecznych list w języku Łacińskim w warstwie Podstawowa.

Gdzie należy przeprowadzić kodowanie?

Ogólnie przyjętą praktyką jest to, że kodowanie odbywa się w punkcie danych wyjściowych i zakodowanych wartości nigdy nie powinno być przechowywane w bazie danych. Kodowanie w punkcie danych wyjściowych umożliwia zmianę użycia danych, na przykład z html na wartość ciągu zapytania. Umożliwia również łatwe przeszukiwanie danych bez konieczności kodowania wartości przed wyszukiwaniem i umożliwia korzystanie z wszelkich zmian lub poprawek błędów w koderach.

Walidacja jako technika zapobiegania XSS

Walidacja może być przydatnym narzędziem w ograniczaniu ataków XSS. Na przykład ciąg liczbowy zawierający tylko znaki 0–9 nie wyzwoli ataku XSS. Walidacja staje się bardziej skomplikowana podczas akceptowania kodu HTML w danych wejściowych użytkownika. Analizowanie danych wejściowych HTML jest trudne, jeśli nie jest niemożliwe. Język Markdown, w połączeniu z analizatorem, który usuwa osadzony kod HTML, jest bezpieczniejszą opcją akceptowania bogatych danych wejściowych. Nigdy nie polegaj tylko na walidacji. Zawsze koduj niezaufane dane wejściowe przed danymi wyjściowymi, niezależnie od tego, jaka weryfikacja lub oczyszczanie zostało wykonane.