DOM-basierte Cross-Site-Scripting-Sicherheitslücken mit vertrauenswürdigen Typen verhindern

Krzysztof Kotowicz
Krzysztof Kotowicz

Unterstützte Browser

  • 83
  • 83
  • x
  • x

Quelle

DOM-basiertes Cross-Site-Scripting (DOM XSS) findet statt, wenn Daten aus einer nutzergesteuerten Quelle (z. B. ein Nutzername oder eine vom URL-Fragment entnommene Weiterleitungs-URL) eine Senke erreichen. Dabei handelt es sich um eine Funktion wie eval() oder einen Attribut-Setter wie .innerHTML, der beliebigen JavaScript-Code ausführen kann.

DOM XSS ist eine der häufigsten Sicherheitslücken im Web. Es kommt häufig vor, dass Entwicklungsteams diese Sicherheitslücke versehentlich in ihren Anwendungen einführen. Mit vertrauenswürdigen Typen können Sie gefährliche Web-API-Funktionen standardmäßig sicher machen, damit Sie Anwendungen schreiben, Sicherheitsprüfungen ausführen und Anwendungen frei von DOM XSS-Sicherheitslücken halten können. Vertrauenswürdige Typen sind als polyfill für Browser verfügbar, die sie noch nicht unterstützen.

Hintergrund

DOM XSS ist seit vielen Jahren eine der häufigsten und gefährlichsten Sicherheitslücken im Web.

Es gibt zwei Arten von Cross-Site-Scripting. Einige XSS-Sicherheitslücken werden durch serverseitigen Code verursacht, der den HTML-Code für die Website unsicher erstellt. Andere haben eine Ursache auf dem Client, weil der JavaScript-Code gefährliche Funktionen mit vom Nutzer kontrollierten Inhalten aufruft.

Um serverseitiges XSS zu verhindern, generieren Sie keinen HTML-Code durch das Verketten von Strings. Verwenden Sie stattdessen sichere kontextabhängige Vorlagenbibliotheken mit automatischem Escape-Code und eine nicht verwendete Content Security Policy, um Fehler zusätzlich zu beheben.

Browser können jetzt mithilfe von vertrauenswürdigen Typen auch clientseitiges DOM-basiertes XSS verhindern.

Einführung in das API

Bei vertrauenswürdigen Typen werden die folgenden riskanten Senkenfunktionen gesperrt. Einige davon kennen Sie vielleicht schon, da Browseranbieter und Web-Frameworks Sie aus Sicherheitsgründen bereits von der Verwendung dieser Funktionen abhalten.

Bei vertrauenswürdigen Typen müssen Sie die Daten verarbeiten, bevor sie an diese Senkenfunktionen übergeben werden. Die Verwendung nur eines Strings schlägt fehl, da der Browser nicht weiß, ob die Daten vertrauenswürdig sind:

Don'ts
anElement.innerHTML  = location.href;
Wenn vertrauenswürdige Typen aktiviert sind, gibt der Browser einen TypeError aus und verhindert die Verwendung einer DOM-XSS-Senke mit einem String.

Erstellen Sie ein spezielles Objekt, einen vertrauenswürdigen Typ, um anzugeben, dass die Daten sicher verarbeitet wurden.

Das sollten Sie tun:
anElement.innerHTML = aTrustedHTML;
  
Wenn vertrauenswürdige Typen aktiviert sind, akzeptiert der Browser ein TrustedHTML-Objekt für Senken, die HTML-Snippets erwarten. Es gibt auch die Objekte TrustedScript und TrustedScriptURL für andere sensible Senken.

Durch vertrauenswürdige Typen wird die DOM-XSS-Angriffsfläche Ihrer Anwendung erheblich reduziert. Sie vereinfacht Sicherheitsüberprüfungen und ermöglicht Ihnen, die typbasierten Sicherheitsprüfungen zu erzwingen, die beim Kompilieren, Linting oder Bündeln Ihres Codes zur Laufzeit im Browser durchgeführt werden.

Vertrauenswürdige Typen verwenden

Auf Meldungen von Verstößen gegen die Content Security Policy vorbereiten

Sie können einen Report Collector wie den Open-Source-Dienst reporting-api-processor oder go-csp-collector bereitstellen oder eine der kommerziellen Entsprechungen verwenden. Sie können mit ReportingObserver auch benutzerdefiniertes Logging hinzufügen und Verstöße im Browser beheben:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

oder durch Hinzufügen eines Event-Listeners:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

CSP-Header nur für Berichte hinzufügen

Fügen Sie den Dokumenten, die Sie zu vertrauenswürdigen Typen migrieren möchten, den folgenden HTTP-Antwortheader hinzu:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Jetzt werden alle Verstöße an //my-csp-endpoint.example gemeldet, aber die Website funktioniert weiterhin. Im nächsten Abschnitt wird die Funktionsweise von //my-csp-endpoint.example erläutert.

Verstöße gegen vertrauenswürdige Typen identifizieren

Ab jetzt sendet der Browser jedes Mal, wenn vertrauenswürdige Typen einen Verstoß erkennen, einen Bericht an eine konfigurierte report-uri. Wenn Ihre Anwendung beispielsweise einen String an innerHTML übergibt, sendet der Browser den folgenden Bericht:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Dies besagt, dass in https://my.url.example/script.js in Zeile 39 innerHTML mit dem String aufgerufen wurde, der mit <img src=x beginnt. Mithilfe dieser Informationen können Sie eingrenzen, welche Codeteile möglicherweise DOM XSS einführen und geändert werden müssen.

Verstöße beheben

Es gibt mehrere Möglichkeiten, einen Verstoß gegen den vertrauenswürdigen Typ zu beheben. Sie können den problematischen Code entfernen, eine Bibliothek verwenden, eine Richtlinie für vertrauenswürdige Typen erstellen oder als letzte Möglichkeit eine Standardrichtlinie erstellen.

Umformulieren des anstößigen Codes

Möglicherweise wird der nicht konforme Code nicht mehr benötigt oder kann ohne die Funktionen, die die Verstöße verursachen, neu geschrieben werden:

Das sollten Sie tun:
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Don'ts
el.innerHTML = '<img src=xyz.jpg>';

Bibliothek verwenden

Einige Bibliotheken generieren bereits vertrauenswürdige Typen, die Sie an die Senkenfunktionen übergeben können. Sie können beispielsweise DOMPurify verwenden, um ein HTML-Snippet zu bereinigen und XSS-Nutzlasten zu entfernen.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify unterstützt vertrauenswürdige Typen und gibt bereinigten HTML-Code zurück, der in ein TrustedHTML-Objekt eingebettet ist, sodass der Browser keinen Verstoß generiert.

Richtlinie für vertrauenswürdige Typen erstellen

Manchmal können Sie den Code, der den Verstoß verursacht, nicht entfernen und es gibt keine Bibliothek, um den Wert zu bereinigen und einen vertrauenswürdigen Typ für Sie zu erstellen. In diesen Fällen können Sie selbst ein vertrauenswürdiges Typobjekt erstellen.

Erstellen Sie zuerst eine Richtlinie. Richtlinien sind Factorys für vertrauenswürdige Typen, die bestimmte Sicherheitsregeln für die Eingabe erzwingen:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Mit diesem Code wird eine Richtlinie mit dem Namen myEscapePolicy erstellt, die TrustedHTML-Objekte mithilfe ihrer createHTML()-Funktion erstellen kann. Die definierten Regeln enthalten HTML-Escape-Zeichen (<), um die Erstellung neuer HTML-Elemente zu verhindern.

Verwenden Sie die Richtlinie wie folgt:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Standardrichtlinie verwenden

Manchmal können Sie den problematischen Code nicht ändern, z. B. wenn Sie eine Bibliothek eines Drittanbieters aus einem CDN laden. Verwenden Sie in diesem Fall eine Standardrichtlinie:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

Die Richtlinie mit dem Namen default wird immer dann verwendet, wenn ein String in einer Senke verwendet wird, die nur einen vertrauenswürdigen Typ akzeptiert.

Zur Durchsetzung der Content Security Policy wechseln

Wenn Ihre Anwendung keine Verstöße mehr verursacht, können Sie damit beginnen, vertrauenswürdige Typen zu erzwingen:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Unabhängig davon, wie komplex Ihre Webanwendung ist, kann eine DOM-XSS-Sicherheitslücke nur durch den Code in einer Ihrer Richtlinien entstehen. Sie können diesen noch weiter sperren, indem Sie die Richtlinienerstellung einschränken.

Weitere Informationen