Prevence skriptování mezi weby (XSS) v ASP.NET Core

Autor: Rick Anderson

Skriptování mezi weby (XSS) je ohrožení zabezpečení, které útočníkovi umožňuje umístit skripty na straně klienta (obvykle JavaScript) na webové stránky. Když ostatní uživatelé načtou ovlivněné stránky, spustí se skripty útočníka, což útočníkovi umožní odcinout tokeny relace, změnit obsah webové stránky pomocí manipulace s objektem DOM nebo přesměrovat prohlížeč na cookie jinou stránku. Ohrožení zabezpečení XSS obvykle nastanou, když aplikace přebírá uživatelský vstup a vystupuje na stránku bez ověření, kódování nebo uchytění.

Ochrana aplikace před XSS

Na základní úrovni funguje XSS tak, že aplikaci zmáhá vložení značky na vykreslenou stránku <script> nebo On* vložením události do elementu. Vývojáři by měli použít následující preventivní kroky, aby se vyhnuli zavedení XSS do své aplikace.

  1. Do vstupu HTML nikdy nevedejte nedůvěryhodná data, pokud nepostupíte podle zbývajících kroků níže. Nedůvěryhodná data jsou jakákoli data, která může řídit útočník, vstupy ve formátu HTML, řetězce dotazů, hlavičky PROTOKOLU HTTP, dokonce i data zdrojová z databáze jako útočník, která by mohla být schopna prolomení vaší databáze, i když nemůže aplikaci prolomovat.

  2. Před vložením nedůvěryhodných dat do elementu HTML se ujistěte, že jsou kódovaná ve formátu HTML. Kódování HTML přebírá znaky, jako je , < a mění je do bezpečné podoby, jako je & lt;

  3. Před vložením nedůvěryhodných dat do atributu HTML se ujistěte, že jsou kódovaná ve formátu HTML. Kódování atributů HTML je nadmnožina kódování HTML a kóduje další znaky, například " a ".

  4. Před vložením nedůvěryhodných dat do JavaScriptu umístěte data do elementu HTML, jehož obsah načtete za běhu. Pokud to není možné, ujistěte se, že jsou data kódovaná v JavaScriptu. Kódování JavaScriptu přijímá nebezpečné znaky pro JavaScript a nahrazuje je šestnáctkovou znaky, například by < bylo kódováno jako \u003C .

  5. Před vložením nedůvěryhodných dat do řetězce dotazu adresy URL se ujistěte, že jsou zakódovaná adresa URL.

Kódování HTML s využitím Razor

Modul použitý v MVC automaticky kóduje veškerý výstup, který pochází z proměnných, pokud ve skutečnosti nepracujete, abyste Razor tomu zabránili. Při každém použití direktivy používá pravidla kódování atributů @ HTML. Protože kódování atributů HTML je nadmnožina kódování HTML, znamená to, že se nemusíte zajímat, jestli byste měli použít kódování html nebo atribut HTML. Při pokusu o vložení nedůvěryhodného vstupu přímo do JavaScriptu musíte zajistit, abyste v kontextu HTML nepouložili jen znak @. Pomocná zařízení značek také kódují vstup, který použijete v parametrech značek.

Podívejte se na následující Razor zobrazení:

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

@untrustedInput

Výstupem tohoto zobrazení je obsah proměnné untrustedInput. Tato proměnná obsahuje některé znaky, které se používají při útocích XSS, konkrétně , " a < > . Zkoumání zdroje ukazuje vykreslený výstup kódovaný takto:

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

Upozornění

ASP.NET Core MVC poskytuje třídu, která se při výstupu automaticky HtmlString nezakóduje. Nikdy by se neměl používat v kombinaci s nedůvěryhodným vstupem, protože to odhalí ohrožení zabezpečení XSS.

JavaScript Encoding using Razor

Může se zobrazit, že budete chtít vložit hodnotu do JavaScriptu, aby se v zobrazení zpracuje. Můžete to provést dvěma způsoby. Nejbezpečnější způsob vložení hodnot je umístit hodnotu do datového atributu značky a načíst ji v JavaScriptu. Například:

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

Předchozí kód vygeneruje následující kód 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>

Předchozí kód vygeneruje následující výstup:

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

Upozornění

NEŘEŠTE zřetězit nedůvěryhodný vstup v JavaScriptu pro vytvoření elementů modelu DOM nebo použití document.write() u dynamicky generovaného obsahu.

Pokud chcete zabránit vystavení kódu do XSS založeného na modelu DOM, použijte jeden z následujících přístupů:

  • createElement() a přiřaďte hodnoty vlastností příslušnými metodami nebo vlastnostmi, jako node.textContent= je nebo node.InnerText= .
  • document.CreateTextNode() a připojte ho v příslušném umístění modelu DOM.
  • element.SetAttribute()
  • element[attribute]=

Přístup ke kodérům v kódu

Kodéry HTML, JavaScript a URL jsou pro váš kód k dispozici dvěma způsoby. Můžete je vložit prostřednictvím injektáže závislostí nebo můžete použít výchozí kodéry obsažené v oboru System.Text.Encodings.Web názvů . Pokud použijete výchozí kodéry, žádný z použitých rozsahů znaků, který se bude považovat za bezpečný, se nebude uplatňovat – výchozí kodéry používají nejbezpečnější možná pravidla kódování.

Pokud chcete používat konfigurovatelné kodéry prostřednictvím IN, konstruktory by měly podle potřeby vzít parametr HtmlEncoder, JavaScriptEncoder a UrlEncoder. Například:

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 adresy URL pro kódování

Pokud chcete vytvořit řetězec dotazu adresy URL s nedůvěryhodným vstupem jako hodnotou, použijte ke UrlEncoder kódování hodnoty . Třeba

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

Po kódování bude proměnná encodedValue obsahovat %22Quoted%20Value%20with%20spaces%20and%20%26%22 . Mezery, uvozovky, interpunkce a další nebezpečné znaky budou procentuálně zakódované na šestnáctkové hodnoty, například znak mezery se stane %20.

Upozornění

Jako součást cesty URL nepoužívejte nedůvěryhodný vstup. Vždy předejte nedůvěryhodný vstup jako hodnotu řetězce dotazu.

Přizpůsobení kodérů

Ve výchozím nastavení používají kodéry bezpečný seznam omezený na rozsah Základní latinky Unicode a kódují všechny znaky mimo tento rozsah jako jejich ekvivalenty kódu znaků. Toto chování také ovlivňuje vykreslování TagHelper a HtmlHelper, protože bude používat kodéry Razor pro výstup řetězců.

Důvodem je ochrana před neznámými nebo budoucími chybami prohlížeče (předchozí chyby prohlížeče se na základě zpracování jiných než anglických znaků přetápěly o parsování). Pokud váš web velmi často používá jiné znaky než latinky, jako je čínština, cyrilice nebo jiné, pravděpodobně toto chování nechcete.

Seznamy bezpečných kodérů můžete přizpůsobit tak, aby zahrnovaly rozsahy Unicode vhodné pro vaši aplikaci během spouštění, v nástroji ConfigureServices() .

Například při použití výchozí konfigurace můžete použít Razor HtmlHelper, jako je tento.

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

Když zobrazíte zdroj webové stránky, uvidíte, že se vykresloval následujícím způsobem s kódem čínštiny.

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

Pokud chcete rozšířit znaky, které kodér považoval za bezpečné, vložte do metody ConfigureServices() v souboru následující startup.cs řádek.

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

Tento příklad rozšiřuje seznam bezpečných znaků tak, aby zahrnoval rozsah Znaků Unicode CjkUnifiedIdeographs. Vykreslený výstup by se teď stal

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

Sejf seznamu jsou určeny jako grafy kódu Unicode, nikoli jako jazyky. Standard Unicode obsahuje seznam grafů kódu, které můžete použít k vyhledání grafu obsahujícího vaše znaky. Každý kodér Html, JavaScript a Url musí být nakonfigurovaný samostatně.

Poznámka

Přizpůsobení seznamu bezpečných položek má vliv pouze na kodéry, které jsou zdrojem prostřednictvím induimentální funkce. Pokud k kodéru přistupujte přímo přes , použije se výchozí seznam bezpečných hodnot pouze pro základní System.Text.Encodings.Web.*Encoder.Default latinku.

Kde se má kódování odehrát?

Obecně se akceptuje, že kódování probíhá v místě výstupu a kódované hodnoty by nikdy neměly být uložené v databázi. Kódování v místě výstupu umožňuje změnit použití dat, například z HTML na hodnotu řetězce dotazu. Umožňuje také snadno prohledávat data bez nutnosti kódovat hodnoty před vyhledáváním a umožňuje využít výhod všech změn nebo oprav chyb provedených v kodérech.

Ověřování jako technika prevence XSS

Ověřování může být užitečným nástrojem pro omezení útoků XSS. Například číselný řetězec obsahující jenom znaky 0–9 nespouštěl útok XSS. Při přijetí kódu HTML v uživatelském vstupu se ověřování zkomplikuje. Analýza vstupu HTML je obtížná, pokud ne nemožná. Markdown, který je spolu s analyzátorem, který prokládá vložený kód HTML, je bezpečnější možností pro přijetí bohatého vstupu. Nikdy nespoléhejte na samotné ověřování. Před výstupem vždy kódujte nedůvěryhodný vstup bez ohledu na to, jaké ověření nebo sanitizaci bylo provedeno.