Förhindra XSS (Cross-Site Scripting) i ASP.NET Core

Av Rick Anderson

XSS (Cross-Site Scripting) är en säkerhetsrisk som gör det möjligt för en cyberattacker att placera skript på klientsidan (vanligtvis JavaScript) på webbsidor. När andra användare läser in berörda sidor körs cyberattackerns skript. Cyberattackern kan sedan stjäla cookies och sessionstoken, ändra innehållet på webbsidan via DOM-manipulation eller omdirigera webbläsaren till en annan sida. XSS-sårbarheter uppstår vanligtvis när ett program tar användarindata och matar ut det till en sida utan att validera, koda eller undvika det.

Den här artikeln gäller främst för ASP.NET Core MVC med vyer, Razor Pages och andra appar som returnerar HTML som kan vara sårbara för XSS. Webb-API:er som returnerar data i form av HTML, XML eller JSON kan utlösa XSS-attacker i sina klientappar om de inte sanerar användarindata korrekt. Det här beteendet beror på hur mycket förtroende klientappen har i API:et. Om ett API accepterar användargenererat innehåll och returnerar det i ett HTML-svar är data öppna för angrepp. En cyberattacker kan mata in skadliga skript i innehållet som körs när svaret återges i användarens webbläsare.

För att förhindra XSS-attacker bör webb-API:er implementera indataverifiering och utdatakodning. Indataverifiering säkerställer att användarindata uppfyller förväntade kriterier och inte innehåller skadlig kod. Utdatakodning säkerställer att alla data som returneras av API:et är korrekt sanerade så att de inte kan köras som kod av användarens webbläsare. Mer information finns i GitHub dotnet/aspnetcore.docs issue #28789.

Skydda ditt program mot XSS

På en grundläggande nivå fungerar XSS genom att lura ditt program att infoga en <script> tagg på den renderade sidan eller genom att infoga en On* händelse i ett element.

För att undvika att introducera XSS i programmet bör utvecklare implementera följande förebyggande tekniker:

  • Placera aldrig ej betrodda data i DINA HTML-indata, såvida du inte följer de andra teknikerna som anges i det här avsnittet.

    Ej betrodda data är alla data som kan kontrolleras av en cyberattacker. Exempel är HTML-formulärindata, frågesträngar, HTTP-huvuden eller till och med data som kommer från en databas. En cyberattacker kanske kan bryta mot din databas även om de inte kan bryta mot ditt program.

  • Innan du placerar ej betrodda data i ett HTML-element kontrollerar du att data är HTML-kodade.

    HTML-kodning tar tecken som vänster vinkelparentes eller mindre än (<) och ändrar dem till ett säkert formulär som (&lt;).

  • Innan du placerar ej betrodda data i ett HTML-attribut kontrollerar du att data är HTML-attributkodade.

    Den här specialiserade formen av HTML-kodning hanterar dubbla citattecken ("), enkla citattecken ('), et-tecken (&) och mindre än (<) tecken. När du hanterar ej betrodda indata använder du HTML-kodning för allmänt HTML-innehåll och HTML-attributkodning för HTML-attribut.

  • Innan du lägger in otillförlitliga data i JavaScript, placera data i ett HTML-element vars innehåll du hämtar under körning.

    Om du inte kan följa den här tekniken, se till att data är JavaScript-kodade. JavaScript-kodning konverterar farliga tecken för JavaScript till ett hexadecimalt ekvivalent värde. JavaScript-kodning ändrar till exempel tecknet mindre än (<) till hexvärdet \u003C.

  • Innan du placerar ej betrodda data i en URL-frågesträng kontrollerar du att data är URL-kodade.

Utforska HTML-kodning med Razor

Motorn Razor som används i MVC kodar automatiskt alla utdata från variabler, såvida du inte arbetar för att förhindra detta beteende. Den använder KODningsregler för HTML-attribut när du använder at symbol-direktivet @ . Eftersom HTML-attributkodning är en superuppsättning HTML-kodning behöver du inte överväga om du vill använda HTML-kodning eller HTML-attributkodning. Du måste se till att du bara använder at-symbolen @ i en HTML-kontext och inte när du försöker infoga ej betrodda indata direkt i JavaScript. Razor Tag Helpers kodar även indata som du använder i taggparametrar.

Överväg följande Razor vy:

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

@untrustedInput

Den här vyn matar ut innehållet i variabeln untrustedInput . Variabeln innehåller vissa tecken som används i XSS-attacker: mindre än (<), dubbla citattecken (") och höger vinkelparentes eller större än (>). När du undersöker källan visas de renderade utdata som kodats som:

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

Varning

ASP.NET Core MVC tillhandahåller en HtmlString-klass som inte kodas automatiskt vid utdata. Den här klassen bör aldrig användas i kombination med ej betrodda indata eftersom den exponerar en XSS-säkerhetsrisk.

Utforska JavaScript-kodning med Razor

I vissa fall kanske du vill infoga ett värde i JavaScript för att bearbeta i vyn. Det finns två sätt att utföra den här uppgiften. Det säkraste sättet att infoga värden är att placera värdet i ett dataattribut för en tagg och hämta det i JavaScript. Till exempel:

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

Föregående markering genererar följande 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>

Föregående kod genererar följande utdata:

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

Varning

Sammanfoga inte ej betrodda indata i JavaScript för att skapa DOM-element eller använda document.write() på dynamiskt genererat innehåll.

Använd i stället någon av följande metoder för att förhindra att kod exponeras för DOM-baserad XSS:

  • Anropa createElement() och tilldela egenskapsvärden med lämpliga metoder eller egenskaper, till exempel node.textContent= eller node.InnerText=.
  • document.CreateTextNode() Anropa metoden och lägg till den på rätt DOM-plats.
  • Anropa element.SetAttribute() metoden.
  • Använd element[attribute]=-tilldelningen.

Få åtkomst till kodare i kod

Du kan använda HTML-, JavaScript- och URL-kodare i koden på två sätt:

  • Inför dem via beroendeinjektion.
  • Använd standardkodarna i System.Text.Encodings.Web-namnområdet.

När du använder standardkodarna börjar inte eventuella anpassningar som tillämpas på teckenintervall (så att de behandlas som säkra) att gälla. Standardkodarna använder de säkraste kodningsreglerna som är möjliga.

Om du vill använda de konfigurerbara kodarna via beroendeinmatningen bör konstruktorerna använda parametern HtmlEncoder, JavaScriptEncoderoch UrlEncoder , efter behov.

Till exempel:

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

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

Koda om URL-parametrar

Om du vill skapa en URL-frågesträng med ej betrodda indata som ett värde använder du parametern UrlEncoder för att koda värdet:

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

Efter kodning innehåller variabeln encodedValue strängen %22Quoted%20Value%20with%20spaces%20and%20%26%22. Blanksteg, citattecken, skiljetecken och andra osäkra tecken är procentkodade till deras hexadecimala värde. Till exempel konverteras ett blankstegstecken till %20.

Varning

Använd inte ej betrodda indata som en del av en URL-sökväg. Ange alltid ej betrodda indata som ett värde i frågesträngen.

Anpassa kodarna

Som standard använder kodare en säker lista som är begränsad till Basic Latin Unicode-intervallet. Alla tecken utanför det angivna intervallet kodas som deras teckenkodsekvivalenter. Det här beteendet påverkar även rendering av Razor Tag Helpers och HTML-hjälpare eftersom de använder kodare för att mata ut strängarna.

Syftet med det här beteendet är att skydda mot okända eller framtida webbläsarbuggar. Tidigare webbläsarbuggar utlöste parsning baserat på bearbetning av icke-engelska tecken. Om din webbplats använder icke-latinska tecken, till exempel kinesiska, kyrilliska eller andra, passar det här beteendet förmodligen inte för din konfiguration.

Du kan anpassa kodarens säkra listor så att de innehåller Unicode-intervall som är lämpliga för appen under starten. Gör anpassningarna i filen Program.cs .

Du kan till exempel använda standardkonfigurationen med en Razor HTML-hjälp som liknar följande HTML:

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

Föregående markering återges med kinesisk text kodad:

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

Om du vill bredda intervallet med tecken som behandlas som säkra av kodaren infogar du följande rad i filen Program.cs :

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

Du kan anpassa kodarens säkra listor så att de innehåller Unicode-intervall som är lämpliga för ditt program under starten, i ConfigureServices().

Om du till exempel använder standardkonfigurationen kan du använda en Razor HtmlHelper så här;

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

När du visar källan till webbsidan ser du att den har renderats på följande sätt, med den kinesiska texten kodad.

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

Om du vill bredda de tecken som behandlas som säkra av kodaren infogar du följande rad i metoden ConfigureServices() i startup.cs;

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

I det här exemplet breddas listan över säkra till att omfatta Unicode Range CJK Unified Ideographs. Följande utdata visar den renderade vyn för det bredare intervallet av säkra tecken:

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

Säkra listintervall anges som Unicode-koddiagram, inte språk. Unicode-standarden har en lista över koddiagram som du kan använda för att hitta diagrammet som innehåller dina tecken. Varje kodare (HTML, JavaScript, URL) måste konfigureras separat.

Anteckning

Anpassning av den säkra listan påverkar endast kodare som tillhandahålls via beroendeinjektion. Om du har direkt åtkomst till en kodare via System.Text.Encodings.Web.*Encoder.Defaultanvänds endast standardlistan för säkerhetsskydd, Basic Latin.

Fastställa när och var du ska koda

I allmänhet är den godkända metoden att kodning sker vid utdatapunkten och kodade värden aldrig ska lagras i en databas.

Med kodning vid utdatapunkten kan du ändra användningen av data. Du kan till exempel ändra från HTML till ett frågesträngsvärde. Med den här metoden kan du enkelt söka efter dina data utan att behöva koda värden innan du söker. Du kan också dra nytta av ändringar eller felkorrigeringar som görs för kodare.

Använda validering som en XSS-förebyggande teknik

Validering kan vara ett användbart verktyg för att begränsa XSS-attacker. Till exempel utlöser inte en numerisk sträng som bara innehåller tecknen 0–9 en XSS-attack.

Validering är mer komplicerat när HTML accepteras i användarindata. Det kan vara svårt och ibland omöjligt att parsa HTML-indata. Markdown, tillsammans med en parser som tar bort inbäddad HTML, är ett säkrare alternativ för att acceptera omfattande indata.

Förlita dig aldrig på validering ensam. Koda alltid ej betrodda indata före utdata, oavsett vilken validering eller sanering som utförs.