Jedes SQL-Statement, das durch das Zusammenfügen von Strings mit nicht vertrauenswürdigen Werten entsteht, kann für eine Injection missbraucht werden. Die Lösung ist in jeder Sprache dieselbe: Das SQL bleibt fest, die Werte werden als Parameter gebunden. Das OWASP Query Parameterization Cheat Sheet ordnet SQL Injection in den OWASP Top 10:2025 unter A05:2025-Injection ein und bezeichnet sie als eine der gefährlichsten Schwachstellen in Webanwendungen.
Warum Verkettung scheitert
SQL unterscheidet nicht wirklich zwischen Kontrollebene und Datenebene. Ein Angreifer platziert ein Metazeichen in einer Eingabe, und plötzlich entstehen SQL-Befehle, wo vorher keine waren. Die OWASP-Community-Seite zu SQL Injection zeigt eine in C# per Verkettung gebaute Abfrage:
string query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ ItemName.Text + "'";
Gibt der Angreifer name' OR 'a'='a als Item-Namen ein, ist die Bedingung immer wahr. Die Abfrage liefert dann jede Zeile der Tabelle items, unabhängig vom Besitzer. Je nach Datenbankfunktionen und Berechtigungen kann dieselbe Schwachstelle auch Daten verändern, Dateien beeinflussen oder Betriebssystembefehle ausführen.
Die Herkunft des Werts spielt keine Rolle. Die Schutzmaßnahmen gelten gleichermaßen, ob die Daten aus einem HTTP-Request, von einem anderen Service oder aus zuvor gespeicherten Daten stammen.
String-basierte Abfragen finden
Beginnen Sie mit einem grep nach SQL-Schlüsselwörtern in der Nähe von Verkettung oder Formatierung. Diese Muster sind ein grober erster Durchgang und kein Scanner. Sie erzeugen viele Fehlalarme und übersehen Abfragen, die über mehrere Zeilen verteilt sind.
# concatenation (Java, C#, JavaScript, PHP uses "." instead of "+")
grep -rnEi "(select|insert into|update|delete from)[^;]*[\"'] *\+" src/
# Python f-strings and .format()
grep -rnEi "f[\"'](select|insert|update|delete)|(select|insert|update|delete).*\.format\(" .
# JavaScript template literals
grep -rnEi '`(select|insert|update|delete)[^`]*\$\{' src/
# sprintf-style formatting
grep -rnEi "sprintf\(.*(select|insert|update|delete)" .
Lesen Sie jeden Treffer und prüfen Sie, ob nicht vertrauenswürdige Werte gebunden statt verkettet werden. Schauen Sie anschließend an den Stellen nach, die ein grep erfahrungsgemäß auslässt:
- ORM-Query-Strings. Hibernate-Abfragen können verwundbar sein, wenn Werte in den Query-String verkettet statt gebunden werden.
- Stored Procedures. Sie halten das SQL in der Datenbank, sind aber nicht automatisch sicher. OWASP zeigt eine PL/SQL-Prozedur, die
usrunditmzu einem Query-String zusammenfügt, und stellt fest, sie sei „anfällig für denselben SQL-Injection-Angriff wie im ersten Beispiel". Das Binden der Argumente des Aufrufs schützt den Aufruf, nicht aber dynamisches SQL, das innerhalb der Prozedur gebaut wird. - Clientseitige Parametrisierung. Viele clientseitige Bibliotheken bauen Abfragen per String-Verkettung und senden die rohe Abfrage an den Server. Stellen Sie sicher, dass die Parametrisierung serverseitig stattfindet.
- Dynamische Bezeichner und Sortierrichtungen. Diese lassen sich nicht binden, daher überlebt die Verkettung hier häufig eine Umstellung auf Parameter.
Testen Sie danach. Der OWASP Web Security Testing Guide enthält SQL-Injection-Tests für manuelle und automatisierte Prüfungen, einschließlich Blind- und Out-of-Band-Techniken. Für eine externe Kontrolle dessen, was Ihre Prüfung übersehen hat, ist ein Penetrationstest das richtige Mittel.
Durch Parameter ersetzen
Definieren Sie die SQL-Struktur getrennt von den Datenwerten und binden Sie die Werte anschließend über die Datenbank-API. Das folgende Beispiel stammt aus dem OWASP SQL Injection Prevention Cheat Sheet, wobei Aufräumarbeiten und Fehlerbehandlung weggelassen wurden:
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, customerName);
ResultSet results = statement.executeQuery();
Ein Wert wie tom' or '1'='1 wird nun als der buchstäbliche Benutzername behandelt, nach dem gesucht wird. Wer einen nicht vertrauenswürdigen Wert vor dem Prepare in die Abfrage verkettet, hebelt den Schutz aus.
Auch mit einem ORM binden Sie im Query-String. Die OWASP-Beispiele verwenden in HQL einen benannten Parameter, FROM Inventory i WHERE i.productDescription = :productDescription, und übergeben den Wert mit setParameter.
Für dynamisches SQL in Stored Procedures verwenden Sie Bind-Variablen. In Oracle PL/SQL bedeutet das EXECUTE IMMEDIATE mit USING. In SQL Server bedeutet es sp_executesql mit einer deklarierten Parameterliste. Prozeduren ohne dynamisches SQL brauchen nichts Besonderes, da ihre Parameter bereits gebunden sind.
Das Binden verhindert, dass ein Wert die SQL-Syntax verändert. Es entscheidet nicht darüber, ob der Aufrufer auf das jeweilige Konto zugreifen darf. Ihre Autorisierungsprüfungen bleiben daher bestehen.
Was Parameter nicht abdecken
Parameter-Platzhalter stehen für Datenwerte. Sie können nicht für Tabellennamen, Spaltennamen oder Schlüsselwörter wie ASC und DESC stehen. Wenn Benutzer eine Auswahl treffen müssen, ordnen Sie die Auswahl einer kleinen Menge anwendungseigener Tokens zu und weisen Sie alles Unbekannte ab. Vorher:
String query = "SELECT id, name FROM items ORDER BY name " + sortParam;
Nachher:
Map<String, String> directions = Map.of("asc", "ASC", "desc", "DESC");
String direction = directions.get(sortParam);
if (direction == null) {
throw new IllegalArgumentException("Unknown sort direction");
}
String query = "SELECT id, name FROM items ORDER BY name " + direction;
Der an die Abfrage angehängte Text ist nun immer eines von zwei Literalen und nie das, was der Benutzer gesendet hat. Binden Sie weiterhin jeden Datenwert in derselben Abfrage, auch wenn er Typ-, Längen- oder Formatprüfungen besteht.
Greifen Sie nicht auf manuelles Escaping zurück. Es hängt vom SQL-Dialekt und von der Konfiguration ab: Der MySQL-Modus NO_BACKSLASH_ESCAPES deaktiviert beispielsweise das Backslash-Escaping. OWASP stuft Escaping als dringend nicht empfohlen ein und beschreibt Deny Lists als voller Schlupflöcher.
Den Schaden begrenzen
Least Privilege verhindert SQL Injection nicht. Es begrenzt, was ein Angreifer über ein Datenbankkonto tun kann, und gehört deshalb zusätzlich zu den Parametern umgesetzt.
- Beginnen Sie bei null und fügen Sie die Rechte hinzu, die jedes Anwendungskonto braucht. Wer überlegt, was er von einer breiten Berechtigung wieder wegnehmen soll, übersieht leicht Rechte.
- DBA- oder Admin-Zugriff für ein Anwendungskonto ist die übliche Abkürzung, weil dann alles „einfach funktioniert". OWASP nennt das sehr gefährlich.
- Eine Login-Seite braucht Lesezugriff auf die Felder für Benutzername und Passwort, aber keinen Schreibzugriff. Eine Registrierungsseite braucht Insert. Diese Trennung lässt sich nur durchsetzen, wenn beide unterschiedliche Datenbankbenutzer verwenden. Geben Sie daher jeder Webanwendung einen eigenen.
- Angreifer brauchen keine Injection, um ein überprivilegiertes Konto zu missbrauchen. Sie können einen Parameter von einem zulässigen Wert auf einen ändern, für den sie nicht autorisiert sind, die Anwendung aber schon. Deshalb helfen enge Berechtigungen auch dort, wo die Abfragen sauber sind.
- Ein View kann nur die Felder bereitstellen, die ein Konto braucht, wobei
SELECTauf den View und nicht auf die Tabelle vergeben wird. Prüfen Sie zuvor die Regeln Ihrer Datenbank: PostgreSQL prüft den Zugriff auf die zugrunde liegenden Tabellen normalerweise mit den Rechten des View-Eigentümers. Auch Abfragen auf Views benötigen Parameter. - Auch das Betriebssystemkonto ist wichtig. Betreiben Sie das DBMS nicht als root oder system. OWASP weist darauf hin, dass MySQL unter Windows standardmäßig als system läuft.
Womit Sie anfangen
- Listen Sie jede Stelle auf, an der SQL gebaut wird: Anwendungscode, ORM-Query-Strings und Stored Procedures mit dynamischem SQL. Führen Sie die oben genannten grep-Muster als ersten Durchgang aus.
- Ersetzen Sie verkettete Werte durch gebundene Parameter, und zwar serverseitig, und bauen Sie dynamisches SQL in Prozeduren mit Bind-Variablen neu auf.
- Ersetzen Sie dynamische Tabellen-, Spalten- und Sortierauswahlen durch feste Zuordnungen, die unbekannte Eingaben abweisen.
- Prüfen Sie die Datenbankkonten. Entziehen Sie Anwendungskonten DBA- und Admin-Rechte, geben Sie jeder Anwendung einen eigenen Benutzer und verlagern Sie Lesezugriffe auf enge Berechtigungen oder Views.
- Führen Sie die SQL-Injection-Tests des Web Security Testing Guide gegen das Ergebnis aus oder bitten Sie unser Team für sichere Softwareentwicklung, die Änderungen zu prüfen.
