Delen via


Cross-Site Scripting (XSS) voorkomen in ASP.NET Core

Door Rick Anderson

Cross-Site Scripting (XSS) is een beveiligingsprobleem waarmee een cyberaanval scripts (meestal JavaScript) op webpagina's kan plaatsen. Wanneer andere gebruikers betrokken pagina's laden, worden de scripts van de cyberaanvalker uitgevoerd, waardoor de cyberaanval cookies en sessietokens kan stelen, de inhoud van de webpagina wijzigt via DOM-manipulatie of de browser omleidt naar een andere pagina. XSS-kwetsbaarheden treden meestal op wanneer een applicatie gebruikersinvoer ontvangt en deze naar een pagina uitvoert zonder deze te valideren, te coderen of te escapen.

Dit artikel is voornamelijk van toepassing op ASP.NET Core MVC met weergaven, Razor pagina's en andere apps die HTML retourneren die mogelijk kwetsbaar zijn voor XSS. Web-API's die gegevens retourneren in de vorm van HTML, XML of JSON kunnen XSS-aanvallen in hun client-apps activeren als ze gebruikersinvoer niet goed opschonen, afhankelijk van hoeveel vertrouwen de client-app in de API plaatst. Als een API bijvoorbeeld door de gebruiker gegenereerde inhoud accepteert en retourneert in een HTML-antwoord, kan een cyberaanval schadelijke scripts injecteren in de inhoud die wordt uitgevoerd wanneer het antwoord wordt weergegeven in de browser van de gebruiker.

Om XSS-aanvallen te voorkomen, moeten web-API's invoervalidatie en uitvoercodering implementeren. Invoervalidatie zorgt ervoor dat gebruikersinvoer voldoet aan de verwachte criteria en geen schadelijke code bevat. Uitvoercodering zorgt ervoor dat alle gegevens die door de API worden geretourneerd, correct worden opgeschoond, zodat deze niet als code kunnen worden uitgevoerd door de browser van de gebruiker. Zie dit GitHub-probleem voor meer informatie.

Uw toepassing beveiligen tegen XSS

Op basisniveau werkt XSS door uw toepassing te misleiden om een <script> tag in te voegen op uw weergegeven pagina of door een On* gebeurtenis in een element in te voegen. Ontwikkelaars moeten de volgende preventiestappen gebruiken om te voorkomen dat XSS in hun toepassingen wordt ingevoerd:

  1. Plaats nooit niet-vertrouwde gegevens in uw HTML-invoer, tenzij u de rest van de onderstaande stappen volgt. Niet-vertrouwde gegevens zijn gegevens die kunnen worden beheerd door een cyberaanval, zoals HTML-formulierinvoer, queryreeksen, HTTP-headers of zelfs gegevensbronnen uit een database, omdat een cyberaanval mogelijk uw database kan schenden, zelfs als ze uw toepassing niet kunnen schenden.
  2. Voordat u niet-vertrouwde gegevens in een HTML-element plaatst, moet u ervoor zorgen dat het HTML-gecodeerd is. HTML-codering neemt tekens zoals `<` en wijzigt deze in een veilige vorm zoals `<`.
  3. Voordat u niet-vertrouwde gegevens in een HTML-kenmerk plaatst, moet u ervoor zorgen dat het HTML-kenmerk is gecodeerd. Deze speciale vorm van HTML-codering verwerkt dubbele aanhalingstekens ("), enkele aanhalingstekens ('), ampersands (&) en minder dan (<) tekens. Als u te maken hebt met niet-vertrouwde invoer, gebruikt u HTML-codering voor algemene HTML-inhoud en HTML-kenmerkcodering voor HTML-kenmerken.
  4. Voordat u niet-vertrouwde gegevens in JavaScript plaatst, plaatst u de gegevens in een HTML-element waarvan u de inhoud tijdens runtime ophaalt. Als dit niet mogelijk is, controleert u of de gegevens zijn gecodeerd met JavaScript. JavaScript-codering neemt gevaarlijke tekens voor JavaScript en vervangt ze door hun hex, bijvoorbeeld, < zou worden gecodeerd als \u003C.
  5. Voordat u niet-vertrouwde gegevens in een URL-querytekenreeks plaatst, moet u ervoor zorgen dat deze URL is gecodeerd.

HTML-codering met Razor

De Razor engine die in MVC wordt gebruikt, codeert automatisch alle uitvoer die afkomstig is van variabelen, tenzij u echt hard werkt om dit te voorkomen. Er worden html-kenmerkcoderingsregels gebruikt wanneer u de @ instructie gebruikt. Omdat HTML-kenmerkcodering een superset van HTML-codering is, betekent dit dat u zich niet hoeft bezig te houden met het feit of u HTML-codering of HTML-kenmerkcodering moet gebruiken. U moet ervoor zorgen dat u alleen @ gebruikt in een HTML-context, niet wanneer u niet-vertrouwde invoer rechtstreeks in JavaScript probeert in te voegen. Tag-helpers coderen ook invoer die u gebruikt in tagparameters.

Bekijk de volgende Razor weergave:

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

@untrustedInput

In deze weergave wordt de inhoud van de variabele untrustedInput uitgevoerd. Deze variabele bevat enkele tekens die worden gebruikt in XSS-aanvallen, namelijk <, " en >. Als u de bron bekijkt, ziet u de weergegeven uitvoer die is gecodeerd als:

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

Waarschuwing

ASP.NET Core MVC biedt een HtmlString klasse die niet automatisch wordt gecodeerd bij uitvoer. Dit mag nooit worden gebruikt in combinatie met niet-vertrouwde invoer, omdat hierdoor een XSS-beveiligingsprobleem wordt weergegeven.

JavaScript-codering met behulp van Razor

Het kan voorkomen dat u een waarde in JavaScript wilt invoegen om deze in uw weergave te verwerken. Er zijn twee manieren om dit te doen. De veiligste manier om waarden in te voegen, is door de waarde in een gegevenskenmerk van een tag te plaatsen en op te halen in uw JavaScript. Voorbeeld:

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

Met de voorgaande markering wordt de volgende HTML gegenereerd:

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

Met de voorgaande code wordt de volgende uitvoer gegenereerd:

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

Waarschuwing

Voeg geen niet-vertrouwde invoer toe in JavaScript om DOM-elementen te maken of te gebruiken document.write() voor dynamisch gegenereerde inhoud.

Gebruik een van de volgende methoden om te voorkomen dat code wordt blootgesteld aan OP DOM gebaseerde XSS:

  • createElement() en wijs eigenschapswaarden toe met de juiste methoden of eigenschappen, zoals node.textContent= of node.InnerText=.
  • document.CreateTextNode() en voeg deze toe op de juiste DOM-locatie.
  • element.SetAttribute()
  • element[attribute]=

Toegang tot coderingsprogramma's in code

De HTML-, JavaScript- en URL-coderingsprogramma's zijn op twee manieren beschikbaar voor uw code:

  • Injecteer ze via afhankelijkheidsinjectie.
  • Gebruik de standaardcoderingsprogramma's in de System.Text.Encodings.Web naamruimte.

Wanneer u de standaardcoderingsprogramma's gebruikt, worden alle aanpassingen die worden toegepast op tekenbereiken die worden behandeld als veilig, niet van kracht. De standaard coderingsprogramma's maken gebruik van de veiligste coderingsregels die mogelijk zijn.

Als u de configureerbare encoders via DI wilt gebruiken, moeten uw constructors een HtmlEncoder-, JavaScriptEncoder - en UrlEncoder-parameter gebruiken, indien van toepassing. Bijvoorbeeld;

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

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

URL-parameters coderen

Als u een URL-queryreeks met niet-vertrouwde invoer wilt maken als waarde, gebruikt u de UrlEncoder waarde om de waarde te coderen. Bijvoorbeeld

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

Na het coderen bevat encodedValue %22Quoted%20Value%20with%20spaces%20and%20%26%22. Spaties, aanhalingstekens, interpunctie en andere onveilige tekens worden procent gecodeerd naar de hexadecimale waarde, bijvoorbeeld een spatieteken wordt %20.

Waarschuwing

Gebruik geen niet-vertrouwde invoer als onderdeel van een URL-pad. Geef altijd niet-vertrouwde invoer door als een querystring-waarde.

De encoders aanpassen

Standaard gebruiken coderingsprogramma's een veilige lijst die is beperkt tot het Unicode-bereik Basic Latin en coderen alle tekens buiten dat bereik als hun tekencode-equivalenten. Dit gedrag is ook van invloed op Razor TagHelper- en HtmlHelper-rendering, omdat deze de encoders gebruikt om uw tekenreeksen uit te voeren.

De reden hiervoor is om bescherming te bieden tegen onbekende of toekomstige browserfouten (eerdere browserfouten hebben geleid tot problemen bij het verwerken van niet-Engelse tekens). Als uw website intensief gebruikmaakt van niet-Latijnse tekens, zoals Chinees, Cyrillisch of anderen, is dit waarschijnlijk niet het gewenste gedrag.

De veilige lijsten voor coderingsprogramma's kunnen worden aangepast om Unicode-bereiken op te nemen die geschikt zijn voor de app tijdens het opstarten, in Program.cs:

Als u bijvoorbeeld de standaardconfiguratie gebruikt met behulp van een Razor HtmlHelper, vergelijkbaar met de volgende:

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

De voorgaande markering wordt weergegeven met Chinese tekst gecodeerd:

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

Als u de tekens die door de encoder worden behandeld als veilig wilt vergroten, voegt u de volgende regel in Program.cs.:

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

U kunt de veilige lijsten met coderingsprogramma's aanpassen om Unicode-bereiken op te nemen die geschikt zijn voor uw toepassing tijdens het opstarten, in ConfigureServices().

Als u bijvoorbeeld de standaardconfiguratie gebruikt, kunt u een Razor HtmlHelper als volgt gebruiken;

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

Wanneer u de bron van de webpagina bekijkt, ziet u dat deze als volgt is weergegeven, met de Chinese tekst gecodeerd;

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

Als u de tekens die door de encoder worden behandeld als veilig wilt vergroten, voegt u de volgende regel in de ConfigureServices() methode in startup.cs;

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

In dit voorbeeld wordt de veilige lijst uitgebreid met de Unicode Range CjkUnifiedIdeographs. De gerenderde uitvoer zou nu beschikbaar worden.

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

Veilige lijstbereiken worden opgegeven als Unicode-codegrafieken, niet als talen. De Unicode-standaard bevat een lijst met codegrafieken die u kunt gebruiken om de grafiek met uw tekens te vinden. Elke encoder, Html, JavaScript en URL moet afzonderlijk worden geconfigureerd.

Opmerking

Aanpassing van de veilige lijst is alleen van invloed op encoders die via DI worden opgehaald. Als u rechtstreeks toegang hebt tot een encoder via System.Text.Encodings.Web.*Encoder.Default, wordt de standaard safelist, waarbij alleen Basic Latin is toegestaan, gebruikt.

Waar moet codering plaatsvinden?

De algemene geaccepteerde praktijk is dat codering plaatsvindt op het punt van uitvoer en gecodeerde waarden nooit mogen worden opgeslagen in een database. Met codering op het uitvoerpunt kunt u het gebruik van gegevens wijzigen, bijvoorbeeld van HTML naar een querytekenreekswaarde. Hiermee kunt u uw gegevens ook eenvoudig doorzoeken zonder dat u waarden hoeft te coderen voordat u zoekt en waarmee u kunt profiteren van wijzigingen of oplossingen voor fouten die zijn aangebracht in encoders.

Validatie als een XSS-preventietechniek

Validatie kan een handig hulpmiddel zijn bij het beperken van XSS-aanvallen. Een numerieke tekenreeks met alleen de tekens 0-9 activeert bijvoorbeeld geen XSS-aanval. Validatie wordt ingewikkelder bij het accepteren van HTML in gebruikersinvoer. Het parseren van HTML-invoer is moeilijk, als dat niet onmogelijk is. Markdown, in combinatie met een parser waarmee ingesloten HTML wordt gestript, is een veiligere optie voor het accepteren van uitgebreide invoer. Vertrouw nooit alleen op validatie. Codeer altijd niet-vertrouwde invoer vóór uitvoer, ongeacht welke validatie of opschoning is uitgevoerd.