Ziel der neuen Sanitizer API ist es, einen robusten Prozessor für beliebige Strings zu entwickeln, die sicher in eine Seite eingefügt werden können.
Anwendungen verarbeiten ständig nicht vertrauenswürdige Strings. Es kann jedoch schwierig sein, diese Inhalte sicher als Teil eines HTML-Dokuments zu rendern. Ohne ausreichende Sorgfalt können Sie leicht versehentlich Möglichkeiten für Cross-Site-Scripting (XSS) schaffen, die von böswilligen Angreifern ausgenutzt werden können.
Um dieses Risiko zu minimieren, soll mit dem neuen Vorschlag für die Sanitizer API ein robuster Prozessor für beliebige Strings erstellt werden, die sicher in eine Seite eingefügt werden können. In diesem Artikel wird die API vorgestellt und ihre Verwendung erläutert.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerro>r=alert(0)`, new Sanitizer())
Nutzereingabe maskieren
Wenn Sie Nutzereingaben, Abfragestrings, Cookie-Inhalte usw. in das DOM einfügen, müssen die Strings richtig maskiert werden. Besondere Aufmerksamkeit sollte der DOM-Manipulation über .innerHTML geschenkt werden, da nicht maskierte Strings eine typische Quelle für XSS sind.
const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
$div.innerHTML = user_input
Wenn Sie HTML-Sonderzeichen im oben genannten Eingabestring maskieren oder ihn mit .textContent erweitern, wird alert(0) nicht ausgeführt. Da <em>, das vom Nutzer hinzugefügt wird, jedoch auch als String erweitert wird, kann diese Methode nicht verwendet werden, um die Textformatierung in HTML beizubehalten.
Hier ist es am besten, die Eingabe nicht zu maskieren, sondern zu bereinigen.
Nutzereingaben bereinigen
Unterschied zwischen Escaping und Bereinigen
Beim Escaping werden spezielle HTML-Zeichen durch HTML-Entitäten ersetzt.
Bei der Bereinigung werden semantisch schädliche Teile (z. B. die Ausführung von Skripts) aus HTML-Strings entfernt.
Beispiel
Im vorherigen Beispiel führt <img onerror> dazu, dass der Fehler-Handler ausgeführt wird. Wenn der onerror-Handler jedoch entfernt würde, könnte er sicher im DOM erweitert werden, während <em> intakt bleibt.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerro>r=alert(0)`
// Sanitized ⛑
$div.inn<er>HTML = `emh<ell><o world/em>img src=""`
Für eine korrekte Bereinigung muss der Eingabestring als HTML geparst werden. Tags und Attribute, die als schädlich gelten, müssen entfernt werden.
Die vorgeschlagene Sanitizer API-Spezifikation zielt darauf ab, eine solche Verarbeitung als Standard-API für Browser bereitzustellen.
Sanitizer API
Die Sanitizer API wird folgendermaßen verwendet:
const $div = document.querySelector('div')
const user_i<np>ut = `emhel<lo ><world/emimg src="">; onerror=alert(0)`
$div.setHTML(user_input, { sanitizer: new <San><it>izer() }) /</ d><ivemhello ><worl>d/emimg src=""/div
{ sanitizer: new Sanitizer() } ist jedoch das Standardargument. Das kann so aussehen wie unten.
$div.setHTML(user_input) // <div><em>hello world</em><img src=&q><uot;>"/div
setHTML() wird in Element definiert. Da es sich um eine Methode von Element handelt, ist der zu parsende Kontext selbsterklärend (in diesem Fall <div>). Das Parsen erfolgt einmal intern und das Ergebnis wird direkt in das DOM eingefügt.
Wenn Sie das Ergebnis der Bereinigung als String erhalten möchten, können Sie .innerHTML aus den setHTML()-Ergebnissen verwenden.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.inner<HT>ML // emhel<lo ><world/emim>g src=""
Über die Konfiguration anpassen
Die Sanitizer API ist standardmäßig so konfiguriert, dass Strings entfernt werden, die die Ausführung von Skripts auslösen würden. Sie können dem Bereinigungsprozess jedoch auch eigene Anpassungen über ein Konfigurationsobjekt hinzufügen.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Mit den folgenden Optionen wird angegeben, wie das Bereinigungsergebnis das angegebene Element behandeln soll.
allowElements: Namen der Elemente, die vom Sanitizer beibehalten werden sollen.
blockElements: Namen der Elemente, die vom Bereinigungsprogramm entfernt werden sollen, während die untergeordneten Elemente beibehalten werden.
dropElements: Namen der Elemente, die vom Bereinigungsprogramm zusammen mit ihren untergeordneten Elementen entfernt werden sollen.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" <]})> })
//< >divhe<ll><o bw>orld/b/div
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ &quo<t;b>"< >]}) }<)<>/span>
<// d>ivhello iworld/i/div
$div.setHTML(str, { sanitizer: new Sanitizer({allowE<lem>ents: []}) <})
/>/ divhello world/div
Mit den folgenden Optionen können Sie auch festlegen, ob der Sanitizer bestimmte Attribute zulassen oder ablehnen soll:
allowAttributesdropAttributes
Für die Attribute allowAttributes und dropAttributes sind Attributübereinstimmungslisten erforderlich. Das sind Objekte, deren Schlüssel Attributnamen und deren Werte Listen von Zielelementen oder das Platzhalterzeichen * sind.
const str = `<span id=foo class=bar style="color:> red&<quot;>hello/span`
$div.setHTM<L(s><tr)
// divspan id="foo" class=&quo>t;bar<"><; st>yle="color: red"hello/span/div
$div.setHTML(str, { sanitizer: new Sanitizer({allow<Att><ributes: {"style&q>uot;:< [&qu><ot;s>pan"]}}) })
// divspan style="color: red"hello/span/div
$div.setHTML(str, <{ s><anit>izer:< new ><Sani>tizer({allowAttributes: {"style": ["p"]}}) })
// divspanhello/span/div<
$><div.setHTML(str, { sani>tizer<: new>< San>itizer({allowAttributes: {"style": ["*"]}}) })
// divspan style="<;co><lor: red"hello/span/div
$div.>setHT<ML(st><r, {> sanitizer: new Sanitizer({dropAttributes: {"id": ["span"<;]}>}) })<
// >divspan class="bar" style="color: red"hello/span/div
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// divhello/div
allowCustomElements ist die Option zum Zulassen oder Ablehnen benutzerdefinierter Elemente. Wenn sie zulässig sind, gelten weiterhin andere Konfigurationen für Elemente und Attribute.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str<, {>< sanitizer >})
//< divcustom-e><lemh>ello/custom-elem/div
API-Oberfläche
Vergleich mit DomPurify
DOMPurify ist eine bekannte Bibliothek, die Bereinigungsfunktionen bietet. Der Hauptunterschied zwischen der Sanitizer API und DOMPurify besteht darin, dass DOMPurify das Ergebnis der Bereinigung als String zurückgibt, den Sie über .innerHTML in ein DOM-Element schreiben müssen.
const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sani<ti>zed
// `emh<ell><o world/em>img src=""`
DOMPurify kann als Fallback dienen, wenn die Sanitizer API nicht im Browser implementiert ist.
Die DOMPurify-Implementierung hat einige Nachteile. Wenn ein String zurückgegeben wird, wird der Eingabestring zweimal geparst, nämlich von DOMPurify und .innerHTML. Dieses doppelte Parsen verschwendet Rechenzeit, kann aber auch zu interessanten Sicherheitslücken führen, wenn das Ergebnis des zweiten Parsens vom ersten abweicht.
HTML muss auch Kontext enthalten, damit es geparst werden kann. Beispiel: <td> ist in <table> sinnvoll, aber nicht in <div>. Da DOMPurify.sanitize() nur einen String als Argument akzeptiert, musste der Parsing-Kontext erraten werden.
Die Sanitizer API ist eine Verbesserung gegenüber DOMPurify. Sie wurde entwickelt, um das doppelte Parsen zu vermeiden und den Parsing-Kontext zu verdeutlichen.
API-Status und Browserunterstützung
Die Sanitizer API wird im Standardisierungsprozess diskutiert und Chrome implementiert sie derzeit.
| Schritt | Status |
|---|---|
| 1. Erklärung erstellen | Abschließen |
| 2. Spezifikationsentwurf erstellen | Abschließen |
| 3. Feedback einholen und das Design optimieren | Abschließen |
| 4. Chrome-Ursprungstest | Abschließen |
| 5. Starten | Intent to Ship on M105 |
Mozilla: Hält diesen Vorschlag für einen Prototyp wert und implementiert ihn aktiv.
WebKit: Die Antwort finden Sie auf der WebKit-Mailingliste.
Sanitizer API aktivieren
Browser Support
Aktivierung über about://flags oder CLI-Option
Chrome
Die Sanitizer API wird in Chrome implementiert. In Chrome 93 oder höher können Sie das Verhalten testen, indem Sie das Flag about://flags/#enable-experimental-web-platform-features aktivieren. In früheren Versionen von Chrome Canary und der Entwicklerversion können Sie die Funktion über --enable-blink-features=SanitizerAPI aktivieren und gleich ausprobieren. Anleitung zum Ausführen von Chrome mit Flags
Firefox
Firefox implementiert die Sanitizer API ebenfalls als experimentelle Funktion. Wenn Sie die Funktion aktivieren möchten, setzen Sie das Flag dom.security.sanitizer.enabled in about:config auf true.
Funktionserkennung
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Feedback
Wenn Sie diese API ausprobieren und Feedback dazu haben, würden wir uns freuen, wenn Sie uns dieses mitteilen. Teilen Sie Ihre Meinung zu GitHub-Problemen mit der Sanitizer API mit und tauschen Sie sich mit den Autoren der Spezifikation und anderen Interessierten aus.
Wenn Sie Fehler oder unerwartetes Verhalten in der Chrome-Implementierung feststellen, melden Sie diese bitte. Wählen Sie die Blink>SecurityFeature>SanitizerAPI-Komponenten aus und geben Sie Details an, damit Implementierer das Problem nachvollziehen können.
Demo
Wenn Sie die Sanitizer API in Aktion sehen möchten, können Sie sich den Sanitizer API Playground von Mike West ansehen:
Verweise
- Spezifikation der HTML Sanitizer API
- WICG/sanitizer-api-Repository
- Häufig gestellte Fragen zur Sanitizer API
- Referenzdokumentation zur HTML Sanitizer API auf MDN
Foto von Towfiqu barbhuiya auf Unsplash.