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

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 148.
  • Safari: 26.

Source

DOM-basiertes Cross-Site-Scripting (DOM XSS) tritt auf, wenn Daten aus einer vom Nutzer gesteuerten Quelle (z. B. ein Nutzername oder eine Weiterleitungs-URL aus dem URL Fragment) eine Senke erreichen. Eine Senke ist eine Funktion wie eval() oder ein Property Setter wie .innerHTML, mit dem beliebiger JavaScript-Code ausgeführt werden kann.

DOM XSS ist eine der häufigsten Sicherheitslücken im Web und es kommt häufig vor, dass Entwicklungsteams sie versehentlich in ihre Apps einführen. Vertrauenswürdige Typen geben Ihnen die Möglichkeit, Anwendungen zu schreiben, Sicherheitsüberprüfungen durchzuführen und sie frei von DOM XSS -Sicherheitslücken zu halten. Dazu werden gefährliche Web-API-Funktionen standardmäßig sicher gemacht. 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, aus dem die Website besteht, auf unsichere Weise erstellt. Andere haben eine Ursache auf dem Client, wo der JavaScript-Code gefährliche Funktionen mit vom Nutzer gesteuerten Inhalten aufruft.

Um serverseitiges XSS zu verhindern, sollten Sie HTML nicht durch Verketten von Strings generieren. Verwenden Sie stattdessen sichere kontextbezogene Templating-Bibliotheken mit automatischer Escaping-Funktion sowie eine nonce-basierte Content Security Policy um weitere Fehler zu vermeiden.

Browser können jetzt auch dazu beitragen, clientseitiges DOM-basiertes XSS zu verhindern, indem sie vertrauenswürdige Typen verwenden.

Einführung in die API

Vertrauenswürdige Typen sperren die folgenden riskanten Senkenfunktionen. Einige davon kennen Sie möglicherweise bereits, da Browseranbieter und Web-Frameworks aus Sicherheitsgründen bereits davon abraten, diese Funktionen zu verwenden.

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

Was Sie nicht tun sollten
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.

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

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 TrustedScript und TrustedScriptURL Objekte für andere sensible Senken.

Vertrauenswürdige Typen reduzieren die DOM XSS Angriffsfläche Ihrer Anwendung erheblich. Sie vereinfachen Sicherheitsüberprüfungen und ermöglichen es Ihnen, die typbasierten Sicherheitsprüfungen zu erzwingen, die beim Kompilieren, Linten oder Bündeln Ihres Codes zur Laufzeit im Browser durchgeführt werden.

Vertrauenswürdige Typen verwenden

Berichte zu Verstößen gegen die Content Security Policy vorbereiten

Sie können einen Berichtssammler wie den Open-Source-Berichts-API-Prozessor oder go-csp-collector bereitstellen oder eine der kommerziellen Alternativen verwenden. Sie können auch benutzerdefiniertes Logging hinzufügen und Verstöße im Browser mit einem ReportingObserver debuggen:

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 indem Sie einen Event-Listener hinzufügen:

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

CSP-Header nur für Berichte hinzufügen

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

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, die Website funktioniert aber weiterhin. Im nächsten Abschnitt wird erläutert, wie //my-csp-endpoint.example funktioniert.

Verstöße gegen vertrauenswürdige Typen erkennen

Wenn vertrauenswürdige Typen einen Verstoß erkennen, sendet der Browser 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"
}
}

Daraus geht hervor, dass in https://my.url.example/script.js in Zeile 39 innerHTML mit dem String aufgerufen wurde, der mit <img src=x beginnt. Mit diesen Informationen können Sie eingrenzen, welche Teile des Codes möglicherweise DOM XSS verursachen und geändert werden müssen.

Verstöße beheben

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

Fehlerhaften Code neu schreiben

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

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

Bibliothek verwenden

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

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

DOMPurify unterstützt vertrauenswürdige Typen und gibt bereinigtes HTML zurück, das in ein TrustedHTML Objekt eingebunden 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, mit der Sie den Wert bereinigen und einen vertrauenswürdigen Typ erstellen können. In diesen Fällen können Sie selbst ein Objekt für vertrauenswürdige Typen erstellen.

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

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

Dieser Code erstellt eine Richtlinie namens myEscapePolicy, mit der TrustedHTML-Objekte mithilfe der Funktion createHTML() erstellt werden können. Die definierten Regeln führen ein HTML-Escaping für <-Zeichen durch, um die Erstellung neuer HTML-Elemente zu verhindern.

Verwenden Sie die Richtlinie so:

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 fehlerhaften Code nicht ändern, z. B. wenn Sie eine Drittanbieterbibliothek 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 überall verwendet, wo ein String in einer Senke verwendet wird, die nur vertrauenswürdige Typen akzeptiert.

Content Security Policy erzwingen

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

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

Unabhängig davon, wie komplex Ihre Webanwendung ist, kann jetzt nur noch der Code in einer Ihrer Richtlinien eine DOM XSS-Sicherheitslücke verursachen. Sie können dies noch weiter einschränken, indem Sie die Erstellung von Richtlinien begrenzen.

Weitere Informationen