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 cyberatakom umieszczanie skryptów po stronie klienta (zwykle JavaScript) na stronach internetowych. Gdy inni użytkownicy wczytują zaatakowane strony, uruchamiają się skrypty cyberatakującego. Cyberatak może następnie ukraść pliki cookie i tokeny sesji, zmienić zawartość strony internetowej za pomocą manipulacji DOM lub przekierować przeglądarkę 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 Pages i innymi aplikacjami, które zwracają kod HTML, który może być narażony na 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. To zachowanie zależy od tego, ile zaufania aplikacja kliencka umieszcza w interfejsie API. Jeśli interfejs API akceptuje zawartość wygenerowaną przez użytkownika i zwraca ją w odpowiedzi HTML, dane są otwarte do ataku. Cyberprzestępca może wstrzyknąć złośliwe skrypty do treści, które są wykonywane, gdy odpowiedź jest renderowana 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 wyjścia zapewnia, że wszelkie dane zwracane przez interfejs API są odpowiednio sanityzowane, dzięki czemu przeglądarka użytkownika nie może wykonać ich jako kodu. Aby uzyskać więcej informacji, zobacz GitHub dotnet/aspnetcore.docs issue #28789.

Ochrona aplikacji przed usługą XSS

Na podstawowym poziomie XSS działa poprzez oszukanie aplikacji, aby wstawiła tag <script> do renderowanej strony lub zdarzenie On* do elementu.

Aby uniknąć wprowadzenia XSS do aplikacji, deweloperzy powinni zaimplementować następujące techniki zapobiegania:

  • Nigdy nie umieszczaj niezaufanych danych do danych wejściowych HTML, chyba że zastosujesz inne techniki wymienione w tej sekcji.

    Niezaufane dane są wszelkie dane, które można kontrolować przez cyberatakę. Przykłady obejmują dane wejściowe formularza HTML, ciągi zapytania, nagłówki HTTP, a nawet dane pochodzące z bazy danych. Cyberataka może być w stanie naruszyć bazę danych, nawet jeśli nie może naruszyć twojej aplikacji.

  • Przed umieszczeniem niezaufanych danych do elementu HTML upewnij się, że dane są zakodowane w formacie HTML.

    Kodowanie HTML przekształca takie znaki jak lewy nawias ostrokątny lub mniejszy niż (<) w bezpieczną postać, taką jak (&lt;).

  • Przed umieszczeniem niezaufanych danych w atrybucie HTML upewnij się, że dane są kodowane za pomocą atrybutu HTML.

    Ta wyspecjalizowana forma kodowania HTML obsługuje znak podwójnego cudzysłowu ("), pojedynczego cudzysłowu ('), ampersand (&) oraz znak mniejszości (<). W przypadku niezaufanych danych wejściowych użyj kodowania HTML dla ogólnej zawartości HTML i kodowania atrybutów HTML dla atrybutów HTML.

  • Przed umieszczeniem niezaufanych danych w języku JavaScript umieść dane w elemmencie HTML, którego zawartość jest pobierana w czasie wykonywania.

    Jeśli nie możesz wykonać tej techniki, upewnij się, że dane są kodowane w języku JavaScript. Kodowanie w języku JavaScript zamienia znaki niebezpieczne w języku JavaScript na równoważną wartość szesnastkową. Na przykład kodowanie JavaScript zmienia znak „mniejszy niż” (<) na szesnastkową wartość \u003C.

  • Przed umieszczeniem niezaufanych danych w ciągu zapytania adresu URL upewnij się, że dane są zakodowane pod adresem URL.

Eksplorowanie kodowania HTML za pomocą polecenia Razor

Silnik Razor używany w MVC automatycznie koduje całe wyjście pochodzące ze zmiennych, chyba że celowo temu zapobiegniesz. Używa reguł kodowania atrybutów HTML za każdym razem, gdy używasz dyrektywy at symbol @ . Ponieważ kodowanie atrybutów HTML jest nadzbiorem kodowania HTML, nie musisz brać pod uwagę, czy używać kodowania HTML, czy kodowania atrybutów HTML. Należy upewnić się, że symbol at @ jest używany tylko w kontekście HTML, a nie podczas próby wstawienia niezaufanych danych wejściowych bezpośrednio do języka JavaScript. Razor Pomocnicy tagów również kodują dane wejściowe używane w parametrach tagu.

Rozważmy następujący Razor widok:

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

@untrustedInput

Ten widok generuje zawartość zmiennej untrustedInput . Zmienna zawiera kilka znaków używanych w atakach XSS: mniejsze niż (<), podwójny cudzysłów (") i nawias prawy lub większy niż (>). 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. Ta klasa nigdy nie powinna być używana w połączeniu z niezaufanymi danymi wejściowymi, ponieważ uwidacznia lukę w zabezpieczeniach XSS.

Eksplorowanie kodowania w języku JavaScript za pomocą polecenia Razor

W niektórych przypadkach możesz chcieć wstawić wartość do kodu JavaScript, aby przetworzyć ją w widoku. Istnieją dwa sposoby wykonania tego zadania. 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 instance uses 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 instance uses 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 łącz niezaufanych danych w języku JavaScript, aby tworzyć elementy DOM, ani nie używaj document.write() w przypadku dynamicznie generowanej zawartości.

Zamiast tego należy użyć jednej z następujących metod, aby zapobiec uwidocznieniu kodu w modelu XSS opartym na modelu DOM:

  • Wywołaj createElement() i przypisz wartości właściwości przy użyciu odpowiednich metod lub właściwości, takich jak node.textContent= lub node.InnerText=.
  • Wywołaj metodę document.CreateTextNode() i dołącz ją w odpowiedniej lokalizacji DOM.
  • Wywołaj metodę element.SetAttribute() .
  • Użyj przypisania element[attribute]=.

Dostęp do enkoderów w kodzie

Kodery HTML, JavaScript i URL można używać w kodzie na dwa sposoby:

  • Wstrzyknij je za pomocą wstrzykiwania zależności.
  • Użyj domyślnych koderów zawartych w System.Text.Encodings.Web przestrzeni nazw.

Gdy używasz domyślnych koderów, wszelkie dostosowania zastosowane do zakresów znaków (aby były traktowane jako bezpieczne) nie są uwzględniane. Domyślne kodery używają najbezpieczniejszych reguł kodowania.

Aby używać konfigurowalnych koderów za pośrednictwem wstrzykiwania zależności, konstruktory powinny odpowiednio przyjmować parametry 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;
    }
}

Kodowanie parametrów adresu URL

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

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

Po kodowaniu zmienna encodedValue zawiera ciąg %22Quoted%20Value%20with%20spaces%20and%20%26%22. Spacje, cudzysłowy, znaki interpunkcyjne i inne niedozwolone znaki są kodowane procentowo przy użyciu ich wartości szesnastkowych. Na przykład znak spacji jest konwertowany na %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. Wszystkie znaki spoza wskazanego zakresu są kodowane jako ich odpowiedniki kodu znaków. To zachowanie wpływa również na renderowanie przez pomocniki Razor Tag Helpers i HTML Helpers, ponieważ używają one koderów do wyprowadzania Twoich ciągów znaków.

Celem tego zachowania jest ochrona przed nieznanymi lub przyszłymi usterkami przeglądarki. Poprzednie błędy przeglądarki utrudniały parsowanie podczas przetwarzania znaków spoza języka angielskiego. Jeśli witryna internetowa korzysta z znaków innych niż łaciński, takich jak chiński, cyrylica lub inne, to zachowanie prawdopodobnie nie jest odpowiednie dla twojej konfiguracji.

Listy bezpieczne kodera można dostosować, aby uwzględnić zakresy Unicode odpowiednie dla aplikacji podczas uruchamiania. Wprowadź dostosowania w pliku Program.cs .

Na przykład możesz użyć domyślnej konfiguracji z pomocnikiem Razor HTML podobnym do następującego kodu HTML:

<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ć zakres znaków traktowanych jako bezpieczny przez koder, wstaw następujący wiersz do pliku Program.cs :

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

Możesz dostosować listy dozwolonych znaków kodera tak, aby podczas uruchamiania uwzględniały zakresy Unicode odpowiednie dla Twojej aplikacji, w elemencie 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 rozszerzyć zestaw znaków uznawanych przez koder za bezpieczne, należy wstawić następujący wiersz do metody ConfigureServices() w pliku startup.cs;

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

Ten przykład rozszerza bezpieczną listę o zakres Unicode ujednolicone ideogramy CJK. Następujące dane wyjściowe przedstawiają renderowany widok dla szerszego zakresu bezpiecznych znaków:

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

Zakresy bezpiecznych 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, URL) musi być skonfigurowany oddzielnie.

Uwaga

Dostosowanie listy dozwolonych wpływa tylko na kodery uzyskiwane za pośrednictwem iniekcji zależności. Jeśli uzyskujesz bezpośredni dostęp do kodera za pośrednictwem metody System.Text.Encodings.Web.*Encoder.Default, używana jest tylko domyślna bezpieczna lista, Podstawowa łacińska.

Określanie, kiedy i gdzie należy kodować

Ogólnie rzecz biorąc, akceptowaną praktyką jest to, że kodowanie odbywa się w punkcie danych wyjściowych i zakodowanych wartości nigdy nie powinny być przechowywane w bazie danych.

Kodowanie w punkcie danych wyjściowych umożliwia zmianę użycia danych. Na przykład zmień HTML na wartość ciągu zapytania. Takie podejście umożliwia łatwe przeszukiwanie danych bez konieczności kodowania wartości przed wyszukiwaniem. Umożliwia również korzystanie z wszelkich zmian lub poprawek błędów wprowadzonych w koderach.

Używanie walidacji jako techniki 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 wyzwala ataku XSS.

Walidacja jest bardziej skomplikowana, gdy kod HTML jest akceptowany w danych wejściowych użytkownika. Analizowanie danych wejściowych HTML może być trudne, a czasami 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 ich wyprowadzeniem, niezależnie od zastosowanej walidacji lub sanityzacji.