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

Von Rick Anderson

Cross-Site Scripting (XSS) ist eine Sicherheitslücke, die es einem Cyberangreifer ermöglicht, clientseitige Skripte (in der Regel JavaScript) in Webseiten zu platzieren. Wenn andere Benutzer betroffene Seiten laden, werden die Skripts des Cyberattackers ausgeführt. Der Cyberattacker kann dann Cookies und Sitzungstoken stehlen, den Inhalt der Webseite durch DOM-Manipulation ändern oder den Browser auf eine andere Seite umleiten. XSS-Sicherheitsrisiken treten in der Regel auf, wenn eine Anwendung Benutzereingaben einnimmt und sie an eine Seite ausgibt, ohne sie zu überprüfen, zu codieren oder zu entfernen.

Dieser Artikel bezieht sich in erster Linie auf ASP.NET Core MVC mit Ansichten, Razor Pages und anderen Apps, die HTML zurückgeben, die anfällig für XSS sein können. Web-APIs, die Daten in Form von HTML, XML oder JSON zurückgeben, können XSS-Angriffe in ihren Client-Apps auslösen, wenn sie benutzereingaben nicht ordnungsgemäß sanitieren. Dieses Verhalten hängt davon ab, wie viel Vertrauen die Client-App in der API platziert. Wenn eine API vom Benutzer generierten Inhalt akzeptiert und in einer HTML-Antwort zurückgibt, sind die Daten offen für Angriffe. Ein Cyberattacker kann schädliche Skripts in den Inhalt einfügen, der ausgeführt wird, wenn die Antwort im Browser des Benutzers gerendert wird.

Um XSS-Angriffe zu verhindern, sollten Web-APIs eine Eingabeüberprüfung und eine Codierung der Ausgabe implementieren. Die Eingabeüberprüfung stellt sicher, dass die Eingaben des Benutzers die erwarteten Kriterien erfüllen und keinen böswilligen Code enthalten. Die Ausgabecodierung stellt sicher, dass alle von der API zurückgegebenen Daten ordnungsgemäß sanitiert werden, sodass sie nicht vom Browser des Benutzers als Code ausgeführt werden können. Weitere Informationen finden Sie unter GitHub dotnet/aspnetcore.docs Issue #28789.

Schützen Sie Ihre Anwendung gegen XSS

Grundsätzlich funktioniert XSS, indem Ihre Anwendung dazu gebracht wird, ein <script>-Tag in Ihre gerenderte Seite oder ein On*-Ereignis in ein Element einzufügen.

Um die Einführung von XSS in die Anwendung zu vermeiden, sollten Entwickler die folgenden Präventionstechniken implementieren:

  • Fügen Sie niemals nicht vertrauenswürdige Daten in Ihre HTML-Eingabe ein, es sei denn, Sie folgen den anderen Techniken, die in diesem Abschnitt aufgeführt sind.

    Nicht vertrauenswürdige Daten sind daten, die von einem Cyberangriff kontrolliert werden können. Beispiele sind HTML-Formulareingaben, Abfragezeichenfolgen, HTTP-Header oder sogar Datenquellen aus einer Datenbank. Ein Cyberattacker kann ihre Datenbank möglicherweise verletzen, auch wenn sie Ihre Anwendung nicht verletzen können.

  • Bevor Sie nicht vertrauenswürdige Daten in ein HTML-Element einfügen, stellen Sie sicher, dass die Daten HTML-codiert sind.

    DIE HTML-Codierung akzeptiert Zeichen wie die linke eckige Klammer oder kleiner als (<) und ändert sie in eine sichere Form wie (&lt;).

  • Bevor Sie nicht vertrauenswürdige Daten in ein HTML-Attribut einfügen, stellen Sie sicher, dass die Daten HTML-Attribut codiert sind.

    Diese spezielle Form der HTML-Codierung kodiert doppelte Anführungszeichen ("), einfache Anführungszeichen ('), das Ampersand-Zeichen (&) und Kleiner-als-Zeichen (<). Verwenden Sie beim Umgang mit nicht vertrauenswürdigen Eingaben HTML-Codierung für allgemeine HTML-Inhalte und HTML-Attributcodierung für HTML-Attribute.

  • Bevor Sie nicht vertrauenswürdige Daten in JavaScript einfügen, platzieren Sie die Daten in einem HTML-Element, dessen Inhalt Sie zur Laufzeit abrufen.

    Wenn Sie diese Technik nicht befolgen können, stellen Sie sicher, dass die Daten javaScript-codiert sind. Die JavaScript-Codierung konvertiert gefährliche Zeichen für JavaScript in einen hexadezimalen Entsprechungswert. Die JavaScript-Codierung ändert z. B. das Zeichen kleiner als (<) in den Hexadexwert \u003C.

  • Bevor Sie nicht vertrauenswürdige Daten in eine URL-Abfragezeichenfolge einfügen, stellen Sie sicher, dass die Daten URL-codiert sind.

Erkunden der HTML-Codierung mit Razor

Das Razor in MVC verwendete Modul codiert automatisch alle ausgabequellen aus Variablen, es sei denn, Sie arbeiten daran, dieses Verhalten zu verhindern. Es verwendet HTML-Attributcodierungsregeln, wenn Sie die At-Symbol-Direktive @ verwenden. Da die HTML-Attributcodierung eine Obermenge der HTML-Codierung ist, müssen Sie nicht überlegen, ob Sie HTML-Codierung oder HTML-Attributcodierung verwenden. Sie müssen sicherstellen, dass Sie nur das At-Symbol @ in einem HTML-Kontext verwenden und nicht, wenn Sie versuchen, nicht vertrauenswürdige Eingaben direkt in JavaScript einzufügen. Razor Tag-Hilfsprogramme codieren auch Eingaben, die Sie in Tagparametern verwenden.

Betrachten Sie die folgende Razor Ansicht:

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

@untrustedInput

Diese Ansicht gibt den Inhalt der untrustedInput Variablen aus. Die Variable enthält einige Zeichen, die in XSS-Angriffen verwendet werden: kleiner als (<), doppeltes Anführungszeichen (") und rechtwinklige Klammer oder größer als (>). Die Untersuchung der Quelle zeigt, dass die gerenderte Ausgabe wie folgt codiert ist:

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

Warnung

ASP.NET Core MVC stellt eine HtmlString-Klasse bereit, die bei der Ausgabe nicht automatisch codiert wird. Diese Klasse sollte niemals in Kombination mit nicht vertrauenswürdigen Eingaben verwendet werden, da sie eine XSS-Sicherheitsanfälligkeit verfügbar macht.

Erkunden der JavaScript-Codierung mit Razor

In einigen Fällen möchten Sie möglicherweise einen Wert in JavaScript einfügen, der in Ihrer Ansicht verarbeitet werden soll. Es gibt zwei Möglichkeiten, diese Aufgabe auszuführen. Der sicherste Weg, Werte einzufügen, ist, den Wert in ein data-Attribut eines Tags einzufügen und es in Ihrem JavaScript-Code abzurufen. Zum Beispiel:

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

Das oben gezeigte Markup generiert den folgenden HTML-Code:

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

Der vorangehende Code generiert die folgende Ausgabe:

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

Warnung

Verketten Sie in JavaScript keine nicht vertrauenswürdigen Eingaben, um DOM-Elemente zu erzeugen, und verwenden Sie document.write() nicht für dynamisch generierte Inhalte.

Verwenden Sie stattdessen einen der folgenden Ansätze, um zu verhindern, dass Code für DOM-basierte XSS verfügbar gemacht wird:

  • Rufen Sie createElement() auf und weisen Sie Eigenschaftswerte mithilfe geeigneter Methoden oder Eigenschaften zu, wie node.textContent= oder node.InnerText=.
  • Rufen Sie die Methode document.CreateTextNode() auf und hängen Sie das Ergebnis an der entsprechenden DOM-Position an.
  • Rufen Sie die element.SetAttribute()-Methode auf.
  • Verwenden Sie die element[attribute]=-Zuweisung.

Auf Encoder im Code zugreifen

Sie können HTML-, JavaScript- und URL-Encoder in Ihrem Code auf zwei Arten verwenden:

  • Fügen Sie sie über die Abhängigkeitseinschleusung ein.
  • Verwenden Sie die im Namespace System.Text.Encodings.Web enthaltenen Standardencoder.

Wenn Sie die Standard-Encoder verwenden, werden alle Anpassungen, die auf Zeichenbereiche angewendet werden (sodass sie als sicher behandelt werden) nicht wirksam. Die Standardencoder verwenden die sichersten Codierungsregeln, die möglich sind.

Um die konfigurierbaren Encoder über Abhängigkeitsinjektion zu verwenden, sollten Ihre Konstruktoren gegebenenfalls einen HtmlEncoder-, JavaScriptEncoder- und UrlEncoder-Parameter annehmen.

Zum Beispiel:

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

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

Codieren von URL-Parametern

Wenn Sie eine URL-Abfragezeichenfolge mit nicht vertrauenswürdiger Eingabe als Wert erstellen möchten, verwenden Sie den UrlEncoder Parameter, um den Wert zu codieren:

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

Nach der Codierung enthält die encodedValue Variable die Zeichenfolge %22Quoted%20Value%20with%20spaces%20and%20%26%22. Leerzeichen, Anführungszeichen, Interpunktionszeichen und andere unsichere Zeichen werden mit ihrem Hexadezimalwert codiert. Zum Beispiel wird ein Leerzeichen in %20 umgewandelt.

Warnung

Verwenden Sie keine nicht vertrauenswürdigen Eingaben als Teil eines URL-Pfads. Übergeben Sie nicht vertrauenswürdige Eingaben immer als Wert einer Abfragezeichenfolge.

Encoder anpassen

Standardmäßig verwenden Encoder eine sichere Liste, die auf den Unicode-Bereich "Basic Latin" beschränkt ist. Alle Zeichen außerhalb des angegebenen Bereichs werden als ihre Zeichencodeentsprechungen codiert. Dieses Verhalten wirkt sich auch auf das Rendern durch Razor Tag-Hilfsprogramme und HTML-Hilfsprogramme aus, da sie die Encoder zum Ausgeben ihrer Zeichenfolgen verwenden.

Zweck dieses Verhaltens ist es, vor unbekannten oder zukünftigen Browserfehlern zu schützen. Frühere Browserfehler behinderten das Parsen bei der Verarbeitung nicht-englischer Zeichen. Wenn Ihre Website nicht lateinische Zeichen wie Chinesisch, Kyrillisch oder andere stark nutzt, eignet sich dieses Verhalten wahrscheinlich nicht für Ihre Konfiguration.

Sie können die encodersicheren Listen anpassen, um Unicode-Bereiche einzuschließen, die für die App beim Start geeignet sind. Nehmen Sie die Anpassungen in der datei Program.cs vor.

Sie können z. B. die Standardkonfiguration mit einem Razor HTML-Hilfsprogramm verwenden, das dem folgenden HTML-Code ähnelt:

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

Die vorangehende Markierung wird mit chinesischem Text codiert gerendert:

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

Um den vom Encoder als sicher behandelten Zeichenbereich zu erweitern, fügen Sie die folgende Zeile in die datei Program.cs ein:

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

Sie können die für Encoder sicheren Listen so anpassen, dass sie während des Starts Unicode-Bereiche enthalten, die für Ihre Anwendung geeignet sind, in ConfigureServices().

Mithilfe der Standardkonfiguration könnten Sie z. B. eine Razor HtmlHelper-Instanz wie folgt verwenden:

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

Wenn Sie sich den Quelltext der Webseite ansehen, sehen Sie, dass er wie folgt gerendert wurde, wobei der chinesische Text codiert wurde.

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

Um die vom Encoder als sicher behandelten Zeichen zu erweitern, fügen Sie die folgende Zeile in die ConfigureServices()-Methode in startup.cs ein.

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

In diesem Beispiel wird die sichere Liste erweitert, um die Unicode Range CJK Unified Ideographen einzuschließen. Die folgende Ausgabe zeigt die gerenderte Ansicht für den breiteren Bereich sicherer Zeichen:

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

Sichere Listenbereiche werden als Unicode-Zeichentabellen angegeben, nicht als Sprachen. Der Unicode-Standard enthält eine Liste von Codediagrammen , die Sie verwenden können, um das Diagramm zu finden, das Ihre Zeichen enthält. Jeder Encoder (HTML, JavaScript, URL) muss separat konfiguriert werden.

Hinweis

Die Anpassung der Safe-Liste betrifft nur Encoder, die per Abhängigkeitsinjektion bereitgestellt werden. Wenn Sie direkt über System.Text.Encodings.Web.*Encoder.Defaulteinen Encoder zugreifen, wird nur die standardsichere Liste verwendet, Basic Latin.

Bestimmen, wann und wo codiert werden soll

Im Allgemeinen ist es anerkannte Praxis, dass die Kodierung bei der Ausgabe erfolgt und kodierte Werte niemals in einer Datenbank gespeichert werden sollten.

Mit der Codierung am Ausgabepunkt können Sie die Verwendung von Daten ändern. Ändern Sie z. B. von HTML zu einem Abfragezeichenfolgenwert. Mit diesem Ansatz können Sie Ihre Daten ganz einfach durchsuchen, ohne vor der Suche Werte codieren zu müssen. Außerdem können Sie alle Änderungen oder Fehlerkorrekturen nutzen, die an Encodern vorgenommen wurden.

Verwenden der Validierung als XSS-Präventionsmethode

Die Überprüfung kann ein nützliches Tool darstellen, um XSS-Angriffe zu begrenzen. Beispielsweise löst eine numerische Zeichenfolge, die nur die Zeichen 0-9 enthält, keinen XSS-Angriff aus.

Die Überprüfung ist komplizierter, wenn HTML in der Benutzereingabe akzeptiert wird. Das Analysieren von HTML-Eingaben kann schwierig und manchmal unmöglich sein. Markdown in Verbindung mit einem Parser, der eingebettetes HTML entfernt, ist eine sicherere Option, um umfangreiche Eingaben zu akzeptieren.

Verlassen Sie sich niemals allein auf die Überprüfung. Codieren Sie immer nicht vertrauenswürdige Eingaben vor der Ausgabe, unabhängig davon, welche Überprüfung oder Bereinigung ausgeführt wird.