🌐 Dieser Artikel ist auch verfügbar auf: English

SQL Injection: Beispiele, Arten und Schutz erklärt

SQL Injection Grundlagen: Angriffsarten und Schutz durch parametrisierte Abfragen

SQL Injection (kurz SQLi) gehört zu den ältesten und zugleich hartnäckigsten Schwachstellen in Webanwendungen. Bei einem solchen Angriff schleust ein Angreifer über normale Eingabefelder, URL-Parameter oder HTTP-Header eigenen SQL-Code in eine Datenbankabfrage ein. Fehlt eine saubere Trennung zwischen Programmcode und Benutzereingabe, interpretiert die Datenbank diese Eingabe als Befehl statt als reinen Datenwert. Das Ergebnis reicht vom Umgehen der Anmeldung über das Auslesen kompletter Datenbanken bis hin zur vollständigen Übernahme des Servers.

Dieser Artikel erklärt die Grundlagen sauber und vollständig: Was SQL Injection technisch genau ist, wie ein Angriff abläuft, welche Angriffstypen es gibt und, als Herzstück, wie Sie sich mit parametrisierten Abfragen wirksam schützen, inklusive praxisnaher Codebeispiele (Ausschnitte) in mehreren Programmiersprachen. Für die tiefergehenden Exploitation- und Filter-Bypass-Techniken finden Sie am Ende einen Verweis auf unseren Artikel zu fortgeschrittener SQL Injection.

Wenn Sie einen Überblick über die gesamte Bedrohungslandschaft für Webanwendungen suchen, hilft Ihnen zusätzlich unsere Übersicht zu den OWASP Top 10.

Was ist eine SQL Injection?

SQL Injection ist die klassische Schwachstelle aus der Kategorie der Injection-Angriffe. Die formale Definition liefert die MITRE-Klassifikation CWE-89 (Improper Neutralization of Special Elements used in an SQL Command). Sinngemäß bedeutet das: Eine Anwendung baut eine SQL-Abfrage ganz oder teilweise aus extern beeinflussbaren Eingaben zusammen, neutralisiert dabei aber die SQL-Sonderzeichen nicht oder nur unzureichend, die die eigentlich beabsichtigte Abfrage verändern können.

Der Kern des Problems liegt in der fehlenden Trennung von Code und Daten. Eine SQL-Abfrage besteht aus einem festen Befehlsgerüst (etwa SELECT ... FROM ... WHERE ...) und variablen Werten (etwa einem eingegebenen Benutzernamen). Werden diese Werte einfach als Zeichenketten in den Befehl hineinkopiert (String-Konkatenation), kann ein Angreifer Zeichen einschmuggeln, die aus dem Datenbereich ausbrechen und die Struktur des Befehls verändern. Genau das ist eine SQL Injection.

Die Auswirkungen sind laut MITRE breit gefächert:

  • Vertraulichkeit: Da SQL-Datenbanken in der Regel sensible Daten enthalten, ist der Verlust der Vertraulichkeit ein häufiges Problem (Auslesen von Kundendaten, Passwort-Hashes, Zahlungsinformationen).
  • Integrität: Angreifer können Daten verändern oder löschen.
  • Authentifizierung: Angreifer können sich als anderer Benutzer anmelden, ohne dessen Passwort zu kennen.
  • Codeausführung: Im schlimmsten Fall, etwa über gefährliche Datenbankfunktionen wie xp_cmdshell beim Microsoft SQL Server, kann eine SQL Injection sogar zur Ausführung von Betriebssystembefehlen eskalieren.

Einordnung in die OWASP Top 10 und CWE

SQL Injection ist kein exotisches Nischenproblem, sondern seit Jahren fester Bestandteil der wichtigsten Sicherheitsstandards. Die Einordnung sollte editionsscharf erfolgen, denn die OWASP Top 10 haben ihre Nummerierung zwischen den Ausgaben verändert:

  • OWASP Top 10:2021: SQL Injection fällt unter die Kategorie A03:2021 Injection. CWE-89 ist auf der offiziellen Kategorieseite ausdrücklich als gemappte Schwachstelle gelistet.
  • OWASP Top 10:2025: In der Ausgabe 2025 ist die Injection-Kategorie auf A05:2025 gewandert (also zwei Plätze nach unten). CWE-89 bleibt auch hier ausdrücklich unter den gemappten CWEs gelistet.

Wichtig ist die begriffliche Genauigkeit: Die OWASP-Kategorie „Injection" ist breiter als SQL Injection allein. Sie umfasst unter anderem auch NoSQL-, OS-Command-, ORM-, LDAP- und Expression-Language-Injection. SQL Injection (CWE-89) ist ein spezifischer, prominenter Vertreter dieser Familie, nicht mit der gesamten Kategorie gleichzusetzen.

Der Bedeutungswandel lässt sich nüchtern beschreiben: Injection-Schwachstellen zählen seit Erscheinen der ersten modernen OWASP-Top-10-Listen durchgehend zu den Top-5-Risiken. Der relative Rang ist zwar langsam gesunken, weil andere Kategorien schneller an Bedeutung gewannen, aber allein für die Injection-Kategorie weist OWASP in der Ausgabe 2025 über 62.000 CVEs aus, davon nach eigener Angabe mehr als 14.000 CVEs speziell für SQL Injection. Das unterstreicht: Die Schwachstelle ist alt, aber keineswegs erledigt.

Wie funktioniert eine SQL Injection?

Am anschaulichsten wird das Prinzip an einem Anmeldeformular. Eine unsichere Anwendung könnte die eingegebenen Werte direkt in die Abfrage einsetzen:

SELECT * FROM Users WHERE Username = 'EingegebenerNutzer' AND Password = 'EingegebenesPasswort';

Gibt ein Angreifer als Benutzernamen nun folgende Zeichenkette ein:

' OR '1'='1'; --

dann verändert sich die zusammengesetzte Abfrage zu:

SELECT * FROM Users WHERE Username = '' OR '1'='1'; --' AND Password = 'EingegebenesPasswort';

Zwei Dinge passieren hier: Die Bedingung '1'='1' ist immer wahr, und der doppelte Bindestrich -- leitet in SQL einen Kommentar ein, wodurch der gesamte Rest der Abfrage (die Passwortprüfung) ignoriert wird. Die Datenbank liefert damit den ersten Benutzerdatensatz zurück, ganz ohne gültiges Passwort. Die Anmeldung ist umgangen.

Ein zweites, ebenso klassisches Muster stammt aus dem PortSwigger-Lehrmaterial. Eine Produktseite filtert Waren nach Kategorie:

https://insecure-website.com/products?category=Gifts'-- 

Daraus wird serverseitig:

SELECT * FROM products WHERE category = 'Gifts'-- ' AND released = 1

Der auskommentierte Teil AND released = 1 fällt weg, sodass auch noch nicht freigegebene Produkte angezeigt werden. Beachten Sie, dass in MySQL auf den Kommentar -- ein Leerzeichen folgen muss, damit er greift, weshalb die Payload mit einem abschließenden Leerzeichen endet. Beide Beispiele zeigen dasselbe Grundmuster: Ein einzelnes Anführungszeichen bricht aus dem Datenbereich aus, der Rest ist Kreativität des Angreifers.

Die Arten von SQL Injection im Überblick

SQL Injection ist kein einheitlicher Angriff, sondern eine ganze Familie von Techniken. Sie unterscheiden sich vor allem darin, wie der Angreifer die Antwort der Datenbank zurückerhält. Die folgende Typologie orientiert sich an den offiziellen Definitionen von OWASP und PortSwigger.

In-Band SQL Injection

Bei der In-Band-Variante nutzt der Angreifer denselben Kommunikationskanal für Angriff und Ergebnis, die Daten kommen also direkt in der Antwort der Anwendung zurück. Das ist die einfachste und häufigste Form.

Error-based (fehlerbasiert): Der Angreifer provoziert absichtlich Datenbankfehler, deren ausführliche Meldungen Rückschlüsse auf Struktur, Tabellen- oder Spaltennamen erlauben. Verräterische Fehlermeldungen im Frontend sind hier die Informationsquelle.

UNION-based: Über das Schlüsselwort UNION hängt der Angreifer die Ergebnisse einer zweiten, selbst kontrollierten Abfrage an das ursprüngliche Ergebnis an. Voraussetzung ist, dass die eingeschleuste Abfrage dieselbe Spaltenanzahl und kompatible Datentypen liefert. Ein kanonisches Beispiel:

' UNION SELECT username, password FROM users--

So lassen sich Anmeldedaten aus einer ganz anderen Tabelle in die sichtbare Ergebnisliste einschleusen.

Blind SQL Injection

Bei der blinden Variante gibt die Anwendung weder die Abfrageergebnisse noch ausführliche Fehlermeldungen direkt aus. Der Angreifer muss die Datenbank stattdessen mit Wahr-oder-Falsch-Fragen ausfragen und die Antwort aus dem Verhalten der Anwendung ableiten.

Boolean-based (inhaltsbasiert): Der Angreifer hängt eine Bedingung an und beobachtet, ob sich der Seiteninhalt ändert. Der klassische Kontrast:

and 1=1
and 1=2

1=1 ist immer wahr und liefert die normale Seite, 1=2 ist immer falsch und liefert eine abweichende Ausgabe. Über systematische Abfragen einzelner Zeichen (etwa mit SUBSTRING) lässt sich so Zeichen für Zeichen ein Passwort-Hash extrahieren.

Time-based (zeitbasiert): Wenn selbst der Inhalt keinen Unterschied verrät, nutzt der Angreifer datenbankspezifische Verzögerungsfunktionen. Eine messbar längere Antwortzeit signalisiert, dass die Bedingung wahr war. Beispiele:

' waitfor delay '00:00:10'--

(Microsoft SQL Server; MySQL nutzt SLEEP(), PostgreSQL pg_sleep().) Weil pro extrahiertem Zeichen eine eigene Anfrage nötig ist, ist blinde SQL Injection von Hand sehr zeitaufwendig und wird üblicherweise mit Werkzeugen wie SQLMap automatisiert.

Out-of-Band SQL Injection

Kommt weder über den Inhalt noch über das Timing eine Antwort zurück, kann der Angreifer die Datenbank dazu bringen, über einen zweiten Kanal Kontakt aufzunehmen, etwa per DNS- oder HTTP-Anfrage an einen von ihm kontrollierten Server. Diese Out-of-Band-Technik ist besonders bei streng asynchron verarbeiteten Anfragen relevant.

Second-Order (Stored) SQL Injection

Die tückischste Variante: Der schädliche Wert wird zunächst völlig harmlos gespeichert und erst später, an einer ganz anderen Stelle der Anwendung, unsicher in eine neue Abfrage eingebaut. PortSwigger definiert das so: „Second-order SQL injection arises when user-supplied data is stored by the application and later incorporated into SQL queries in an unsafe way."

Ein typisches Muster: Ein Angreifer registriert einen Benutzernamen, der SQL-Syntax enthält. Beim Speichern wird der Wert über eine parametrisierte Abfrage korrekt als harmloser Text abgelegt. Später liest eine Reporting- oder Admin-Funktion diesen Wert wieder aus und fügt ihn per String-Konkatenation in eine neue Abfrage ein, wo er dann als SQL wirkt. Die entscheidende Lehre: Es genügt nicht, nur die schreibende Abfrage zu parametrisieren. Jede Abfrage, die diese Daten anfasst, an jeder Stelle der Anwendung, muss parametrisiert sein.

Wie diese Typen in der Praxis kombiniert und gegen Filter durchgesetzt werden, inklusive konkreter Bypass-Tricks, behandeln wir vertieft im Artikel zu fortgeschrittener SQL Injection.

Schutz mit Prepared Statements: der wirksamste Baustein

Der mit Abstand wichtigste Schutz gegen SQL Injection sind parametrisierte Abfragen, auch Prepared Statements genannt. Das OWASP-Cheat-Sheet listet sie als „Primary Defense Option 1", noch vor allen anderen Maßnahmen. Das Prinzip: Der Entwickler definiert zuerst das komplette SQL-Befehlsgerüst mit Platzhaltern und übergibt die Benutzerwerte erst danach separat. Dadurch unterscheidet die Datenbank immer zwischen Code und Daten, egal welche Eingabe kommt.

Entscheidend ist das Verständnis für das unsichere Gegenmuster. Überall dort, wo Eingaben per String-Konkatenation in eine Abfrage eingebaut werden, entsteht die Lücke. Die folgenden Beispiele zeigen daher jeweils zuerst das verwundbare Anti-Pattern und danach die sichere, parametrisierte Variante, wörtlich an den offiziellen Dokumentationen der jeweiligen Sprache orientiert.

PHP mit PDO

<?php
// UNSICHER: Benutzereingabe direkt in die Abfrage konkateniert
$sql = "SELECT * FROM users WHERE email = '" . $_GET['email'] . "'";
$stmt = $dbh->query($sql);

Sicher mit benannten Platzhaltern (:calories, :colour), deren Werte als Array an execute() übergeben werden:

<?php
/* Prepared Statement, Werte als Array übergeben */
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour';
$sth = $dbh->prepare($sql, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
$sth->execute(['calories' => 150, 'colour' => 'red']);
$red = $sth->fetchAll();
?>

Als ergänzenden Hinweis empfiehlt Sekundärliteratur, PDO::ATTR_EMULATE_PREPARES auf false zu setzen, damit die Trennung von Struktur und Daten auf Protokollebene erfolgt statt clientseitig emuliert zu werden. Das ist ein Feinschliff, den Sie bei Bedarf gegen das PDO-Handbuch gegenprüfen sollten; gegen Injection schützt bereits die parametrisierte Übergabe selbst.

PHP mit mysqli

Statt der oft empfohlenen Escaping-Funktion mysqli_real_escape_string() (die nur maskiert, aber kein echtes Prepared Statement ist) sollten Sie prepare() und bind_param() nutzen. Der Typ-String 'sssd' deklariert jeden Parameter (s = String, d = Double) und ergänzt eine zusätzliche Typsicherheit:

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');

$stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
$stmt->bind_param('sssd', $code, $language, $official, $percent);

$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;

$stmt->execute();

printf("%d row inserted.\n", $stmt->affected_rows);
?>

Java mit JDBC PreparedStatement

Das verwundbare Muster baut die Abfrage per Konkatenation:

// UNSICHER
String query = "SELECT * FROM products WHERE category = '" + input + "'";

Sicher mit PreparedStatement und Platzhalter ?, dessen Wert über setString() gebunden wird:

String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();

Python mit der DB-API

Die offizielle Python-Dokumentation warnt ausdrücklich vor String-Formatierung („Never do this -- insecure!"):

# UNSICHER, laut offizieller Doku niemals so
symbol = input()
sql = "SELECT * FROM stocks WHERE symbol = '%s'" % symbol
cur.execute(sql)

Sicher ist die Parametersubstitution der DB-API mit dem Platzhalter ? (bei sqlite3) beziehungsweise %s bei vielen anderen Treibern wie psycopg2. Wichtig: Der Platzhalter %s ist hier kein String-Format, sondern echte Parametrisierung, die Werte werden getrennt übergeben:

import sqlite3

con = sqlite3.connect(":memory:")
cur = con.execute("CREATE TABLE lang(name, first_appeared)")

# qmark-Stil in einer SELECT-Abfrage:
params = (1972,)
cur.execute("SELECT * FROM lang WHERE first_appeared = ?", params)
print(cur.fetchall())
con.close()

Node.js mit parametrisierten Abfragen

Der PostgreSQL-Treiber pg verwendet nummerierte Platzhalter ($1, $2), die Werte werden in einem separaten Array übergeben, niemals in den Query-String interpoliert:

const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *'
const values = ['brianc', '[email protected]']

const res = await client.query(text, values)
console.log(res.rows[0])

Dasselbe Prinzip greift sprachübergreifend: PHP, Java, Python, Node.js, alle trennen die feste Struktur von den variablen Werten. Parametrisierung ist keine Eigenheit einer einzelnen Sprache, sondern das universelle Grundmuster.

Die ORM-Falle: sicher, aber nicht automatisch

Ein Object-Relational-Mapper (ORM) wie Django oder Hibernate parametrisiert Standardabfragen automatisch und ist damit im Normalfall sicher. Ein weit verbreiteter Irrtum ist jedoch, ein ORM schütze pauschal vor SQL Injection. Das stimmt nur, solange Sie in der sicheren Abfrage-API bleiben. Sobald Rohabfragen oder String-Konkatenation ins Spiel kommen, ist die Lücke wieder da. MITRE führt dafür sogar eine eigene Klassifikation: CWE-564 (SQL Injection: Hibernate).

Bei Django umgehen genau vier Ausstiegsluken die automatische Parametrisierung: .extra(), .raw(), RawSQL und der direkte Datenbank-Cursor. Ein verwundbares Beispiel:

# UNSICHER: f-String-Interpolation in .extra()
products = Product.objects.extra(
    where=[f"name LIKE '%{search_term}%' AND category = '{category}'"],
    order_by=[sort_field]
)

Die sichere Lösung bleibt in der parametrisierten ORM-API mit Q()-Objekten und filter():

queryset = Product.objects.all()
if validated_data['search_term']:
    queryset = queryset.filter(
        Q(name__icontains=validated_data['search_term']) |
        Q(description__icontains=validated_data['search_term'])
    )
if validated_data['category']:
    queryset = queryset.filter(category=validated_data['category'])
queryset = queryset.order_by(validated_data['sort_field'])

Die klare Botschaft: Ein ORM ist kein automatischer Schutz, sobald Rohabfragen oder String-Konkatenation im Spiel sind. Bei Hibernate gilt dasselbe für roh zusammengebautes HQL statt benannter Parameter.

Allowlist für nicht parametrisierbare Elemente

Platzhalter funktionieren nur für Datenwerte, niemals für strukturelle Elemente wie Tabellennamen, Spaltennamen oder die Sortierrichtung (ASC/DESC). Diese gehören zur SQL-Struktur, nicht zu den Daten, und können nicht als Parameter gebunden werden. Muss ein solches Element aus Benutzereingaben stammen, ist eine Allowlist die einzige korrekte Absicherung: Sie bilden erlaubte Eingaben auf eine feste, im Code hinterlegte Menge zulässiger Werte ab und lehnen alles andere ab.

String tableName;
switch (PARAM) {
  case "Value1": tableName = "fooTable"; break;
  case "Value2": tableName = "barTable"; break;
  default: throw new InputValidationException("unexpected value provided");
}

Für die Sortierrichtung genügt eine boolesche Zuordnung auf die festen Literale statt einer beliebigen Zeichenkette:

public String someMethod(boolean sortOrder) {
  String SQLquery = "some SQL ... order by Salary " + (sortOrder ? "ASC" : "DESC");
}

Niemals sollte der rohe Benutzerwert direkt als Tabellenname oder ORDER-BY-Klausel eingesetzt werden.

Defense in Depth: mehrere Schichten statt einer

Prepared Statements sind die primäre Verteidigung, aber echte Sicherheit entsteht durch mehrere ineinandergreifende Schichten. Das OWASP-Cheat-Sheet und die CWE-89-Empfehlungen ergänzen die Parametrisierung um folgende Bausteine.

Allowlist-Validierung als zusätzliche Schicht: Eingaben sollten in allen Fällen serverseitig gegen eine Positivliste erlaubter Zeichen und Formate geprüft werden, auch wenn bereits parametrisiert wird. Validierung und Parametrisierung lösen unterschiedliche Probleme; validierte, aber per String zusammengebaute Eingaben können weiterhin unsicher sein.

Least Privilege für Datenbankbenutzer: Das Anwendungskonto sollte nur die minimal nötigen Rechte besitzen. OWASP formuliert das mit deutlicher Warnung: Weisen Sie Anwendungskonten niemals DBA- oder Admin-Rechte zu. Empfohlen werden getrennte Datenbankbenutzer je Anwendung, Nur-Lese-Zugänge wo keine Schreibrechte nötig sind, Views zur Feldbeschränkung und niemals den Datenbankprozess als root oder System zu betreiben. So bleibt der Schaden begrenzt, selbst wenn eine Injection gelingt.

Web Application Firewall (WAF) als zusätzliche, nicht ersetzende Schicht: Eine WAF kann bekannte Angriffsmuster abfangen und verschafft Zeit, etwa bei noch nicht gepatchten Altsystemen. Sie ist aber kein Ersatz für sicheren Code, denn Angreifer entwickeln laufend Techniken, um Filter zu umgehen. Eine WAF ergänzt Prepared Statements, sie ersetzt sie nicht.

Warum Escaping und Blocklists allein nicht genügen

Ein häufiges Anti-Pattern ist der Versuch, SQL Injection allein durch das Maskieren (Escaping) von Sonderzeichen oder durch Blocklists verbotener Wörter zu verhindern. OWASP stuft das Escaping aller Eingaben ausdrücklich als „STRONGLY DISCOURAGED" ein: Es ist stark datenbankspezifisch, fehleranfällig und im Vergleich zu anderen Verteidigungen fragil. Blocklists lassen sich durch alternative Schreibweisen, Kommentare oder Kodierungen umgehen. Auch der im alten Bestandsartikel gezeigte Ansatz mit mysqli_real_escape_string() ist genau so ein schwächeres, reines Escaping-Verfahren und kein Ersatz für ein echtes Prepared Statement. Setzen Sie deshalb immer auf Parametrisierung als primäre Kontrolle und behandeln Sie Escaping bestenfalls als letzten Notnagel.

Reale Fälle: von der CVE bis zum Millionenschaden

SQL Injection ist keine theoretische Übung. Zwei Beispiele verdeutlichen die Bandbreite.

Ein aktuelles CVE-Beispiel: Der Eintrag CVE-2024-2333 (veröffentlicht am 9. März 2024, gegen die NVD-Datenbank verifiziert) betrifft das Nischenprodukt CodeAstro Membership Management System 1.0. Laut NVD-Beschreibung führt die Manipulation des Parameters fullname in der Datei /add_members.php zu einer SQL Injection, klassifiziert als CWE-89. Interessant ist, dass die beiden auf der NVD-Seite genannten Bewertungsquellen zu unterschiedlichen Schweregraden kommen (NIST 7.2 HIGH, VulDB 6.3 MEDIUM), je nach Annahme über erforderliche Rechte. Das zeigt: Schweregrade sind keine objektive Einzelzahl. Das Beispiel dient hier nur zur Illustration, dass solche Schwachstellen bis heute regelmäßig dokumentiert werden.

Ein bekannter historischer Vorfall (mit Quellenvorbehalt): Der TalkTalk-Datenleck von 2015 gilt als Lehrbuchfall. Nach überwiegend sekundären Quellen (Wikipedia, Presseberichte, ICO-Untersuchung) nutzten Angreifer SQL-Injection-Schwachstellen in drei veralteten, aus einer Übernahme geerbten Legacy-Seiten aus, um auf 156.959 Kundenkonten zuzugreifen. Die britische Datenschutzbehörde ICO verhängte anschließend ein Bußgeld, das nach diesen Quellen 400.000 Pfund betrug. Der genaue Bußgeldbetrag ist nur sekundär belegt (die ICO-Originalseite war beim Abruf nicht direkt erreichbar), weshalb wir ihn ausdrücklich mit Vorbehalt nennen. Unstrittig ist die Kernlehre: Vergessene, ungepatchte Altsysteme sind ein Einfallstor, und ein frei verfügbares Werkzeug wie SQLMap genügte, um die Lücke zu finden.

Abgrenzung zu verwandten Angriffen

SQL Injection vs. NoSQL Injection: NoSQL-Datenbanken wie MongoDB verwenden keine standardisierte Abfragesprache, sondern flexible, dokumentbasierte Strukturen. Das Grundprinzip bleibt gleich (Angreifer bricht die vorgesehene Abfragestruktur auf), doch die Payloads unterscheiden sich deutlich, etwa über Operatoren wie $ne oder $regex in JSON. Ein Beispiel für einen Authentifizierungs-Bypass:

{"username":{"$ne":"invalid"},"password":{"$ne":"invalid"}}

Bei NoSQL kommt als zusätzliche Schutzmaßnahme das Allowlisting der erlaubten Operator-Schlüssel hinzu (etwa das Ablehnen von Schlüsseln, die mit $ beginnen).

SQL Injection vs. Command Injection: Beide gehören zur breiten Injection-Familie, zielen aber auf unterschiedliche Interpreter. SQL Injection manipuliert Datenbankabfragen, Command Injection schleust Befehle an das Betriebssystem ein. Wie eine SQL Injection über Funktionen wie xp_cmdshell bis zur Betriebssystemebene eskalieren kann, zeigt unser Artikel zu Remote Code Execution.

Verwandt ist auch die Umgehung von Zugriffskontrollen, die wir unter Broken Access Control und IDOR behandeln.

Ein weiterer naher Verwandter im Web-Umfeld ist Cross-Site Scripting (XSS), das ebenfalls auf mangelnder Trennung von Code und Daten beruht, dort allerdings im Browser des Opfers statt in der Datenbank.

Häufige Fragen zu SQL Injection

Was ist eine SQL Injection einfach erklärt?

Eine SQL Injection ist eine Schwachstelle, bei der ein Angreifer über normale Eingabefelder eigenen SQL-Code in eine Datenbankabfrage einschleust. Formal beschrieben wird sie durch CWE-89. Die Ursache ist immer dieselbe: Die Anwendung trennt Programmcode und Benutzereingabe nicht sauber, sodass die Datenbank die Eingabe als Befehl statt als reinen Datenwert interpretiert.

Wie kann man SQL Injection verhindern?

Der wirksamste Schutz sind parametrisierte Abfragen (Prepared Statements), die OWASP als „Primary Defense" listet. Dabei steht das SQL-Befehlsgerüst mit Platzhaltern fest, und die Benutzerwerte werden erst danach getrennt übergeben. Ergänzend gehören Allowlist-Validierung für Strukturelemente, das Least-Privilege-Prinzip für Datenbankkonten und eine WAF als zusätzliche Schicht dazu. Reines Escaping oder Blocklists genügen nicht.

Was ist ein Beispiel für eine SQL Injection?

Ein klassisches Beispiel ist der Anmelde-Bypass. Gibt ein Angreifer als Benutzernamen ' OR '1'='1'; -- ein, wird die Bedingung '1'='1' immer wahr, und der doppelte Bindestrich -- kommentiert die Passwortprüfung aus. Die Anmeldung ist ohne gültiges Passwort umgangen. Ein zweites Muster ist die UNION-based Injection mit ' UNION SELECT username, password FROM users--, die Daten aus einer fremden Tabelle sichtbar macht.

Was ist der Unterschied zwischen Blind und In-Band SQL Injection?

Bei der In-Band-Variante kommen die Daten direkt in der Antwort der Anwendung zurück (etwa Error-based oder UNION-based), das ist die einfachste Form. Bei der Blind SQL Injection gibt die Anwendung weder Ergebnisse noch aussagekräftige Fehlermeldungen aus. Der Angreifer stellt der Datenbank stattdessen Wahr-oder-Falsch-Fragen und leitet die Antwort aus dem Verhalten der Seite (Inhalt oder Antwortzeit) ab.

Schützt ein ORM automatisch vor SQL Injection?

Nein, nicht pauschal. Ein ORM wie Django oder Hibernate parametrisiert Standardabfragen automatisch und ist damit im Normalfall sicher. Der Schutz gilt aber nur, solange Sie in der sicheren Abfrage-API bleiben. Sobald Rohabfragen (.raw(), .extra()) oder String-Konkatenation ins Spiel kommen, ist die Lücke wieder da. MITRE führt dafür eine eigene Klassifikation: CWE-564.

Zusammenfassung

SQL Injection bleibt eine der folgenreichsten Schwachstellen für Webanwendungen und Datenbanken. Formal beschrieben durch CWE-89 und eingeordnet unter OWASP A03:2021 beziehungsweise A05:2025 Injection, entsteht sie immer aus derselben Wurzel: der fehlenden Trennung von Programmcode und Benutzereingabe. Die Angriffstypen reichen von In-Band (Error-based, UNION-based) über Blind (Boolean- und Time-based) bis zu Out-of-Band und der besonders tückischen Second-Order-Variante.

Der wirksamste Schutz ist eindeutig: parametrisierte Abfragen (Prepared Statements) in jeder Sprache und an jeder Stelle der Anwendung, ergänzt um Allowlist-Validierung für nicht parametrisierbare Strukturelemente, das Least-Privilege-Prinzip für Datenbankkonten und eine WAF als zusätzliche Schicht. Reines Escaping oder Blocklists genügen ausdrücklich nicht. Und Vorsicht bei ORMs: Sie schützen nur, solange Sie in ihrer sicheren API bleiben und nicht in Rohabfragen ausweichen.

Wenn Sie die Exploitation-Seite tiefer verstehen wollen, inklusive konkreter Filter-Bypass-Techniken und eines praxisnahen Fallbeispiels, lesen Sie weiter in unserem Artikel zu fortgeschrittener SQL Injection.