htmlspecialchars($text) wandelt &, <, >, " und ' in &, <, >, " und ' um. Ruf es für jedes Stück Nutzereingabe auf, das du in eine Seite ausgibst, dann wird die Eingabe als Text angezeigt, statt als HTML gelesen zu werden.
Der Block gibt denselben Kommentar zweimal aus, einmal roh und einmal escapt. Führe ihn aus und vergleiche die beiden Zeilen im Tab Page, tippe dann eigenes HTML ins Formular, zum Beispiel <h1>big</h1> oder <img src=x>, und klicke auf Show.
In der rohen Zeile befolgt der Browser die Tags: Das fette Wort ist fett, und ein Besucher, der <script> tippt, lässt sein Skript im Browser jedes anderen Lesers laufen. Dieser Angriff heißt Cross-Site Scripting (XSS). In der escapten Zeile kommen dieselben Zeichen als <b> an, und der Browser zeichnet sie als Text. Wechsle zum Tab Output, um die Entities zu sehen, die PHP tatsächlich ausgegeben hat.
Was htmlspecialchars umwandelt
Fünf Zeichen, sonst nichts. Buchstaben, Akzente und Emoji bleiben unverändert.
& steht auf der Liste, weil damit jede Entity beginnt: Bliebe es unverändert, würde ein Kommentar, der < erwähnt, als < angezeigt.
Attribute escapen, nicht nur Text
Nutzereingaben in einem Attribut brauchen genauso Escaping. Ohne schließt ein Anführungszeichen im Wert das Attribut, und der Rest der Eingabe wird zu neuen Attributen. Hier schmuggelt der „Name“ ein Attribut style hinein; führe es aus und schau dir die beiden Felder an.
Im unsicheren Feld sieht der Browser value="Ada", gefolgt von einem neuen Attribut style, also wird das Feld rot und zeigt nur Ada. Ein Angreifer würde dort onfocus="..." statt style schreiben, und sein Code würde laufen. Im sicheren Feld wurde jedes " zu ", also bleibt der ganze String in value und wird so angezeigt, wie er getippt wurde.
Seit PHP 8.1 sind die Standard-Flags ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, also werden auch einfache Anführungszeichen escapt, und Attribute mit '...' sind sicher. Älterer Code übergibt ENT_QUOTES oft von Hand, und unter PHP 7 und älter war das nötig:
Ein kurzer Helfer für Templates
htmlspecialchars($x, ENT_QUOTES, 'UTF-8') dutzende Male in einem Template zu schreiben ist unübersichtlich, also packen die meisten Projekte es in eine Funktion mit einem Buchstaben. Template-Engines wie Twig und Blade machen dasselbe automatisch für jedes {{ $var }}.
Der Typ ?string und ?? '' sind wichtig: null an htmlspecialchars() zu übergeben ist seit PHP 8.1 veraltet, und dein eigenes PHP würde für jeden Nutzer ohne Bio einen Deprecation-Hinweis ausgeben.
Doppeltes Kodieren und htmlspecialchars_decode
Wird ein Wert zweimal escapt, sieht der Leser die Entities: & wird beim ersten Mal zu & und beim zweiten zu &amp;, was der Browser als & anzeigt. Meist heißt das, dass der Wert beim Speichern und noch einmal bei der Ausgabe escapt wurde. Übergib double_encode: false, um vorhandene Entities in Ruhe zu lassen, und verwende htmlspecialchars_decode(), um zurückzugehen.
Die echte Lösung ist, Rohtext zu speichern und nur bei der Ausgabe zu escapen. double_encode: false ist für Text, der bereits Entities enthält, die du nicht erzeugt hast, etwa einen importierten Feed.
htmlspecialchars vs htmlentities vs strip_tags
Diese drei werden oft verwechselt. Der Block wendet alle drei auf dieselbe Eingabe an und zeigt jeweils, was PHP ausgibt und was der Browser daraus macht:
htmlspecialchars()escapt die fünf HTML-Zeichen. Verwende es für jeden Text, den du in HTML ausgibst.htmlentities()macht außerdem auséeiné. Das war nützlich, als Seiten nicht UTF-8 waren; heute macht es den Quelltext nur schwerer lesbar.strip_tags()löscht Tags und behält ihren Text. Es ist dafür da, aus HTML reinen Text zu machen (eine E-Mail-Vorschau, eine Meta-Beschreibung), nicht für Sicherheit: Die letzte Zeile zeigt, dass ein erlaubtes<b>seinonclickbehält, und Text in einem Attribut wird gar nicht angerührt.
Wo htmlspecialchars nicht reicht
htmlspecialchars() ist das richtige Escaping für HTML-Text und Attribute in Anführungszeichen. Andere Stellen einer Seite haben andere Regeln:
- In einer URL kodiert
http_build_query()oderurlencode()den Wert;htmlspecialchars()macht dann das&zwischen den Parametern zu gültigem HTML. - In JavaScript erzeugt
json_encode()einen gültigen JS-Wert, undJSON_HEX_TAGmacht aus<und>die Escapes\u003Cund\u003E, damit ein</script>in den Daten das Script-Tag nicht schließen kann. - Gib Nutzereingaben nie in ein
hrefaus, ohne das Schema zu prüfen:htmlspecialchars('javascript:alert(1)')bleibt unverändert und läuft beim Klicken trotzdem. Akzeptiere nur URLs mithttpundhttps, wie auf der Seite zu filter_var gezeigt.
Für die Verarbeitung von Formularen, die all das zusammenbringt, siehe PHP-Formulare.
Häufig gestellte Fragen
Was macht htmlspecialchars in PHP?
Es ersetzt &, <, >, " und ' durch &, <, >, " und '. Der Browser zeigt diese Zeichen dann an, statt sie als HTML zu lesen, also wird ein <script>, das in ein Formular getippt wurde, als Text gezeigt und läuft nie.
Was ist der Unterschied zwischen htmlspecialchars und htmlentities?
htmlspecialchars() wandelt nur die fünf Zeichen um, die in HTML besonders sind. htmlentities() wandelt außerdem jedes Zeichen um, für das es eine benannte Entity gibt, also wird café zu café. Bei UTF-8-Seiten sind beide gleich sicher, und htmlspecialchars() hält die Ausgabe lesbar, also ist es die übliche Wahl.
Brauche ich in PHP 8 noch ENT_QUOTES?
Nicht für die Sicherheit: Seit PHP 8.1 sind die Standard-Flags ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, also werden auch einfache Anführungszeichen escapt. Viele Codebasen übergeben ENT_QUOTES, 'UTF-8' trotzdem ausdrücklich, damit sich der Aufruf auf älteren Versionen gleich verhält und für Leser offensichtlich ist.
Sollte ich htmlspecialchars bei der Eingabe oder bei der Ausgabe verwenden?
Bei der Ausgabe. Speichere und validiere den Rohwert und escape ihn in dem Moment, in dem du ihn in HTML ausgibst. Bei der Eingabe zu escapen speichert < in deiner Datenbank, verfälscht Längen und Suchen und führt zu doppeltem Escapen wie &lt;.
Reicht strip_tags, um XSS zu verhindern?
Nein. strip_tags() entfernt Tags, aber sein Parameter für erlaubte Tags behält deren Attribute, also überlebt <b onclick="...">, und für Text in einem Attribut tut es gar nichts. Verwende htmlspecialchars(), wenn du Nutzereingaben ausgibst.