SQL Injection verstehen und sicher verhindern

Autor: Veröffentlicht

Kurz erklärt: SQL Injection ist eine Schwachstelle, durch die sich Datenbankabfragen mit gezielt präparierten Eingaben verändern lassen. Sie entsteht vor allem dann, wenn eine Anwendung SQL-Befehle mit vom Benutzer eingegebenem Text zusammensetzt. Den grundlegenden Schutz bieten parametrisierte Abfragen: Sie trennen den Befehlstext von den Daten.

Wie Benutzereingaben zum Befehl werden

SQL ist eine Sprache zum Lesen und Ändern von Daten in relationalen Datenbanken. Eine Anwendung kann damit ein Konto suchen, eine Bestellung abrufen oder eine neue Kundenadresse speichern. SQL Injection, also das Einschleusen von SQL, liegt vor, wenn Eingaben als Teil der Syntax eines solchen Befehls behandelt werden.

Angenommen, eine Anwendung sucht ein Konto anhand des Benutzernamens. Der Entwickler schreibt den Anfang der Abfrage, hängt den Wert aus dem Formular an und schließt ihn mit einem einfachen Anführungszeichen ab. Ein gewöhnlicher Benutzername liefert das erwartete Ergebnis. Enthält die Eingabe jedoch ein einfaches Anführungszeichen und eine zusätzliche Bedingung, kann sie die Bedeutung der gesamten Suche verändern.

Der gefährliche Wert muss nicht aus einem Anmeldeformular stammen. Auch ein URL-Parameter, ein Produktfilter, ein Anfrage-Header oder bereits in der Datenbank gespeicherte Daten können die Quelle sein. Entscheidend ist, wie die Anwendung den Wert verwendet, nicht woher er stammt.

Ein einfaches Experiment unter Linux

Das folgende Beispiel arbeitet ausschließlich mit einer temporären Datenbank im Speicher des Prozesses. Es stellt keine Verbindung zu einer Website oder einem externen Server her. Es zeigt, wie mehr Datensätze als vorgesehen ausgelesen werden, und bildet keinen vollständigen Anmeldevorgang ab.

Du benötigst Python 3 mit dem verfügbaren Modul sqlite3. Das kannst du im Terminal mit diesem Befehl prüfen:

python3 -c "import sqlite3; print(sqlite3.sqlite_version)"

Falls der Befehl fehlschlägt, findest du in der Dokumentation deiner Distribution Hinweise zur Installation von Python 3 oder des fehlenden Moduls. Ein eigener Datenbankserver muss nicht gestartet werden.

Führe den gesamten Block im Terminal aus:

python3 - <<'PY'
import sqlite3

verbindung = sqlite3.connect(":memory:")
polaczenie.execute(
    "CREATE TABLE konten (id INTEGER PRIMARY KEY, login TEXT)"
)
polaczenie.executemany(
    "INSERT INTO konten (login) VALUES (?)",
    [("anna",), ("peter",)]
)

wert = "' OR 1=1 -- "
abfrage = "SELECT id, login FROM konten WHERE login = '" + wert + "'"

print("Abfrage:", abfrage)
print("Ergebnis:", polaczenie.execute(abfrage).fetchall())
polaczenie.close()
PY

Das Ergebnis enthält beide Konten, obwohl keines einen Benutzernamen hat, der der eingegebenen Zeichenfolge entspricht. Das einfache Anführungszeichen beendet den Text des Benutzernamens. OR 1=1 ergänzt eine Bedingung, die immer wahr ist, und -- leitet einen Kommentar ein. Dadurch hat das abschließende Anführungszeichen keinen Einfluss mehr auf die Abfrage.

Dieser Code ist absichtlich verwundbar und dient der lokalen Demonstration. Teste ähnliche Eingaben nicht auf fremden Websites, solange keine ausdrückliche Erlaubnis vorliegt, die den Umfang der Tests abdeckt.

So behebst du die Schwachstelle im selben Code

Eine parametrisierte Abfrage enthält Platzhalter für Daten. Die Werte selbst werden separat an die Datenbankbibliothek übergeben. Im Modul sqlite3 kann ? als solcher Platzhalter dienen. Die Bibliothek behandelt den übergebenen Wert als Daten und nicht als zusätzliche SQL-Syntax.

Ersetze im vorherigen Beispiel das Zusammensetzen und Ausführen der Abfrage durch diesen Abschnitt. Er muss weiterhin vor polaczenie.close() stehen:

abfrage = "SELECT id, login FROM konten WHERE login = ?"
ergebnis = polaczenie.execute(abfrage, (wert,)).fetchall()
print("Ergebnis:", ergebnis)

Jetzt erhältst du eine leere Liste: Die Datenbank sucht nach einem Benutzernamen, der exakt den gesamten übergebenen Text enthält. Wenn du wert auf "anna" setzt, gibt die Abfrage nur Annas Konto zurück. Das Komma in (wert,) erzeugt ein Tupel mit einem Element, das bei dieser Art der Parameterübergabe erwartet wird.

Ersetze Parameter nicht durch manuelles Entfernen einfacher Anführungszeichen oder durch Textformatierung, etwa mit einem f-String. Ein Benutzername mit einem einfachen Anführungszeichen kann eine gültige Eingabe sein; die Anwendung muss ihn sicher verarbeiten. Die Parametersyntax hängt von der Bibliothek ab. Deshalb solltest du das Muster ? nicht ungeprüft auf jeden Datenbanktreiber übertragen.

Welche Folgen die Schwachstelle haben kann

SQL Injection kann es ermöglichen, fremde Daten auszulesen, Teile der Zugriffskontrolle zu umgehen und Datensätze zu verändern oder zu löschen. Die tatsächlichen Folgen hängen von der Abfrage, dem Datenbanksystem und den Berechtigungen des von der Anwendung verwendeten Kontos ab. Eine entdeckte Schwachstelle bedeutet nicht automatisch, dass der gesamte Server übernommen werden kann.

Auch das Ausbleiben einer sichtbaren Fehlermeldung beweist keine Sicherheit. Die Anwendung kann Datenbankmeldungen verbergen, sodass sich das veränderte Verhalten nur an unterschiedlichen Antworten erkennen lässt. Umgekehrt ist ein einzelner Fehler nach der Eingabe eines einfachen Anführungszeichens ein Anlass zur Untersuchung, aber für sich genommen noch kein Beweis für SQL Injection.

Was du in deiner eigenen Anwendung prüfen solltest

Beginne die Prüfung an den Stellen, an denen SQL-Abfragen entstehen:

Wähle zunächst eine Suche aus, die Benutzereingaben verwendet. Ergänze einen Test mit einem gewöhnlichen Wert und mit einem Text, der ein einfaches Anführungszeichen enthält. Prüfe, ob beide Eingaben als Daten behandelt werden, statt die Struktur der Abfrage zu verändern.

Siehe auch: Was ist ein ETF?

Lesen Sie auch

Wie PHP-FPM PHP-Anfragen verarbeitet, sich vom Webserver unterscheidet und wie du Prozesse an Last und Hosting-Ressourcen anpasst.
BIND ist offene DNS-Serversoftware. Erfahre, welche Aufgaben sie übernimmt und wann sich der Betrieb eines eigenen Servers lohnt.
Was Polens REGON-Register und seine Kennnummer bedeuten, wie sie sich von NIP und KRS unterscheiden und wie Sie Unternehmensdaten prüfen.