Zabránění 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, které útočníkovi umožní ukrást cookietokeny a tokeny relací, změnit obsah webové stránky prostřednictvím manipulace s nástrojem DOM nebo přesměrovat prohlížeč na jinou stránku. K ohrožením zabezpečení XSS obvykle dochází, když aplikace přijme uživatelský vstup a vypíše ji na stránku bez ověření, kódování nebo úniku.

Tento článek se týká především ASP.NET Core MVC se zobrazeními, Razor stránkami a dalšími aplikacemi, které vracejí HTML, které můžou být ohrožené XSS. Webová rozhraní API, která vracejí data ve formě HTML, XML nebo JSON, můžou v klientských aplikacích aktivovat útoky XSS, pokud správně neosvědčí uživatelský vstup v závislosti na tom, kolik důvěřuje klientské aplikaci v rozhraní API. Pokud například rozhraní API přijme uživatelem vygenerovaný obsah a vrátí ho v odpovědi HTML, útočník může do obsahu, který se spustí při vykreslení odpovědi v prohlížeči uživatele, vložit škodlivé skripty.

Aby se zabránilo útokům XSS, webová rozhraní API by měla implementovat vstupní ověřování a kódování výstupu. Ověření vstupu zajišťuje, že vstup uživatele splňuje očekávaná kritéria a neobsahuje škodlivý kód. Kódování výstupu zajišťuje, že všechna data vrácená rozhraním API jsou správně sanitována, aby se nedaly spustit jako kód v prohlížeči uživatele. Další informace najdete u tohoto problému na GitHubu.

Ochrana aplikace před XSS

XSS funguje na základní úrovni tím, že aplikaci oklame do <script> vykreslené stránky vložením značky nebo vložením On* události do prvku. Vývojáři by měli použít následující kroky prevence, aby se zabránilo zavedení XSS do svých aplikací:

  1. Nevkládejte do vstupu HTML nedůvěryhodná data, pokud nedodržíte zbývající kroky uvedené níže. Nedůvěryhodná data jsou všechna data, která můžou být řízena útočníkem, jako jsou vstupy formulářů HTML, řetězce dotazů, hlavičky HTTP nebo dokonce data ze databáze, protože útočník může být schopen prolomit vaši databázi, i když nemůže narušit vaši aplikaci.

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

  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žinou 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čítáte za běhu. Pokud to není možné, ujistěte se, že jsou data kódovaná v JavaScriptu. Kódování JavaScriptu přebírá nebezpečné znaky pro JavaScript a nahrazuje je jejich šestnáctkovým kódem, < například by byl kódován jako \u003C.

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

Kódování HTML pomocí Razor

Modul Razor použitý v MVC automaticky zakóduje všechny výstupy zdrojové z proměnných, pokud to opravdu těžko znemožníte. Používá pravidla kódování atributů HTML při každém použití direktivy @ . Vzhledem k tomu, že kódování atributů HTML je nadmnožinou kódování HTML, znamená to, že se nemusíte zabývat tím, jestli byste měli použít kódování HTML nebo kódování atributů HTML. Je nutné zajistit, abyste v kontextu HTML používali znak @, ne při pokusu o vložení nedůvěryhodného vstupu přímo do JavaScriptu. Pomocné rutiny značek také kódují vstup, který použijete v parametrech značek.

Podívejte se na toto Razor :

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

@untrustedInput

Toto zobrazení vypíše obsah nedůvěryhodné proměnnéInput . Tato proměnná obsahuje některé znaky, které se používají v útocích XSS, konkrétně <" a >. Při zkoumání zdroje se zobrazí vykreslený výstup kódovaný takto:

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

Upozorňující

ASP.NET Core MVC poskytuje HtmlString třídu, která není automaticky kódována při výstupu. Tato možnost by se nikdy neměla používat v kombinaci s nedůvěryhodným vstupem, protože tím dojde k ohrožení zabezpečení XSS.

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

Možná budete chtít do JavaScriptu vložit hodnotu, která se má zpracovat v zobrazení. Můžete to provést dvěma způsoby. Nejbezpečnější způsob, jak vložit hodnoty, je umístit hodnotu do datového atributu značky a načíst ji v JavaScriptu. Pří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>

Upozorňující

Nepoužívejte zřetězení nedůvěryhodného vstupu v JavaScriptu k vytváření prvků MODELU DOM nebo použití document.write() u dynamicky generovaného obsahu.

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

  • createElement() a přiřaďte hodnoty vlastností odpovídajícími metodami nebo vlastnostmi, například node.textContent= nebo node.InnerText=.
  • document.CreateTextNode() a připojte ho do příslušného umístění DOM.
  • element.SetAttribute()
  • element[attribute]=

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

Kodéry HTML, JavaScript a URL jsou pro váš kód k dispozici dvěma způsoby:

  • Vloží je prostřednictvím injektáže závislostí.
  • Použijte výchozí kodéry obsažené v System.Text.Encodings.Web oboru názvů.

Při použití výchozích kodérů se neprojeví veškeré vlastní nastavení použité u rozsahů znaků, které se budou považovat za bezpečné. Výchozí kodéry používají nejbezpečnější možná pravidla kódování.

Pokud chcete použít konfigurovatelné kodéry prostřednictvím DI konstruktorů, měli byste podle potřeby použí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 kódování

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

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

Po kódování kódované hodnoty proměnná obsahuje %22Quoted%20Value%20with%20spaces%20and%20%26%22. Mezery, uvozovky, interpunkce a další nebezpečné znaky jsou v procentech zakódovány do šestnáctkové hodnoty, například znak mezery bude %20.

Upozorňující

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

Přizpůsobení kodérů

Ve výchozím nastavení kodéry používají bezpečný seznam omezený na rozsah Basic Latin Unicode a kódují všechny znaky mimo tuto oblast jako ekvivalenty kódu jejich znaku. Toto chování má vliv také na Razor vykreslování TagHelper a HtmlHelper, protože k výstupu řetězců používá kodéry.

Důvodem je ochrana před neznámými nebo budoucími chybami prohlížeče (předchozí chyby prohlížeče byly analyzovány na základě zpracování neanglických znaků). Pokud váš web hodně využívá jiné znaky než latinky, jako jsou čínština, cyrilice nebo jiné, pravděpodobně není to chování, které chcete.

Seznamy bezpečných kodérů lze přizpůsobit tak, aby zahrnovaly rozsahy Unicode vhodné pro aplikaci při spuštění, v Program.cs:

Například použití výchozí konfigurace pomocí Razor HtmlHelper podobné následující:

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

Předchozí kód se vykreslí pomocí čínského textu zakódovaného:

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

Pokud chcete rozšířit znaky, které kodér považuje za bezpečné, vložte do Program.cssouboru .:

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

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 ConfigureServices().

Například pomocí výchozí konfigurace můžete použít Razor HtmlHelper, například takto;

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

Když zobrazíte zdroj webové stránky, uvidíte, že byla vykreslena následujícím způsobem s čínským textem zakódovaným;

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

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

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

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

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

Sejf rozsahy seznamů jsou určeny jako grafy kódu Unicode, nikoli 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 adresa URL musí být nakonfigurován samostatně.

Poznámka:

Přizpůsobení seznamu bezpečných kódů má vliv pouze na kodéry zdrojové prostřednictvím DI. Pokud k kodéru přistupujete přímo prostřednictvím System.Text.Encodings.Web.*Encoder.Default , použije se výchozí seznam bezpečných kódů jen pro základní latinku.

Kde by se mělo kódování provést?

Obecně přijímaný postup spočívá v tom, že kódování probíhá v okamžiku výstupu a kódovaných hodnot by nikdy nemělo být uloženo v databázi. Kódování v okamžiku 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, aniž byste museli kódovat hodnoty před hledáním a umožňuje využívat všechny změny nebo opravy chyb provedené v kodérech.

Ověřování jako technika prevence XSS

Ověření může být užitečný nástroj pro omezení útoků XSS. Například číselný řetězec obsahující pouze znaky 0–9 neaktivuje útok XSS. Ověření se při přijetí kódu HTML ve vstupu uživatele stává složitější. Analýza vstupu HTML je obtížná, pokud není nemožné. Markdown, ve spojení s analyzátorem, který odstraní vložený kód HTML, je bezpečnější možností pro příjem 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, co bylo provedeno ověřování nebo sanitizace.