Was ist SQL-Injection und wie verhinderst du sie?
Eine praktische Übersicht von SQL-Injection-Angriffen, echten Beispielen und den konkreten Lösungen, die sie wirklich in Produktionscode stoppen.
SQL-Injection gibt es seit dem späten 1990er Jahren und ist immer noch einer der häufigsten Wege, wie Web-Anwendungen kompromittiert werden. Der Bug ist einfach: Ein Angreifer sendet Input, der als SQL-Code statt als einfache Daten interpretiert wird, und die Datenbank macht etwas, das sie nie hätte tun sollen.
Wie der Angriff tatsächlich funktioniert
Stell dir ein Login-Formular vor, das eine Query wie diese in PHP erstellt:
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
Wenn die App die Eingabe nicht bereinigt, tippt ein Angreifer admin' -- ins Feld für den Benutzernamen. Die Query wird zu:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
Die -- kommentiert den Rest der Zeile aus, also findet die Passwort-Überprüfung nie statt. Das ist ein klassischer Auth-Bypass. Angreifer nutzen auch UNION SELECT, um Daten aus anderen Tabellen zu ziehen, oder stapeln Queries mit Semikolon, um etwas Destruktives wie DROP TABLE users; auszuführen, falls der Driver mehrere Statements erlaubt.
Es gibt auch Blind SQL Injection, wo die App Query-Ergebnisse nicht direkt zeigt. Angreifer leiten Informationen durch Timing ab (SLEEP(5) in MySQL) oder Boolean-Responses (verhält sich die Seite anders bei einer wahren vs. falschen Bedingung). Tools wie sqlmap automatisieren diese Art der Extraktion, sobald ein anfälliger Parameter gefunden ist.
Warum String-Verkettung das Kernproblem ist
Jede Variante dieses Angriffs kommt auf eine Sache zurück: Code und Daten in demselben String zu vermischen. Die Datenbank kann keinen Unterschied zwischen einem legitimen Wert und injizierter Syntax machen, weil sie über denselben Kanal ankommen. Das Escapen von Anführungszeichen hilft in einigen Fällen, ist aber anfällig — unterschiedliche Encodings, Second-Order-Injection (Daten einmal gespeichert, dann später unsicher wiederverwendet) und Driver-spezifische Besonderheiten schaffen alle Bypass-Wege. Escaping ist ein Patch, keine Lösung.
Parametrisierte Queries sind die echte Lösung
Die Lösung ist, SQL-Code und Benutzerdaten auf Driver-Ebene zu trennen, indem man parametrisierte Queries (auch Prepared Statements genannt) verwendet. Die Datenbank erhält zuerst die Query-Struktur, bindet dann Werte danach, sodass Benutzereingaben die Bedeutung der Query nie verändern können.
In Python mit psycopg2:
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
In Node.js mit mysql2:
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
In Java mit JDBC:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
Bemerke das Muster: Platzhalter (%s, ?) halten den Platz für Daten, und die eigentlichen Werte werden separat übergeben. Keine String-Verkettung, kein manuelles Escaping nötig. Das funktioniert für die große Mehrheit der Queries, die du in einer normalen Anwendung schreiben wirst.
Was ist mit dynamischen Tabellen- oder Spaltennamen?
Parametrisierung verarbeitet Werte, aber keine Bezeichner — du kannst einen Tabellennamen nicht als Parameter binden. Falls deine App eine Tabelle dynamisch auswählen muss (selten, und oft ein Design-Geruch), liste die erlaubten Werte gegen eine hardcodierte Liste auf, statt Benutzereingaben direkt zu vertrauen:
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
Erstelle Bezeichnernamen nie durch String-Formatierung von Benutzereingaben, auch nicht mit Escaping.
Mehrschichtige Verteidigungen über die Query hinaus
Parametrisierte Queries sind die primäre Kontrolle, aber ein paar andere Dinge zählen:
- Least Privilege beim Datenbank-Konto. Der DB-User der App sollte keine
DROP-,ALTER- oder Zugriff auf unverwandte Schemas haben. Falls eine Injection doch durchrutscht, begrenzen Limited Privileges den Schaden. - ORMs helfen standardmäßig. Djangos ORM, SQLAlchemy und Hibernate parametrisieren Queries automatisch, wenn du ihre Standard-Query-Building-Methoden nutzt. Das Risiko kehrt zurück, wenn Entwickler in Raw SQL fallen oder
.extra()/text()-Aufrufe mit String-Interpolation nutzen — also überprüfe diese Stellen gezielt. - Input-Validierung ist eine sekundäre Schicht, kein Ersatz. Zu überprüfen, dass ein E-Mail-Feld wie eine E-Mail aussieht, ist gute Praxis, stoppt aber Injection nicht allein — Angreifer finden kreative Payloads, die trotzdem lockere Validierung passieren.
- Eine WAF kann bekannte Angriffsmuster fangen, aber das ist eine Erkennungsebene, keine Lösung für den zugrunde liegenden Code.
Deinen eigenen Code auf diesen Bug testen
Führe deine Queries durch ein Static-Analysis-Tool (Bandit für Python, Semgrep mit SQL-Injection-Rulesets) als Teil von CI aus. Zum manuellen Testen versuche, ein einzelnes Anführungszeichen (') in jedes Eingabefeld zu injizieren und beobachte SQL-Fehlermeldungen, die in der Response durchsickern — das ist oft das erste Zeichen, dass eine Query nicht parametrisiert ist.
Falls du tiefer gehen möchtest, deckt Korra Studios Web Security Track Injection zusammen mit XSS und Auth-Bypass ab, und die Databases-Segmente gehen durch Query-Design-Muster, die diese Bug-Klasse ganz vermeiden.
Mit KI-Unterstützung geschrieben, von Michal Pilch (CISSP), Korra Studio, überprüft und veröffentlicht.
Das ist eine Notiz aus der Korra-Studio-Wissensdatenbank — die Plattform verbindet jedes Thema mit 1-zu-1-Mentoring.
Kostenlos startenarrow_forward