Stichpunkte, Kurzfakten und Antworten in einem FAQ-Block sollen reinen Text enthalten. Beim Erzeugen der Seiten entscheidet die Ausgabe, ob eine spitze Klammer in so einem Feld als Zeichen erscheint oder vom Browser als Beginn eines Elements gelesen wird. Wer Textfelder maskieren will, stößt bald auf ein Problem, das die naheliegende Lösung übersieht: Im Bestand stehen fast immer zwei Schreibweisen nebeneinander.
Der Ausgangsfehler
Ein Textfeld wird ohne Maskierung ausgegeben. Steht darin eine spitze Klammer, etwa in einem Stichpunkt über HTML, liest der Browser sie als Element. Ein geöffnetes Element kann den Rest der Zeile verschlucken oder das Layout verschieben. Bei Feldern, die nur die Redaktion füllt, ist das vor allem ein Darstellungsfehler. Kommt der Inhalt aus einem öffentlichen Formular, wird daraus eine Sicherheitslücke, weil dann fremder Code auf der Seite landen kann.
Das OWASP-Merkblatt zur Abwehr von Cross-Site-Scripting nennt für Text im HTML-Inhalt fünf Zeichen, die umgewandelt werden: das Und-Zeichen, beide spitzen Klammern sowie doppelte und einfache Anführungszeichen [1]. Es betont auch, dass die Kodierung zum Ausgabeort passen muss, weil Browser HTML, JavaScript, Adressen und CSS jeweils anders lesen [1]. In PHP wandelt htmlspecialchars() genau diese fünf Zeichen um [2]. Viele Projekte kürzen den Aufruf als h() ab.
Warum h() allein nicht reicht
Die erste Idee ist, jedes Textfeld durch h() zu schicken. Das stimmt, solange alle Einträge rohe Zeichen enthalten. Sobald mehr als eine Person schreibt oder Daten aus einem Import stammen, stehen aber auch Entities im Bestand, also < statt einer spitzen Klammer oder & statt eines Und-Zeichens. htmlspecialchars() kodiert in der Grundeinstellung auch vorhandene Entities noch einmal [2]. Der Leser sieht dann die Zeichenfolge < im Text, wo eine Klammer stehen sollte.

Die zweite Idee ist, vorher alles Markup zu entfernen, etwa mit strip_tags(). Das trifft die andere Hälfte des Bestands. Eine Zeile, die ein Element als Beispiel nennt, verliert genau dieses Beispiel. Das PHP-Handbuch warnt selbst, dass die Funktion HTML nicht prüft und bei unvollständigen Tags mehr Text entfernen kann als erwartet, und dass sie sich nicht zur Abwehr von Cross-Site-Scripting eignet [3]. Eine Fehlermeldung gibt es dabei nicht, nur einen Stichpunkt, der nichts mehr aussagt.
| Vorgehen | Feld mit rohem Markup | Feld mit Entities |
|---|---|---|
| nur maskieren | richtig | Entity erscheint im Klartext |
| Markup entfernen, dann maskieren | Angabe verschwindet | richtig |
| Entities auflösen, dann maskieren | richtig | richtig |
Die mittlere Zeile ist die gefährliche, weil sie still Daten verliert. Die erste fällt wenigstens auf, sobald jemand die Seite liest.
Noch eine Frage offen?
In der gratis Sprechstunde beantworten wir Fragen zu SEO live, jeden ersten Samstag im Monat um 18 Uhr. Oder du rufst einfach an.
Die tragfähige Fassung
Zuerst werden alle Entities aufgelöst, dann wird maskiert. html_entity_decode() wandelt Entities in ihre Zeichen zurück [4], htmlspecialchars() kodiert danach die fünf kritischen Zeichen genau einmal [2]. Beide Schreibweisen aus dem Bestand kommen so gleich heraus, und die Redaktion muss keinen einzigen Eintrag nachziehen. Bei einem Bestand mit Hunderten Einträgen ist das der Unterschied zwischen einer Korrektur in einer Funktion und einem eigenen Projekt.
Im SEO CMS heißt diese Funktion text_h(). Sie besteht aus genau diesen zwei Schritten, beide mit ENT_QUOTES und UTF-8. Die Angabe der Kodierung ist kein Zierrat: Seit PHP 8.1 lautet die Grundeinstellung beider Funktionen ENT_QUOTES, ENT_SUBSTITUTE und ENT_HTML401, vorher war es ENT_COMPAT, bei dem einfache Anführungszeichen unberührt blieben [2, 4]. Wer die Werte ausdrücklich setzt, bekommt auf jeder PHP-Version dasselbe Ergebnis.
Ein Grenzfall bleibt. Wer in einem Textfeld bewusst die Zeichenfolge einer Entity zeigen will, etwa in einem Stichpunkt über HTML-Entities, bekommt nach dem Auflösen das Zeichen selbst. Für solche Angaben ist ein Textfeld der falsche Ort. Sie gehören in ein HTML-Feld, das die Redaktion roh pflegt.
Ein Feld, eine Art der Ausgabe
Die Regel dahinter: Jedes Feld hat genau eine Art der Ausgabe, und die gilt in jeder Vorlage. Wo dasselbe Feld an einer Stelle roh und an einer anderen maskiert erscheint, fällt der Unterschied irgendwann auf, meist an einer Seite, die schon lange steht. Ob ein Feld HTML enthalten darf, legen sein Name und sein Zweck fest. Ein Beschreibungsfeld mit Absätzen ist ein anderes Feld als ein Stichpunkt, auch wenn beide in derselben Tabelle liegen.

Besonders tückisch sind Ausgaben, die anhand des Inhalts entscheiden, ob sie maskieren. Zwei ähnliche Texte verhalten sich dann verschieden, und ein Zeilenumbruch erscheint plötzlich als <br> mitten im Satz. Nach unserer Erfahrung kostet die Suche nach solchen Fehlern mehr Zeit als die Umstellung auf eine feste Regel je Feld.
Was im JSON-LD anders ist
Dieselben Felder landen oft auch in den strukturierten Daten einer Seite, zum Beispiel die Antworten eines FAQ-Blocks. Dort gilt ein anderer Ausgabeort mit eigenen Regeln: JSON braucht eine JSON-Kodierung, HTML-Entities haben darin nichts zu suchen. Wer den Text aus der Datenbank vorher normalisiert, also die Entities auflöst, bekommt in beiden Ausgaben dieselbe Aussage. Das passt zu Googles Richtlinie, nach der strukturierte Daten den Inhalt der Seite realitätsgetreu wiedergeben sollen [5]. Wie das SEO CMS solche Blöcke auszeichnet, beschreibt die Funktionsseite Strukturierte Daten und Antwortkästen. Ob sich ein FAQ-Block überhaupt lohnt, klärt der Beitrag FAQ-Blöcke richtig einsetzen.
Weitere Schutzmaßnahmen des Systems, etwa für Formulare und Anmeldung, sind unter Sicherheit und Datenschutz zusammengefasst. Was Entities und Elemente sind, erklärt das Glossar unter HTML.
Siehst du in deinem Bestand mal Klammern, mal Entities, und weißt nicht, welche Ausgabe welche Felder betrifft? Bring ein Beispiel in die gratis Sprechstunde mit, dann sprechen wir darüber, wie sich die Ausgabe vereinheitlichen lässt.
- Organisation Cross Site Scripting Prevention Cheat Sheet. OWASP Cheat Sheet Series. Stand laufend. abgerufen am 31.10.2026
- Organisation htmlspecialchars. PHP-Handbuch, The PHP Group. Stand laufend. abgerufen am 31.10.2026
- Organisation strip_tags. PHP-Handbuch, The PHP Group. Stand laufend. abgerufen am 31.10.2026
- Organisation html_entity_decode. PHP-Handbuch, The PHP Group. Stand laufend. abgerufen am 31.10.2026
- Organisation Allgemeine Richtlinien für strukturierte Daten. Google Search Central. Stand 16.09.2026. abgerufen am 31.10.2026
Häufige Fragen
Warum verpflichtet man die Redaktion nicht einfach auf eine Schreibweise?
Das ändert den vorhandenen Bestand nicht. Bei Hunderten Einträgen wäre die Umstellung ein eigenes Projekt, und beim nächsten Import aus einer fremden Quelle stünde man wieder am Anfang.
Ist eine fehlende Maskierung ein Sicherheitsproblem?
Bei Feldern, die nur die Redaktion füllt, ist es vor allem ein Darstellungsproblem. Sobald ein Feld aus einem öffentlichen Formular gespeist wird, entscheidet die Maskierung darüber, ob fremder Code auf der Seite landen kann.
Was ist mit Feldern, die absichtlich HTML enthalten?
Sie bleiben roh und werden überall so behandelt. Erkennbar sind sie am Feldnamen und am Zweck, nie am Inhalt.
Reicht strip_tags() als Schutz?
Nein. Das PHP-Handbuch rät selbst davon ab, strip_tags() gegen Cross-Site-Scripting einzusetzen, und empfiehlt htmlspecialchars() oder andere Mittel passend zum Ausgabeort.
Muss ich die Kodierung bei htmlspecialchars() angeben?
Es ist sinnvoll. Die Grundeinstellung hat sich mit PHP 8.1 geändert. Wer ENT_QUOTES und UTF-8 ausdrücklich setzt, bekommt auf jeder Version dieselbe Ausgabe.
Noch eine Frage offen?
In der gratis Sprechstunde beantworten wir Fragen zu SEO live, jeden ersten Samstag im Monat um 18 Uhr. Oder du rufst einfach an.
Zuletzt geprüft am 03.11.2026, nächste Prüfung spätestens im November 2027. Fehler entdeckt? Schreib uns.




