MB Dev .tech
Registrieren Login

Sicherheit Basics · SQL-Injection & Prepared Statements

← Zurück zu Sicherheit Basics

SQL-Injection ist eine der bekanntesten Schwachstellen in Web-Anwendungen. Sie passiert, wenn Benutzereingaben direkt in SQL-Strings eingebaut werden. Dann kann ein Angreifer den SQL-Befehl „mitsteuern“.

Achtung
SQL und Benutzereingaben dürfen nicht zusammen „zusammengeklebt“ werden

Die Lösung heißt fast immer: Prepared Statements (mit Parametern).

1) Wie entsteht SQL-Injection?

Stell dir vor, du willst einen Benutzer anhand seiner E-Mail suchen. Eine naive (unsichere) Idee wäre: „SQL zusammenbauen“ und die Eingabe direkt einsetzen.

Unsicher (nicht nachmachen)

$email = $_POST['email'] ?? '';

$sql = "SELECT id, email FROM users WHERE email = '$email'";
// Wenn $email Anführungszeichen enthält, „bricht“ die Query auseinander.

Wenn jetzt jemand als Eingabe zum Beispiel etwas eintippt, das das SQL verändert, kann das die Abfrage komplett anders machen als gedacht. Genau das ist SQL-Injection.

Merke
Die Daten „werden Teil des Befehls“

Der Kernfehler ist: Du vermischst SQL-Code (Befehl) und Benutzerdaten (Inhalt).

2) Die Lösung: Prepared Statements (Parameter)

Prepared Statements trennen SQL und Daten. Du schreibst Platzhalter (z.B. :email) und übergibst die Werte separat. Die Daten können dann nicht mehr „SQL werden“.

Sicher (Prepared)

$email = $_POST['email'] ?? '';

$sql  = "SELECT id, email FROM users WHERE email = :email";
$stmt = $pdo->prepare($sql);

$stmt->execute([
    ':email' => $email,
]);

$row = $stmt->fetch(PDO::FETCH_ASSOC);
Tipp
Faustregel: Immer Prepared

Selbst wenn du denkst „das Feld ist doch nur eine Zahl“: Nutz Prepared Statements trotzdem.

3) Auch Zahlen sind Eingaben (IDs, LIMIT, OFFSET)

Ein häufiger Denkfehler: „IDs sind ja Zahlen, das ist sicher.“ Aber Benutzer können auch bei Zahlen Mist eingeben (oder absichtlich manipulieren). Daher: casten, prüfen, und als Integer binden.

ID sauber behandeln

$id = (int)($_GET['id'] ?? 0);
if ($id < 1) {
    $id = 1;
}

$sql  = "SELECT id, title FROM tasks WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
Merke
Cast + Grenzen + Prepared

Das ist die Kombination, die dich in der Praxis sehr weit bringt.

4) Warum „Escaping“ für SQL nicht die richtige Lösung ist

Manche denken: „Ich ersetze einfach Anführungszeichen“ oder „ich nutze eine Escape-Funktion“. Das ist fehleranfällig, abhängig von DB/Encoding und oft nicht vollständig. Außerdem verwechseln viele „Escaping für HTML“ mit „Escaping für SQL“.

Achtung
SQL-Schutz = Prepared Statements

Wenn du SQL-Injection vermeiden willst, sind Prepared Statements der Standard. Das ist die Lösung, auf die du dich verlassen kannst.

5) Typische Stellen, an denen SQLi passiert

  • Login (E-Mail / Benutzername)
  • Suche (Suchbegriffe in WHERE)
  • Filter/Sortierung (ORDER BY, WHERE)
  • „Details“-Seiten (id aus URL)
  • Admin-Bereiche (CRUD-Formulare)
Tipp
ORDER BY ist ein Sonderfall

Spaltennamen kann man nicht wie Werte binden. Stattdessen nutzt man meist eine Whitelist, z.B. erlaubte Sortierfelder: created_at, title, id.

Kleine Aufgaben (zum Mitdenken)

Aufgabe 1: Unsichere Query erkennen

Was ist an folgendem Code unsicher?

Aufgabe

$term = $_GET['q'] ?? '';
$sql  = "SELECT * FROM products WHERE name LIKE '%$term%'";
Lösung einblenden
Lösung
Eingabe ist direkt im SQL-String

$term wird direkt in die Query eingebaut. Das ist genau das Muster, das SQL-Injection möglich macht. Besser: Prepared Statement mit LIKE :term und Wert %...% binden.