Skip to main content

SQL-Injection-Spickzettel: 8 Best Practices zum Schutz vor SQL-Injection-Angriffen

Artikel von

26. März 2021

0 Min. Lesezeit

SQL-Injection zählt zu den gefährlichsten Sicherheitslücken in Webanwendungen. Sie entsteht, wenn Nutzer:innen nicht vertrauenswürdige Daten in eine Datenbankabfrage eingeben, zum Beispiel beim Ausfüllen eines Webformulars. Ist eine SQL-Injection möglich, können Angreifer:innen mit ausgeklügelten Eingaben wertvolle Daten stehlen, die Authentifizierung umgehen oder Datensätze in Ihrer Datenbank manipulieren.

Es gibt verschiedene Arten von SQL-Injection-Angriffen, doch im Allgemeinen haben sie alle eine ähnliche Ursache: Die nicht vertrauenswürdigen Daten, die Nutzer:innen eingeben, werden an die Abfragezeichenfolge angehängt. Dadurch können die Eingaben die ursprüngliche Absicht der Abfrage verändern.

Beispiele für SQL-Injection sind:

  • Ein boolesches Element zu einer WHERE-Klausel hinzufügen, das immer wahr ist, etwa ' OR 1=1

  • Einen Teil der Abfrage auskommentieren, indem Sie Zeilenkommentare eingeben: --

  • Die ursprüngliche Abfrage beenden und eine neue starten: '; DROP TABLE USERS;

  • Daten aus mehreren Tabellen mit UNION verknüpfen

In diesem Spickzettel stelle ich acht Best Practices vor, mit denen alle Anwendungsentwickler:innen SQL-Injection-Angriffe verhindern können. Legen wir also los und machen Ihre Anwendung SQLi-sicher.

Snyk-Cheatsheet mit acht Best Practices zur Verhinderung von SQL-Injection-Angriffen, darunter Validierung, vorbereitete Anweisungen, ORM-Sicherheit und Eingabekontrollen.

Spickzettel herunterladen

  1. Verlassen Sie sich nicht auf clientseitige Eingabevalidierung

  2. Verwenden Sie einen Datenbanknutzer mit eingeschränkten Berechtigungen

  3. Verwenden Sie vorbereitete Anweisungen und Abfrageparametrisierung

  4. Scannen Sie Ihren Code auf SQL-Injection-Schwachstellen

  5. Verwenden Sie eine ORM-Schicht

  6. Verlassen Sie sich nicht auf Blocklisten

  7. Validieren Sie Eingaben

  8. Gehen Sie vorsichtig mit gespeicherten Prozeduren um

1. Verlassen Sie sich nicht auf clientseitige Eingabevalidierung.

Clientseitige Eingabevalidierung ist sehr hilfreich. Damit können Sie verhindern, dass ungültige Daten an Ihre Systemlogik weitergegeben werden. Das funktioniert jedoch leider nur bei Nutzer:innen ohne böse Absichten, die das System wie vorgesehen verwenden möchten. Nutzer:innen direkt darauf hinzuweisen, dass ein bestimmter Wert ungültig ist, ist äußerst hilfreich und benutzerfreundlich. Deshalb sollten Sie die clientseitige Validierung für eine bessere Nutzererfahrung einsetzen.

Bei SQL-Injection ist sie jedoch keine Methode, auf die Sie sich verlassen sollten. Die clientseitige Validierung lässt sich umgehen, indem Sie den im Browser geladenen JavaScript-Code verändern. Außerdem ist es in einer Client-Server-Architektur recht einfach, einen einfachen HTTP-Aufruf mit einem Parameter, der eine SQL-Injection auslöst, an das Backend zu senden. Dafür können Sie Tools wie Postman oder klassische curl-Befehle verwenden.

Sie sollten die Validierung serverseitig durchführen, idealerweise so nah wie möglich an der Quelle – in diesem Fall dort, wo Sie die SQL-Abfrage erstellen. Alles, was ein Client sendet, sollten Sie als potenziell schädlich betrachten. Sich bei SQL-Injection auf die clientseitige Validierung zu verlassen, ist daher eine schlechte Idee.

2. Verwenden Sie einen Datenbanknutzer mit eingeschränkten Berechtigungen

Wie bereits erwähnt, gibt es verschiedene Arten von SQL-Injection-Angriffen. Manche sind schädlicher als andere. Nehmen wir an, meine SQL-Abfrage lautet etwa "SELECT * FROM USER WHERE USERID = '" + userid +"'". Die Injection " foo' OR '1'='1 " gibt alle Nutzer:innen zurück und ist bereits schädlich. " '; UPDATE message SET password = 'EVIL" verursacht jedoch noch größere Probleme, weil der Angreifer nun alle Einträge verändert.

Wenn Sie einen Datenbanknutzer für Ihre Anwendung erstellen, sollten Sie dessen Berechtigungen sorgfältig festlegen. Muss die Anwendung alle Datenbanken lesen, beschreiben und aktualisieren können? Und wie sieht es mit dem Leeren oder Löschen von Tabellen aus? Wenn Sie die Berechtigungen Ihrer Anwendung für die Datenbank einschränken, können Sie die Auswirkungen einer SQL-Injection minimieren. Es ist wahrscheinlich sinnvoll, nicht nur einen Datenbanknutzer für Ihre Anwendung einzurichten, sondern mehrere und diese bestimmten Anwendungsrollen zuzuordnen. Sicherheitsprobleme entstehen meist durch eine Kettenreaktion. Achten Sie daher auf jedes Glied in der Kette, um größeren Schaden zu verhindern.

3. Verwenden Sie vorbereitete Anweisungen und Abfrageparametrisierung

Viele Programmiersprachen bieten integrierte Funktionen, mit denen Sie SQL-Injection verhindern können. Beim Schreiben von SQL-Abfragen können Sie sie beispielsweise mit einer vorbereiteten Anweisung kompilieren. So lässt sich die Abfrage parametrieren. Abfrageparametrisierung ist eine Technik, mit der SQL-Anweisungen dynamisch erstellt werden. Sie erstellen die Basisabfrage mit Platzhaltern und fügen die von Nutzer:innen angegebenen Parameter sicher in diese Platzhalter ein.

Bei echten vorbereiteten Anweisungen und parametrisierten Abfragen übernimmt die Datenbank selbst die Maskierung. Zunächst erstellt sie anhand der Abfragezeichenfolge mit Platzhaltern einen Ausführungsplan. Im zweiten Schritt werden die (nicht vertrauenswürdigen) Parameter an die Datenbank gesendet. Da der Abfrageplan bereits erstellt ist, haben die Parameter keinen Einfluss mehr darauf. So wird eine Injection vollständig verhindert.

Java-Beispiel:

String query = "SELECT * FROM USERS WHERE username LIKE ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, parameter);
ResultSet result = statement.executeQuery();

Python-Beispiel mit MySQL-Connector:

cursor = conn.cursor(prepared=True)
params = ("foo",)
cursor.execute("SELECT * FROM USERS WHERE username = %s", params)

JavaScript-Beispiel mit mysql2:

connection.query("SELECT * FROM USERS WHERE username = ?",[
     req.body.username
    ],function(error, results){}); 
//emulates a prepared statement

//OR

connection.execute("SELECT * FROM USERS WHERE username = ?",[
     req.body.username
    ],function(error, results){});
//prepared statement

In JavaScript gibt es verschiedene Möglichkeiten, dies beispielsweise mit einer MySQL-Datenbank umzusetzen. Beachten Sie, dass Sie mit .query() keine echte vorbereitete Anweisung ausführen. In diesem Fall erfolgt die Parameterersetzung clientseitig. Sie simulieren also eine vorbereitete Anweisung. Um eine echte vorbereitete Anweisung in der Datenbank auszuführen, sollten Sie die Funktion .execute() verwenden.

4. Scannen Sie Ihren Code auf SQL-Injection-Schwachstellen

Benutzerdefinierten Code zu erstellen ist wahrscheinlich einfach. Dabei passieren jedoch leicht Fehler. Um Ihren Code zu überprüfen, setzen Sie vielleicht auf Code-Reviews und Pair Programming. Aber kennt sich die Person, die Ihren Code überprüft oder mit Ihnen zusammenarbeitet, mit Sicherheit aus? Kann sie einen SQL-Injection-Fehler in Ihrem Code erkennen? Unabhängig davon wäre es hilfreich, Ihren eigenen Code automatisch auf mögliche Sicherheitslücken wie SQL-Injection zu untersuchen. Mit einem SAST-Tool (Static Application Security Testing) wie Snyk Code können Sie Ihren Code automatisch auf Sicherheitslücken wie SQL-Injection prüfen. Das lässt sich ganz einfach in Ihren SDLC integrieren, indem Sie beispielsweise Ihr Git-Repository mit Snyk verbinden.

Snyk Code-SQL-Injection-Bildschirm mit nicht bereinigter HTTP-Eingabe, die in MessageRepo.java in executeQuery fließt

5. Verwenden Sie eine ORM-Schicht

Sie können auch eine objekt-relationale Abbildungsschicht (ORM) verwenden. Eine ORM-Schicht wandelt Daten aus der Datenbank in Objekte um und umgekehrt. Eine ORM-Bibliothek reduziert die Anzahl expliziter SQL-Abfragen und damit auch die Gefahr von SQL-Injection.

Bekannte ORM-Bibliotheken sind beispielsweise Hibernate für Java und Entity Framework für C#. Dank der starken Typisierung dieser Sprachen lässt sich die Zuordnung zwischen Objekten und Datenbanktabellen in der Regel automatisch generieren. So müssen Sie sich nicht selbst mit SQL-Abfragen befassen.

Das Problem besteht allerdings darin, dass Sie möglicherweise benutzerdefinierte Abfragen erstellen müssen. Hibernate für Java verfügt über eine eigene Abfragesprache, die Hibernate Query Language (HQL). Achten Sie beim Kompilieren von HQL-Abfragen erneut auf Injection und verwenden Sie die Funktion createQuery(), die ähnlich wie eine vorbereitete Anweisung funktioniert.

Auch für JavaScript gibt es bekannte ORM-Bibliotheken wie Sequelize. Damit können Sie festlegen, wie Werte bestimmten Datentypen in der Datenbank zugeordnet werden. Aber machen wir uns nichts vor: Letztlich muss eine ORM-Bibliothek die Logik wieder in SQL-Anweisungen übersetzen. Wir vertrauen also darauf, dass diese Bibliotheken die Parameter ordnungsgemäß maskieren.

Um sicherzugehen, dass Ihre ORM-Bibliothek keine SQL-Injection-Schwachstellen enthält, sollten Sie sie auf bekannte Sicherheitslücken prüfen. Wenn Sie eine falsche oder veraltete Version von sequelize oder hibernate verwenden, kann das weiterhin zu Problemen führen. Mit Snyk Open Source können Sie Ihr Projekt prüfen und so verborgene SQL-Injection-Schwachstellen in Ihren Bibliotheken und viele weitere Probleme aufdecken.

6. Verlassen Sie sich nicht auf Blocklisten

Viele kennen das vermutlich schon, aber ich sage es noch einmal: Erstellen Sie keine Blocklisten für Ihre Parameter. Bei diesem Ansatz wird eine Sammlung von Regeln angelegt, die anfällige Eingaben definieren. Erfüllt eine Eingabe diese Regeln, wird die Anfrage blockiert. Sind die Regeln jedoch zu schwach, ist ein schädlicher Eintrag weiterhin wirksam. Sind sie zu streng, blockieren sie auch gültige Einträge.

Wir könnten beispielsweise jede Anfrage blockieren, die das Wort OR enthält. Das mag eine sinnvolle Regel sein, aber in der Praxis ist „Or“ ein sehr häufiger israelischer Vorname. Das bedeutet, dass einige meiner Kolleg:innen beim Eingeben ihres Namens blockiert würden. Dasselbe gilt für ein einzelnes Anführungszeichen '. Unzählige Namen enthalten dieses Zeichen. Denken Sie etwa an O’Neill und O'Donnell, aber auch an Vornamen wie Dont’a.

7. Validieren Sie Eingaben

Ja, Sie sollten Eingaben immer validieren! Vorbereitete Anweisungen mit Abfrageparametrisierung sind zwar der beste Schutz vor SQL-Injection, dennoch sollten Sie stets mehrere Verteidigungsebenen einrichten. Wie eingeschränkte Berechtigungen für einen Datenbanknutzer ist auch die Eingabevalidierung eine gute Maßnahme, um das allgemeine Risiko für Ihre Anwendung zu senken. Außerdem sind vorbereitete Anweisungen nicht immer verfügbar. Manche Programmiersprachen unterstützen diesen Mechanismus nicht, oder ältere Datenbanksysteme erlauben es nicht, Nutzereingaben als Parameter zu übergeben. In diesen Fällen ist die Eingabevalidierung eine akzeptable Alternative.

Achten Sie darauf, Eingaben mithilfe von Zulassungslisten statt Blocklisten zu validieren, wie oben beschrieben. Definieren Sie mit einer Regel alle zulässigen Muster, beispielsweise mithilfe eines regulären Ausdrucks, oder verwenden Sie eine gut gepflegte Bibliothek. Kombinieren Sie dies mit vorbereiteten Anweisungen und Abfrageparametrisierung, und Sie erhalten einen umfassenden Schutz.

8. Gehen Sie vorsichtig mit gespeicherten Prozeduren um

Viele glauben, dass gespeicherte Prozeduren eine gute Möglichkeit sind, SQL-Injection zu verhindern. Das ist nicht immer der Fall. Genau wie SQL-Abfragen in Ihrer Anwendung können auch gespeicherte Prozeduren durch eine Injection angegriffen werden.

Wie bei SQL-Abfragen in Ihrer Anwendung sollten Sie die Abfragen in gespeicherten Prozeduren parametrieren, statt die Parameter aneinanderzureihen. SQL-Injection in einer gespeicherten Prozedur lässt sich recht einfach verhindern.

Verwenden Sie also in MySQL nicht Folgendes:

DELIMITER //
CREATE PROCEDURE `FindUsers`(
    IN Username VARCHAR(50)
)
BEGIN

    SET @Statement = CONCAT('SELECT * FROM User WHERE username = ', Username, ' );

    PREPARE stm FROM @Statement;
    EXECUTE stm;

END //
DELIMITER ;

Verwenden Sie stattdessen parametrisierte Abfragen in Ihren gespeicherten Prozeduren:

DELIMITER //
CREATE PROCEDURE `First`(
    IN Username VARCHAR(50)
)
BEGIN

    PREPARE stm FROM 'SELECT * FROM User WHERE username = ?';
    EXECUTE stm USING Username;

END //
DELIMITER ;

Wie oben gezeigt, gelten für gespeicherte Prozeduren dieselben Regeln wie für den Code Ihrer Anwendung. Die Implementierung gespeicherter Prozeduren unterscheidet sich je nach Datenbank. Stellen Sie sicher, dass Sie wissen, wie Sie sie für Ihre Datenbank implementieren, und achten Sie auch dort auf Injection. Ich bin zwar der Meinung, dass die gesamte Logik besser in Ihrer Anwendung aufgehoben ist, aber eine gespeicherte Prozedur kann eine sinnvolle Lösung sein, wenn die von Ihnen verwendete Programmiersprache keine vorbereiteten Anweisungen unterstützt.

Weiterlesen

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSec: Sicherheit, die mit der KI-Entwicklung Schritt hält

KI-gestützte Entwicklung überholt herkömmliche Security-Workflows. Snyk Agentic AppSec setzt Security-Agenten ein, die Schwachstellen kontinuierlich finden, beheben und überprüfen – während Menschen die Maßstäbe festlegen.

blog feature ai violet
Blog

VulnBump: Können KI-Agenten anfällige Abhängigkeiten beheben, ohne den Build zu beschädigen?

VulnBump testet KI-Agenten anhand von 307 realen Dependency-Upgrades. Eine Lösung zählt nur, wenn die Sicherheitslücke behoben ist und Build sowie Tests weiterhin erfolgreich sind.

Blog

Frontier-Modelle fanden die Schwachstellen. Nur der Angreifer fand die Angriffsketten.

Die statische Analyse fand die Schwachstellen, doch erst Live-Angriffstests bewiesen, wie sie sich zu Angriffen verketten lassen. Ein Vergleich von Evo COS, Claude Security und Claude Code Security.