Jedes Web-Sicherheitsgespräch landet irgendwann auf der gleichen Folie: Escapen Sie Benutzereingaben immer, bevor Sie sie in HTML einfügen. Der Grund sind HTML-Entitäten — der Mechanismus, der <script> in <script> verwandelt und verhindert, dass ein Kommentar eines Benutzers zu einem XSS-Angriff wird.
Aber Escaping ist nur die halbe Miete. Unescaping — & zurück in & verwandeln — ist ebenso wichtig und ebenso gefährlich, wenn es falsch gemacht wird. Dieser Beitrag behandelt beide Richtungen, die wichtigen Zeichen und die Fehler, die zu echten Schwachstellen führen.
Die fünf wichtigen Zeichen
HTML hat fünf Zeichen, die strukturell bedeutsam sind — sie begrenzen Tags, Attribute und Entitäten. Wenn eines davon in Benutzerinhalten vorkommt und nicht escaped wird, interpretiert der Browser sie als HTML-Markup:
| Zeichen | Entität | Warum es wichtig ist |
|---|---|---|
< |
< |
Beginnt ein Tag. <script> ist ein Tag. |
> |
> |
Beendet ein Tag. |
& |
& |
Beginnt eine Entität. < ist eine Entität. |
" |
" |
Begrenzt Attributwerte. |
' |
' |
Begrenzt einfach angeführte Attribute. |
Wenn Sie Text in den Inhalt eines HTML-Elements einfügen, müssen Sie <, > und & escapen. Wenn Sie Text in einen Attributwert einfügen, müssen Sie auch " escapen (oder ' wenn das Attribut einfache Anführungszeichen verwendet).
Wie Entitäten funktionieren
Eine HTML-Entität ist eine spezielle Sequenz, die der Browser als ein einzelnes Zeichen interpretiert:
- Benannte Entitäten:
&,<,>,", — leicht zu lesen, begrenzter Satz. - Numerische Entitäten:
&#60;(dezimal),&#x3C;(hex) — jedes Unicode-Zeichen nach Codepoint. - Benannte Entitäten für Symbole:
&copy;(©),&euro;(€),&mdash;(—) — Bequemlichkeitsnamen.
Die &-Entität ist besonders, weil sie das Escape für & selbst ist. Wenn Sie zuerst & escapen, dann < und >, escapen Sie nicht doppelt. Die Reihenfolge ist wichtig:
Richtig: & → &amp; → &amp;lt; (richtig)
Falsch: < → &lt; → &amp;lt; (doppeltes Escaping)
Escapen Sie immer zuerst &. Dann die anderen Zeichen. Dann gibt es kein doppeltes Escaping.
Wwo Escaping erforderlich ist
Benutzergenerierte Inhalte. Kommentare, Forum-Beiträge, Bewertungen, Nachrichten — jeder Text, der von einem Benutzer kommt und auf einer Seite erscheint, muss escaped werden. Ein Kommentar, der <img src=x onerror=alert(1)> enthält, ist ein XSS-Angriff, wenn Sie < und > nicht escapen.
Daten in Attributen. Wenn Sie Benutzerdaten in ein href, src, alt oder title-Attribut geben, escapen Sie die Entitäten-Zeichen. Ein Wert wie foo" onclick="alert(1) bricht aus dem Attribut aus und injiziert einen Event-Handler.
JavaScript-Zeichenketteninterpolation. Wenn Sie HTML innerhalb von JavaScript aufbauen:
// GEFÄHRLICH: rohe Interpolation
element.innerHTML = `<p>${userComment}</p>`;
// SICHER: escaped Interpolation
element.innerHTML = `<p>${escapeHtml(userComment)}</p>`;
innerHTML interpretiert die Zeichenkette als HTML. Wenn userComment Markup enthält, wird es ausgeführt. Escapen Sie immer, bevor Sie in HTML interpolieren.
Serverseitiges Rendering. Template-Engines (Handlebars, Jinja, EJS, Razor) escapen normalerweise standardmäßig automatisch. Aber wenn Sie einen “safe” oder “raw”-Filter verwenden (wie Handlebars dreifache geschweifte Klammern {{{ oder Djangos |safe), schalten Sie das Escaping aus. Tun Sie das nur, wenn Sie den Inhalt kontrollieren.
Wann unescapen
Unescaping kehrt den Prozess um: &lt; → <, &amp; → &. Es ist erforderlich, wenn:
- Anzeige von kodierten Daten. Wenn eine API escaped HTML zurückgibt (manche tun das, um Rendering zu verhindern), müssen Sie unescapen, bevor Sie anzeigen.
- Verarbeitung von Formulardaten. Formularübermittlungen können URL-kodierte oder entitäten-kodierte Werte enthalten.
- Analyse von Benutzereingaben. Wenn ein Benutzer HTML-kodierten Text einfügt und Sie ihn so anzeigen wollen, unescapen Sie zuerst — dann erneut escapen, wenn Sie ihn wieder in HTML einfügen.
Die Gefahr: Unescapen Sie nur, wenn Sie gleich anzeigen, nie wenn Sie speichern. Speichern Sie die ursprüngliche, escaped Form. Unescapen Sie zum Zeitpunkt des Renderings.
XSS: nur ein escaped Zeichen entfernt
Cross-Site Scripting (XSS) tritt auf, wenn unescapte Benutzereingaben den HTML-Parser des Browsers erreichen. Die häufigsten Varianten:
Gespeichertes XSS. Ein Benutzer sendet <script>steal(document.cookie)</script> als Kommentar. Der Server speichert es unescaptd. Jeder Besucher, der den Kommentar ansieht, führt das Skript aus.
Reflektiertes XSS. Eine URL enthält ?q=<script>alert(1)</script>. Der Server reflektiert den Parameter q auf die Seite ohne zu escapen. Jeder, der auf den Link klickt, führt das Skript aus.
DOM-basiertes XSS. JavaScript liest location.hash und fügt es über innerHTML in das DOM ein. Der Hash enthält Markup. Der Browser rendert es.
Alle drei werden durch dasselbe verhindert: Escapen Sie <, >, &, " und ', bevor Sie Inhalte in HTML einfügen.
Die Escaping-Funktion
Jede Sprache hat eine. Das Muster ist dasselbe:
function escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
Die Reihenfolge ist wichtig: & zuerst, dann die anderen. Wenn Sie < vor & escapen, erhalten Sie &amp;lt; statt &lt;.
Für High-Throughput-Systeme verwenden Sie eine Bibliothek. DOMPurify bereinigt HTML (erlaubt sichere Tags, entfernt gefährliche). Die Web-Plattform-eigenen DOMParser und textContent handled Escaping ebenfalls implizit.
Häufige Fehler
&nach<escapen — verursacht doppeltes Escaping:&amp;lt;statt&lt;.- Attributkontext vergessen —
<und>escapen, aber nicht"erlaubt Attributinjektion. innerHTMLohne Escaping verwenden — der häufigste XSS-Vektor.- Serverantworten vertrauen — APIs, die “bereinigtes” HTML zurückgeben, bereinigen möglicherweise nicht alles. Escapen Sie auch auf dem Client.
- Zu früh unescapen — Unescaping zum Zeitpunkt des Speicherungs bedeutet, dass die rohe Nutzlast in Ihrer Datenbank sitzt, bereit auszuführen, wenn das Rendering vergisst zu re-escapen.
Ausprobieren
Wenn Sie HTML-kodierten Text dekodieren oder rohen Text für sicheres Einfügen in HTML escapen müssen, verwenden Sie ein browserbasiertes Tool, damit die Konvertierung lokal bleibt.