XSS und SQL-Injection anhand sicherer Lernbeispiele verstehen
Vom gebundenen Suchwert zum sichtbaren Text: ein reproduzierbarer Lernablauf mit fiktiven Daten und klaren Grenzen.

XSS und SQL-Injection entstehen an unterschiedlichen Stellen, haben aber eine gemeinsame Grundfrage: Werden Daten als Daten behandelt oder erhalten sie unbeabsichtigt die Bedeutung von Anweisungen? Bei einer HTML-Ausgabe entscheidet der Browser, welche Zeichenfolge als Struktur interpretiert wird. Bei einer Datenbankabfrage entscheidet die SQL-Verarbeitung, welche Bestandteile zur Abfrage gehören. Die passenden Schutzmaßnahmen müssen deshalb an der jeweiligen Grenze ansetzen.
Dieser Leitfaden verwendet ein vollständig isoliertes Lernbeispiel mit PHP und einer SQLite-Datenbank im Arbeitsspeicher. Es greift auf keinen produktiven Server und keine vorhandenen Kundendaten zu. Drei fiktive Projekttitel werden angelegt und über eine parametrisierte Abfrage gesucht. Das Ergebnis wird anschließend als HTML-Text ausgegeben. So lassen sich beide Grenzen in einem kleinen Ablauf prüfen, ohne daraus eine fertige Benutzerverwaltung oder eine allgemeine Sicherheitsfreigabe abzuleiten.
1. Zwei Auswertungsgrenzen auseinanderhalten
SQL-Injection betrifft die Bildung und Auswertung einer Datenbankanweisung. Wenn ein variabler Titel durch Zeichenkettenverkettung Teil des SQL-Texts wird, können Daten und Abfragesyntax ineinanderlaufen. Parametrisierte Abfragen halten für Werte die dafür vorgesehene Trennung aufrecht. Die tatsächliche Abfrageform bleibt unter Kontrolle der Anwendung. Welche Datensätze eine Person sehen darf, ist dagegen eine zusätzliche fachliche Entscheidung.
XSS betrifft eine andere Grenze. Eine Anwendung kann einen Wert korrekt aus der Datenbank lesen und ihn anschließend ungeeignet in ein Browserdokument einsetzen. Dann ist die Datenbankabfrage zwar sauber gebunden, die Ausgabe aber weiterhin problematisch. Für gewöhnlichen Text zwischen HTML-Elementen wird deshalb eine passende Ausgabekodierung verwendet. Datenbankparameter sind kein Ersatz dafür. Umgekehrt schützt HTML-Kodierung keine zusammengesetzte SQL-Abfrage.
Zeichne den konkreten Weg einer Information auf: Eingabe, fachliche Prüfung, Speicherung, Abfrage und Ausgabe. Markiere anschließend jede Stelle, an der eine andere Sprache oder Interpretation beginnt. Der Lernablauf hat zwei solche Übergänge. Ein reales Projekt kann weitere besitzen, etwa URLs, JavaScript, CSS oder E-Mail-Vorlagen. Die Lösung folgt dem jeweiligen Kontext. Eine Funktion mit einem allgemein klingenden Namen wie allesBereinigen verschleiert diese Unterschiede eher, als sie zu lösen.
2. Das Lernbeispiel bewusst begrenzen
Die Beispieldaten sind Entwurf O'Reilly, A & B und die wörtliche Zeichenfolge <em>Probe</em>. Sie besitzen absichtlich Zeichen, die in unterschiedlichen Sprachen eine Bedeutung haben können. Ein Apostroph gehört zu einem normalen Namen. Ein kaufmännisches Und gehört zu gewöhnlichen Titeln. Die sichtbare HTML-ähnliche Zeichenfolge ist ein harmloser Strukturprüfwert. Für das Lernziel ist kein Angriff auf eine fremde Anwendung erforderlich.
Die Datenbank existiert nur für die Laufzeit des Skripts. Bei jedem Aufruf wird derselbe kleine Bestand neu aufgebaut. Eine Suche nach Entwurf O'Reilly soll genau diesen einen Titel liefern. Eine Suche nach einem unbekannten Titel soll keine Treffer erzeugen. Die Auswahl verwendet einen exakten Vergleich und keine unscharfe Suchsyntax. Damit bleibt die Erwartung eindeutig und die Prüfung überschaubar.
PHP benötigt für dieses Beispiel die PDO-Erweiterung und den SQLite-Treiber. Die konkret ausgeführten Prüfungen verwenden PHP 8.4.27. Die Wahl des Treibers dient der isolierten Reproduzierbarkeit. Sie behauptet keine identische Konfiguration einer produktiven MySQL-Installation. Für eine Übernahme müssen Verbindungsoptionen, Datenmodell, Berechtigungen und fachliche Zugriffskontrolle in der tatsächlichen Umgebung gesondert geklärt werden.
3. Werte binden und die SQL-Struktur festlegen
Die Abfrage enthält einen Platzhalter für den gesuchten Titel. Der SQL-Text bleibt im Programm festgelegt. Der Wert wird erst beim Ausführen getrennt übergeben. Die PHP-Dokumentation zu PDO::prepare beschreibt Platzhalter und deren Grenzen. Sie stehen für Datenwerte. Tabellenbezeichnungen, Spaltennamen oder eine vollständige Sortieranweisung werden damit nicht beliebig austauschbar.
Wenn eine Oberfläche eine Sortierung anbietet, entscheidet die Anwendung deshalb über eine begrenzte Zuordnung. Ein erlaubter Auswahlwert kann einer fest hinterlegten Spalte entsprechen. Ein unbekannter Wert wird abgelehnt oder erhält eine definierte Voreinstellung. Die Anwendung übernimmt keinen beliebigen Spaltennamen aus einer Anfrage. Diese Entscheidung gehört zur kontrollierten Abfragestruktur und wird unabhängig von der Bindung der übrigen Werte geprüft.
Auch ein vorbereiteter Abfrageaufruf hilft nicht, wenn variable Daten vorher bereits in den SQL-Text hineinkopiert wurden. Suche bei der Prüfung daher nach dem tatsächlichen Aufbau und nicht nur nach dem Wort prepare. Der OWASP-Leitfaden zur Vermeidung von SQL-Injection ordnet parametrisierte Abfragen als zentrale Maßnahme ein. Für das Lernbeispiel ist entscheidend, dass sowohl das Anlegen der Beispielwerte als auch ihre Suche diese Trennung verwenden.
4. Den vollständigen PHP-Ablauf nachvollziehen
Das folgende Skript kann als PHP-Datei über die Kommandozeile ausgeführt werden. Das erste Argument bestimmt den gesuchten Titel. Ohne Argument wird Entwurf O'Reilly verwendet. Die Ausgabe ist ein kleines HTML-Dokument. Seine Treffer stehen als normale Texte in Listeneinträgen. Die Funktion htmlText ist ausdrücklich für diesen HTML-Textkontext benannt.
<?php
declare(strict_types=1);
function htmlText(string $value): string {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE projects (id INTEGER PRIMARY KEY, title TEXT NOT NULL)');
$insert = $db->prepare('INSERT INTO projects (title) VALUES (:title)');
foreach (["Entwurf O'Reilly", 'A & B', '<em>Probe</em>'] as $title) {
$insert->execute(['title' => $title]);
}
$name = $argv[1] ?? "Entwurf O'Reilly";
$query = $db->prepare('SELECT id, title FROM projects WHERE title = :title');
$query->execute(['title' => $name]);
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
echo '<!doctype html><html lang="de"><meta charset="utf-8">';
echo '<title>Isoliertes Lernbeispiel</title><ul id="results">';
foreach ($rows as $row) {
echo '<li>' . htmlText($row['title']) . '</li>';
}
echo '</ul></html>';
Die feste CREATE-TABLE-Anweisung enthält ausschließlich den im Skript bestimmten Aufbau. Die variablen Titel werden anschließend gebunden. Die Suche und die Ausgabe sind getrennte Schritte. Dadurch lässt sich bei einem Fehler klären, ob ein falscher Datensatz ausgewählt wurde oder ein richtiger Datensatz im Browser eine falsche Bedeutung erhielt. Diese Unterscheidung ist für die Fehlersuche wichtiger als eine pauschale Meldung, die Eingabe sei unsicher.
5. HTML-Text erst an der Ausgabegrenze kodieren
Die PHP-Referenz zu htmlspecialchars erläutert die Umwandlung bestimmter HTML-relevanter Zeichen. Im Beispiel sind Zeichensatz und Optionen ausdrücklich angegeben. Die Zeichenfolge mit em-Markierungen soll sichtbar bleiben und kein zusätzliches em-Element erzeugen. Bei einem Apostroph oder kaufmännischen Und bleibt die beabsichtigte lesbare Information ebenfalls erhalten.
Speichere deshalb nicht vorsorglich die für eine einzelne HTML-Ausgabe kodierte Fassung als allgemeines Datenmodell. Derselbe Titel kann später in einer Textdatei oder einer anderen Schnittstelle gebraucht werden. Eine früh festgeschriebene Darstellung erschwert diese Wiederverwendung und kann zu doppelter Kodierung führen. Bewahre den fachlich geprüften Wert und wandle ihn passend zur konkreten Ausgabe um. Das ist eine Architekturentscheidung mit klarer Zuständigkeit.
Die Funktion heißt hier nicht safeString, weil ihr Ergebnis nicht für jeden Kontext geeignet ist. Wird ein Wert in JavaScript, CSS oder eine URL eingesetzt, muss die dortige Behandlung gesondert geplant werden. Auch bei HTML-Attributen zählt die konkrete Art des Attributs. Eine URL wird durch das Kodieren ihrer Anführungszeichen nicht automatisch zu einem erlaubten Ziel. Der OWASP-Leitfaden zur XSS-Vermeidung erklärt die Bedeutung dieser Kontexttrennung.
6. JavaScript-Textausgaben als eigenen Fall behandeln
Wenn eine Anwendung einen gewöhnlichen Titel im DOM aktualisiert, ist eine Textschnittstelle die passende Ausgangsbasis. Die MDN-Dokumentation zu textContent beschreibt die Eigenschaft. Eine Zuweisung wie element.textContent = title behandelt den Wert als Text. Das ist für den Projekttitel etwas anderes als das bewusste Verarbeiten eines HTML-Fragments.
Diese Entscheidung sollte sich in der Komponentenschnittstelle zeigen. Ein Parameter title ist ein Textwert. Ein bewusst zugelassenes redaktionelles HTML-Fragment braucht eine andere Behandlung und einen klaren Vertrauensrahmen. Wird beides unter einem einzigen uneindeutigen Inhaltsparameter zusammengeführt, wächst die Gefahr einer späteren falschen Verwendung. Ein Typ oder eine eindeutig benannte Schnittstelle kann die beabsichtigte Unterscheidung unterstützen.
Ein Wechsel zur Textausgabe ersetzt keine Prüfung aller DOM-Pfade. Eine Komponente kann ihren Haupttitel korrekt setzen und einen Zusatzhinweis weiterhin anders einsetzen. Erfasse deshalb die konkreten Ausgabestellen. Das Lernbeispiel prüft die sichtbaren Listeneinträge. Für eine echte interaktive Projektliste müssten zusätzliche Elemente und Zustandswechsel berücksichtigt werden. Aus einem erfolgreich geprüften Textfeld folgt keine allgemeine Aussage über die gesamte Oberfläche.
7. Fachliche Validierung von technischer Trennung unterscheiden
Eine sichere Ausgabetechnik entscheidet nicht, welche Projektnamen ein Betrieb erlauben möchte. Länge, Pflichtangaben und fachliche Regeln werden an geeigneter Stelle geprüft. Ein leerer Titel kann fachlich unzulässig sein, obwohl seine kodierte Ausgabe keine HTML-Struktur erzeugt. Ein sehr langer Wert kann die Gestaltung oder Verarbeitung belasten. Diese Fragen verdienen ausdrückliche Regeln statt einer Vermischung mit der Ausgabekodierung.
Umgekehrt ist ein Apostroph kein sinnvoller Grund, einen Namen pauschal abzulehnen. Das Lernbeispiel verwendet ihn gerade als normalen Bestandteil eines fiktiven Titels. Ein Verbot bestimmter Wörter oder Zeichen kann die zugrunde liegende Trennungsaufgabe nicht zuverlässig ersetzen. Regeln sollen die tatsächliche Datenanforderung ausdrücken. Die Datenbankabfrage bleibt auch für erlaubte Werte parametrisiert.
Formuliere Fehlermeldungen so, dass sie eine Korrektur ermöglichen. Eine Meldung zur maximalen Titellänge benennt die fachliche Grenze. Sie muss keine Datenbankdetails offenlegen. Für Entwickler bleiben die zugehörigen Prüfregeln dokumentiert. So wird erkennbar, ob ein Test die fachliche Eingabegrenze oder die technische Behandlung eines Werts untersucht. Beide können nötig sein, aber sie beantworten verschiedene Fragen.
8. Rechte und Datenauswahl zusätzlich begrenzen
Das Skript besitzt absichtlich keine Anmeldung und keine Kundenzuordnung. Es arbeitet nur mit seinem eigenen fiktiven Speicherbestand. Eine produktive Projektverwaltung benötigt dagegen eine Entscheidung darüber, wer welchen Eintrag sehen oder verändern darf. Eine korrekt parametrisierte Suche beantwortet diese Berechtigungsfrage nicht. Sie kann eine fachlich zu weit gefasste Auswahl zuverlässig ausführen und trotzdem vertrauliche Datensätze zurückgeben.
Plane deshalb den Datenzugriff als eigene Anforderung. Eine Projektkennung aus einer Anfrage ist kein Berechtigungsnachweis. Die Anwendung muss die zulässige Beziehung zwischen Benutzer und Datensatz feststellen. Welche zusätzliche Bedingung dafür in eine Abfrage gehört, hängt vom tatsächlichen Modell ab. Das Lernbeispiel erfindet dafür bewusst keine universelle Mandantenverwaltung. Die Übernahme eines einzelnen Abfragemusters darf diese Lücke nicht verdecken.
Auch die technischen Rechte des Datenbankkontos sollten zur Aufgabe passen. Ein lesender Vorgang braucht nicht automatisch beliebige Schemaänderungen. Die konkrete Konfiguration wird im jeweiligen Datenbanksystem geprüft. Im Speicherbeispiel werden Aufbau und Daten absichtlich innerhalb eines Prozesses erzeugt. Diese Vereinfachung ist ein Lernmittel und kein Vorschlag für die Rechteverteilung auf einem Produktionsserver.
9. Mit normalen und ungewöhnlichen Werten prüfen
| Prüfwert | Erwartetes Abfrageergebnis | Erwartete Browserausgabe |
|---|---|---|
| Entwurf O'Reilly | Genau der passende Eintrag | Apostroph als normaler Text |
| A & B | Genau der passende Eintrag | Kaufmännisches Und sichtbar |
| <em>Probe</em> | Genau der passende Eintrag | Wörtliche Markierungen, kein em-Kindelement |
| Unbekannter Titel | Kein Eintrag | Leere Ergebnisliste |
Diese Erwartungen werden an der tatsächlichen Ausgabe geprüft. Ein bloßer Vergleich des PHP-Quelltexts reicht nicht. Für jede Suche wird das Skript ausgeführt. Das entstandene Dokument wird im Browser geladen. Die Liste muss die erwartete Anzahl von Einträgen enthalten. Bei vorhandenen Treffern muss deren Text genau dem erwarteten Titel entsprechen und darf keine unbeabsichtigten Kindelemente enthalten.
Ergänzend sind leere Eingaben und nicht exakt übereinstimmende Zeichenfolgen sinnvoll. Die Beispielsuchfunktion behauptet keine Ähnlichkeitssuche. Eine Abweichung soll deshalb nicht plötzlich zusätzliche Datensätze liefern. Die Tests dokumentieren diese konkrete Funktion. Sie sind kein allgemeiner Nachweis, dass jede Variante einer später erweiterten Suche korrekt bleibt. Sobald etwa Platzhaltersuche oder Sortierung hinzukommen, werden die Erwartungen entsprechend erweitert.

10. Die Prüfung gegen einen bekannten Fehler absichern
Eine Prüfung ist aussagekräftiger, wenn sie den gemeinten Fehler auch tatsächlich erkennen kann. In einer isolierten fehlerhaften Variante wird deshalb die HTML-Textkodierung für den harmlosen em-Prüfwert weggelassen. Der Browser erzeugt dann ein em-Kindelement. Die Strukturprüfung muss diese Variante ablehnen. Die fehlerhafte Fassung wird nicht als nutzbares Produktionsbeispiel bereitgestellt und greift auf keine fremden Daten zu.
Diese Gegenprobe untersucht die Ausgabeseite. Auf der Abfrageseite belegen mehrere exakte Suchen, dass jeweils nur der erwartete fiktive Datensatz zurückkommt. Ein unbekannter Titel liefert keinen Treffer. Die Verbindung dieser Beobachtungen ist stärker als die Aussage, alle Eingaben seien bereinigt. Sie beschreibt, was tatsächlich ausgeführt und an welcher Grenze kontrolliert wurde.
Halte Laufzeit, Treiber und Testwerte im Prüfprotokoll fest. Wenn ein späterer Treiber oder eine andere Datenbank verwendet wird, kann das Team die relevanten Fälle gezielt wiederholen. Es muss keine produktive Datenbank kopiert werden, um die grundlegende Trennung zu untersuchen. Für Funktionen mit zusätzlichen fachlichen Bedingungen wird ein eigener geeigneter Testbestand benötigt.
11. Fehlerbehandlung und Protokolle sinnvoll gestalten
Im Lernskript werden Datenbankfehler als Ausnahmen behandelt. In einer produktiven Oberfläche braucht die Anwendung einen kontrollierten Fehlerweg. Besucher sollten weder Zugangsdaten noch rohe Abfrageinformationen sehen. Eine allgemeine Fehlermeldung kann den Vorgang verständlich abbrechen. Die interne Diagnose muss zugleich genügend Kontext enthalten, um den tatsächlichen Fehler untersuchen zu können.
Protokollierung ist kein Anlass, sämtliche Eingaben ungeprüft dauerhaft zu sammeln. Lege fest, welche Information für die Diagnose erforderlich ist und wie sie geschützt wird. Besonders bei personenbezogenen oder vertraulichen Inhalten muss der Umfang begründet werden. Für den isolierten Lerntest werden nur fiktive Werte und technische Prüfergebnisse gespeichert. Es gibt keine echten Ansprechpartner oder Projektinhalte.
Beachte außerdem, dass Protokolldaten später selbst angezeigt werden können. Ein ursprünglich kontrolliert gebundener Wert wird nicht vertrauenswürdig, nur weil er in einer Logdatei steht. Auch die Verwaltungsansicht braucht kontextgerechte Ausgabe. Dieser Rückweg wird leicht übersehen, wenn die Prüfung nur den öffentlich sichtbaren Eingabedialog betrachtet.
12. Eine fokussierte Prüfliste für die Übernahme verwenden
- Für jeden variablen Wert die fachliche Bedeutung und den Weg durch die Anwendung dokumentieren.
- SQL-Text und gebundene Datenwerte getrennt halten.
- Dynamische Bezeichner über eine ausdrücklich erlaubte feste Zuordnung bestimmen.
- HTML-Text unmittelbar an der Ausgabe passend kodieren.
- Andere Ausgabekontexte gesondert beurteilen.
- Anmeldung, Berechtigung und Datenauswahl unabhängig prüfen.
- Gewöhnliche Sonderzeichen nicht als Ersatz für die technische Trennung verbieten.
- Erwartete Treffer und tatsächliche Browserstruktur mit fiktiven Testdaten kontrollieren.
- Mindestens eine bekannte Fehlervariante durch die Prüfung erkennen lassen.
- Fehlerwege und spätere Verwaltungsanzeigen einbeziehen.
Für die Codeprüfung lohnt sich eine klare Rückmeldungssprache. Statt Eingabe unsicher wird die konkrete Stelle benannt: Der Titel wird vor dem Binden in den SQL-Text kopiert. Oder: Die Ergebnisanzeige interpretiert einen gewöhnlichen Textwert als HTML. Eine solche Feststellung führt zu einer gezielten Änderung und einem passenden Test. Sie verhindert, dass eine zusätzliche Filterfunktion an der falschen Stelle eingebaut wird.
Nach einer Änderung wird der betroffene Weg vollständig wiederholt. Die richtige Datenbankabfrage und die richtige Ausgabe gehören gemeinsam zum Lernablauf. Wird später eine weitere Anzeige ergänzt, darf sie nicht ungeprüft aus dem Ergebnis der ersten Anzeige ableiten, ebenfalls abgesichert zu sein. Sicherheit entsteht durch nachvollziehbare Entscheidungen an jeder tatsächlichen Grenze und durch die Pflege dieser Entscheidungen.
13. Suchfunktionen nicht unbemerkt erweitern
Eine spätere Änderung von exaktem Vergleich zu Mustersuche ist eine fachliche Änderung. Die SQLite-Dokumentation zu LIKE erläutert die Musterzeichen Prozent und Unterstrich. Ein gebundener Parameter ist weiterhin ein Datenwert, kann innerhalb einer solchen Suchfunktion aber bewusst eine Musterbedeutung besitzen. Parameterbindung und die Entscheidung, ob Besucher Musterzeichen verwenden dürfen, sind deshalb verschiedene Fragen. Das hier geprüfte Skript verwendet ausdrücklich keine LIKE-Suche.
Wenn eine Projektliste später eine Teilwortsuche erhalten soll, wird zuerst die gewünschte Bedeutung beschrieben. Soll eine Suche nach einem Teil des Namens alle entsprechenden Projekte zeigen? Sollen Prozentzeichen als gewöhnlicher Titelbestandteil gesucht werden? Wie wird ein leeres Suchfeld behandelt? Welche maximalen Ergebnismengen sind sinnvoll? Erst mit diesen Antworten lässt sich festlegen, welche Fälle die neue Funktion prüfen muss. Die vorhandenen Tests für exakte Titel werden dadurch nicht wertlos. Sie dokumentieren den bisherigen Vertrag und machen sichtbar, welche Erwartungen sich ändern.
Dasselbe gilt für eine ergänzte Ergebnisdarstellung. Ein Suchwort im Treffer fett hervorzuheben verlangt eine bewusste Textaufteilung. Es ist kein Grund, den gesamten gespeicherten Titel plötzlich als HTML zu interpretieren. Plane die Hervorhebung als eigenständige Darstellung mit kontrollierten Elementen und Textwerten. Die zugrunde liegenden Daten behalten ihre Bedeutung. Bei der Abnahme wird geprüft, ob derselbe Titel vollständig lesbar bleibt und ob zusätzliche Elemente ausschließlich aus der vorgesehenen Darstellung entstehen.
Für die Übergabe ist eine kurze Änderungsnotiz hilfreich: Welche Suchbedeutung wurde geändert, welche neuen Ausgabestellen sind entstanden und welche Erwartungen gelten weiterhin? Damit kann eine andere Person die Anpassung nachvollziehen, ohne die ganze Anwendung neu zu erraten. Die Notiz verweist auf konkrete Prüfungen. Sie ersetzt diese nicht. Gerade kleine Erweiterungen brauchen diese Klarheit, weil sie sonst leicht als bloße gestalterische Verbesserung behandelt werden.
Quellen und fachlicher Stand
Quellenstand: 9. November 2026. Die verlinkten OWASP-, PHP- und MDN-Primärquellen wurden für die jeweils benannten Grundlagen aufgerufen. Datenbestand, Ablauf und Prüferwartungen sind eigene isolierte Lernmaterialien. Es werden keine fremden Anwendungen getestet und keine umfassende Sicherheitsfreigabe, Zertifizierung oder reale Projektergebnisse behauptet.







