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

Krzysztof Kotowicz
Krzysztof Kotowicz

Unterstützte Browser

  • Chrome: 83.
  • Edge: 83.
  • Firefox: Nicht unterstützt.
  • Safari: Nicht unterstützt.

Quelle

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) einen Senke erreichen. Dies ist eine Funktion wie eval() oder ein Eigenschafts-Setter wie .innerHTML, mit dem beliebiger JavaScript-Code ausgeführt werden kann.

DOM-XSS ist eine der häufigsten Sicherheitslücken in der Websicherheit. Entwicklerteams führen sie häufig versehentlich in ihren Apps ein. Mit Trusted Types haben Sie die Möglichkeit, Anwendungen zu schreiben, zu prüfen und frei von DOM-XSS-Sicherheitslücken zu halten, indem gefährliche Web-API-Funktionen standardmäßig sicher gemacht werden. Vertraute 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 websiteübergreifendem Scripting. Einige XSS-Sicherheitslücken werden durch serverseitigen Code verursacht, der den HTML-Code für die Website auf unsichere Weise erstellt. Andere haben ihre Ursache auf dem Client, wo der JavaScript-Code gefährliche Funktionen mit von Nutzern gesteuerten Inhalten aufruft.

Um serverseitiges XSS zu verhindern, sollten Sie HTML nicht durch Zusammenführen von Strings generieren. Verwenden Sie stattdessen sichere Vorlagenbibliotheken mit automatischem Kontext-Escaping und eine noncebasierte Content Security Policy, um Bugs zu vermeiden.

Mit Trusted Types können Browser jetzt auch clientseitige DOM-basierte XSS-Angriffe verhindern.

Einführung in die API

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

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

Don'ts
anElement.innerHTML  = location.href;
Wenn „Vertrauenswürdige Typen“ aktiviert ist, wirft der Browser eine TypeError aus und verhindert die Verwendung eines DOM-XSS-Sinks mit einem String.

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

Do
anElement.innerHTML = aTrustedHTML;
  
Wenn „Vertrauenswürdige Typen“ aktiviert ist, akzeptiert der Browser ein TrustedHTML-Objekt für Sinks, die HTML-Snippets erwarten. Es gibt auch TrustedScript- und TrustedScriptURL-Objekte für andere sensible Senken.

Mit vertrauenswürdigen Typen lässt sich die Angriffsfläche für DOM-XSS-Angriffe Ihrer Anwendung erheblich reduzieren. Es vereinfacht die Sicherheitsüberprüfungen und ermöglicht es Ihnen, die typbasierten Sicherheitsprüfungen zu erzwingen, die beim Kompilieren, Linieren oder Bündeln Ihres Codes zur Laufzeit im Browser ausgeführt werden.

Vertraute Typen verwenden

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

Sie können einen Berichts-Collector wie den Open-Source-Dienst reporting-api-processor oder go-csp-collector bereitstellen oder eines der kommerziellen Äquivalente verwenden. Mit einem ReportingObserver können Sie auch benutzerdefinierte Protokolle und Fehlerbehebungen im Browser hinzufügen:

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 Ereignis-Listeners:

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

Alle Verstöße werden jetzt 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 identifizieren

Ab sofort sendet der Browser jedes Mal, wenn Trusted Types einen Verstoß erkennt, 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"
}
}

Das bedeutet, dass in https://my.url.example/script.js in Zeile 39 innerHTML mit der Zeichenfolge aufgerufen wurde, die mit <img src=x beginnt. Anhand dieser Informationen können Sie eingrenzen, welche Codeteile 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.

Den fehlerhaften Code umschreiben

Möglicherweise ist der nicht konforme Code nicht mehr erforderlich oder kann ohne die Funktionen umgeschrieben werden, die die Verstöße verursachen:

Do
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-Nutzlast zu entfernen.

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

DOMPurify unterstützt vertrauenswürdige Typen und gibt gereinigte HTML-Inhalte in einem TrustedHTML-Objekt zurück, damit 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 der Wert bereinigt und ein vertrauenswürdiger Typ für Sie erstellt werden kann. In diesen Fällen können Sie ein Objekt vom Typ „Vertrauenswürdiger Typ“ selbst erstellen.

Erstellen Sie zuerst eine Richtlinie. Richtlinien sind Fabriken für vertrauenswürdige Typen, die bestimmte Sicherheitsregeln auf ihre Eingaben anwenden:

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

Mit diesem Code wird eine Richtlinie namens myEscapePolicy erstellt, mit der über die Funktion createHTML() TrustedHTML-Objekte erstellt werden können. Die definierten Regeln führen zu einem HTML-Escape von <-Zeichen, um das Erstellen 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 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 überall dort verwendet, wo ein String in einem Sink verwendet wird, der nur vertrauenswürdige Typen akzeptiert.

Zur Erzwingung der Content Security Policy wechseln

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 nur der Code in einer Ihrer Richtlinien eine DOM-XSS-Sicherheitslücke verursachen. Sie können diese Lücke noch weiter schließen, indem Sie die Richtlinienerstellung einschränken.

Weitere Informationen