Kurz gesagt: XSS in PHP verhindern Sie, indem Sie jede Ausgabe passend zu ihrem Kontext kodieren: htmlspecialchars() für HTML, json_encode() mit Schutzoptionen für JavaScript, geprüfte URLs für Links. Templates mit automatischem Escaping nehmen Ihnen viel davon ab. Eine Content Security Policy und HttpOnly-Cookies begrenzen den Schaden, falls doch eine Lücke bleibt.
Warum Cross-Site-Scripting jetzt mitgeprüft werden sollte
Die Aufmerksamkeit liegt im Oktober 2026 auf PHP2Shell, zwei kritischen Lücken im PHP-Interpreter, für die seit dem 21.10.2026 Patches vorliegen. Viele Unternehmen lassen ihre Webanwendungen in diesem Zuge überprüfen. Das ist ein guter Anlass, auch Schwachstellen im eigenen Code anzusehen, die kein PHP-Update behebt.
Cross-Site-Scripting, kurz XSS, ist eine davon. Die OWASP Top 10 führen XSS innerhalb der Kategorie Injection. Anders als bei einer Lücke im Server richtet sich der Angriff hier gegen die Besucher und Nutzer Ihrer Website, etwa gegen Kunden im Shop oder Mitarbeitende im Backend.
Die Folgen können erheblich sein: übernommene Sitzungen von Administratoren, manipulierte Formulare, die Daten an Dritte senden, oder gefälschte Inhalte auf Ihrer Website. Wird ein Administratorkonto übernommen, kann das bis zur vollständigen Kontrolle über die Anwendung führen.
Wie entsteht XSS in PHP-Anwendungen?
XSS entsteht, wenn eine Anwendung Daten aus einer nicht vertrauenswürdigen Quelle ungeprüft in eine Seite ausgibt. Der Browser kann dann nicht mehr zwischen Ihren Inhalten und eingeschleustem Code unterscheiden und führt beides aus.
Man unterscheidet drei Formen. Bei reflektiertem XSS stammt der Inhalt direkt aus der aktuellen Anfrage, etwa aus einem Suchbegriff in der URL. Bei gespeichertem XSS liegt er in der Datenbank, zum Beispiel in einem Kommentar oder Profilfeld, und trifft jeden, der die Seite aufruft. DOM-basiertes XSS entsteht im JavaScript des Browsers, wenn Skripte Werte aus der URL in die Seite schreiben.
Nicht vertrauenswürdig sind alle Daten, die nicht fest im Code stehen: Formularfelder, URL-Parameter, Cookies, Dateinamen, Antworten externer Schnittstellen und Inhalte aus der eigenen Datenbank, die irgendwann von außen kamen.
Ausgabe je Kontext richtig kodieren
Die wichtigste Regel lautet: Kodieren bei der Ausgabe, und zwar passend zum Ort, an dem der Wert landet. Für HTML-Inhalt und Attribute in Anführungszeichen ist htmlspecialchars() das Mittel der Wahl. Eine kleine Hilfsfunktion verhindert, dass Optionen vergessen werden:
<?php
function e(?string $wert): string
{
return htmlspecialchars($wert ?? '', ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
?>
<h2><?= e($produkt['name']) ?></h2>
<input type="text" name="suche" value="<?= e($_GET['q'] ?? '') ?>">
Andere Kontexte brauchen andere Mittel:
| Kontext | Sichere Methode |
|---|---|
| HTML-Inhalt, Attribut in Anführungszeichen | htmlspecialchars() mit ENT_QUOTES und UTF-8 |
| Daten für JavaScript | json_encode() mit JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT |
| Parameter in einer URL | rawurlencode() für den einzelnen Wert |
| Vollständige URL aus Benutzereingabe | Schema prüfen, nur https und http zulassen, dann htmlspecialchars() |
| Attribute ohne Anführungszeichen, Event-Handler, CSS | vermeiden, Werte dort nicht einsetzen |
Für Daten, die ein Skript braucht, ist ein Datenattribut oder ein JSON-Block meist der sauberste Weg:
<?php
$daten = json_encode($konfiguration, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div id="app" data-konfig="<?= e($daten) ?>"></div>
Im JavaScript lesen Sie den Wert dann über das Datenattribut aus und setzen Inhalte mit textContent statt mit innerHTML.
Templates, HTML-Bereinigung und Frameworks
Template-Engines wie Twig und Blade kodieren Ausgaben standardmäßig automatisch. Das schließt einen Großteil typischer Fehler aus. Gefährlich wird es dort, wo dieses Verhalten bewusst abgeschaltet wird, etwa mit dem Filter raw in Twig oder der Syntax {!! !!} in Blade. Solche Stellen sollten im Code selten sein und bei jedem Review gezielt geprüft werden.
Soll Ihre Anwendung bewusst HTML von Nutzern zulassen, etwa Formatierungen in Kommentaren oder in einem Redaktionssystem, reicht Kodieren nicht, weil das HTML ja dargestellt werden soll. Dann brauchen Sie eine geprüfte Bereinigungsbibliothek wie HTML Purifier, die nur eine feste Liste erlaubter Elemente und Attribute durchlässt. Eigene Filter mit regulären Ausdrücken sind erfahrungsgemäß lückenhaft.
Prüfen Sie besonders Backend-Bereiche. XSS in Ansichten, die nur Administratoren sehen, wird oft unterschätzt, trifft aber genau die Konten mit den meisten Rechten.
XSS in PHP-Bestandscode finden
In gewachsenen Anwendungen ist die erste Frage meist nicht, wie man richtig kodiert, sondern wo es bisher vergessen wurde. Ein pragmatischer Einstieg ist die Suche nach Stellen, an denen Variablen direkt ausgegeben werden:
# Direkte Ausgaben von Anfragedaten
grep -rnE "(echo|print|<\?=)\s*\\\$_(GET|POST|REQUEST|COOKIE|SERVER)" src/ templates/ --include="*.php"
# Abgeschaltetes Auto-Escaping in Twig und Blade
grep -rnE "\|raw|\{!!" templates/ resources/views/
Treffer sind Prüfaufträge, keine fertigen Befunde. Gespeichertes XSS findet eine solche Suche nur bedingt, weil die Werte dort aus der Datenbank kommen. Prüfen Sie deshalb zusätzlich alle Ansichten, die Inhalte von Nutzern anzeigen: Kommentare, Profile, Bestellnotizen, Support-Tickets. Bei CMS und Shops gehören Plugins und Themes von Drittanbietern ausdrücklich dazu, denn dort sitzt ein großer Teil der Ausgabelogik.
Zweite Verteidigungslinie: CSP und sichere Cookies
Auch bei sorgfältigem Code kann eine Stelle übersehen werden. Zwei Maßnahmen begrenzen dann den Schaden. Eine Content Security Policy erlaubt dem Browser nur Skripte aus festgelegten Quellen und verbietet eingebettete Skripte, sofern sie nicht ausdrücklich freigegeben sind:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
Führen Sie eine solche Richtlinie schrittweise ein, zunächst im reinen Berichtsmodus über Content-Security-Policy-Report-Only, denn bestehende Einbindungen von Analyse- oder Zahlungsdiensten müssen meist ergänzt werden. Weitere Header beschreibt der Ratgeber Sicherheits-Header für Websites.
Zusätzlich sollten Sitzungscookies das Attribut HttpOnly tragen, damit Skripte sie nicht auslesen können. Wie Sie Sessions insgesamt absichern, steht im Ratgeber Sessions in PHP absichern. Eine verwandte Schwachstelle auf der Datenbankseite behandelt SQL-Injection in PHP verhindern.
Wie hilft PHP SOS konkret?
- Wir prüfen Templates und Ausgaben Ihrer Anwendung auf fehlende oder falsche Kodierung.
- Wir bewerten Funde nach Erreichbarkeit und betroffenen Konten und zeigen konkrete Korrekturen.
- Wir führen eine Content Security Policy schrittweise ein, ohne Shop- oder Zahlungsfunktionen zu stören.
- Wir testen nach der Korrektur erneut und dokumentieren das Ergebnis.
Eine unabhängige Prüfung beschreibt die Leistung Sicherheitsaudit und Penetrationstest.
Häufige Fragen
Reicht htmlspecialchars gegen XSS?
Für normalen Text im HTML-Inhalt und in Attributen in Anführungszeichen ja, wenn Sie ENT_QUOTES und den Zeichensatz UTF-8 angeben. In JavaScript, in URLs oder in CSS gelten andere Regeln, dort braucht es eine eigene, passende Kodierung.
Sollte ich Eingaben beim Speichern oder bei der Ausgabe filtern?
Kodieren Sie bei der Ausgabe, passend zum Ziel. Eingaben sollten Sie beim Empfang auf Plausibilität prüfen, etwa Länge und Format. Wer nur beim Speichern filtert, übersieht Daten aus anderen Quellen und erschwert spätere Nutzung in anderen Kontexten.
Was bringt eine Content Security Policy?
Eine Content Security Policy legt fest, aus welchen Quellen der Browser Skripte laden und ausführen darf. Sie kann die Auswirkungen einer übersehenen XSS-Lücke deutlich begrenzen, ersetzt aber keine korrekte Kodierung.
Wie erlaube ich Formatierungen wie fett oder Links in Kommentaren?
Nutzen Sie eine geprüfte HTML-Bereinigungsbibliothek mit einer Whitelist erlaubter Elemente und Attribute. Eigene reguläre Ausdrücke zum Entfernen von Skripten sind fehleranfällig und sollten nicht verwendet werden.