Prepared Statements

Erste Schritte: Prepared Statements: SQL-Injection-Schutz

Eines der größten Sicherheitsrisiken beim Zugriff auf eine MySQL/MariaDB-Datenbank in PHP ist die SQL-Injection. Dabei versucht ein Angreifer, durch manipulierte Eingaben die SQL-Abfrage zu verändern, um Daten zu stehlen, zu löschen oder sonstigen Schaden anzurichten. Mit Prepared Statements (vorbereitete Anweisungen) minimierst du dieses Risiko erheblich. In diesem Tutorial lernst du, wie und warum du Prepared Statements einsetzen solltest.

1. Was ist SQL-Injection?

Unter SQL-Injection versteht man das Einschleusen unerwünschter SQL-Befehle in einen regulären Datenbankbefehl, indem man Benutzer-Eingaben nicht korrekt validiert oder maskiert. Beispiel:

// Unsicheres Beispiel (bitte so NICHT machen)
$userInput = $_GET['user']; 
$sql = "SELECT * FROM users WHERE username = '$userInput'";
$result = mysqli_query($conn, $sql);

Gibt der Angreifer nun etwa ‚ OR ‚1‘=’1 ein, könnte das SQL-Statement „gelogen“ werden, sodass er alle Datensätze erhält. Prepared Statements beheben dieses Problem, indem die Anwendungslogik vom Eingabewert klar getrennt wird.

2. Prinzip von Prepared Statements

Bei einem Prepared Statement gibst du dem Datenbankserver erst die SQL-Struktur mit Platzhaltern an. Anschließend bindest du die eigentlichen Werte separat. Der Datenbankserver behandelt die Werte als Daten, nicht als ausführbaren Code. Dadurch wird ein Einschleusen von zusätzlichen SQL-Befehlen verhindert.

3. Beispiel mit PDO

try {
    $pdo = new PDO("mysql:host=localhost;dbname=meinedb", "user", "pass");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $username = $_POST['username'];

    // 1) SQL mit Platzhalter
    $sql = "SELECT * FROM users WHERE username = :uname";

    // 2) Statement vorbereiten
    $stmt = $pdo->prepare($sql);

    // 3) Wert binden
    $stmt->bindValue(':uname', $username, PDO::PARAM_STR);

    // 4) Ausführen
    $stmt->execute();

    // Ergebnis abholen
    $userData = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($userData) {
        echo "Benutzer gefunden: " . $userData['username'];
    } else {
        echo "Kein Eintrag vorhanden.";
    }
} catch (PDOException $e) {
    echo "DB-Fehler: " . $e->getMessage();
}
  • :uname ist ein benannter Platzhalter. Wir geben dem DB-Server erst „SELECT … WHERE username = :uname“. Danach liefern wir $username.
  • Die DB kann so erkennen, dass „:uname“ ein reiner Datenwert ist – kein Code.

4. Beispiel mit mysqli

$conn = new mysqli("localhost", "user", "pass", "meinedb");
if ($conn->connect_error) {
    die("Verbindungsfehler: " . $conn->connect_error);
}

$username = $_POST['username'];

$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $conn->prepare($sql);

// "s" = string, bei int wäre "i", etc.
$stmt->bind_param("s", $username);
$stmt->execute();

$result = $stmt->get_result();
$data = $result->fetch_assoc();
if ($data) {
    echo "Benutzer gefunden: " . $data['username'];
} else {
    echo "Nicht gefunden.";
}

$stmt->close();
$conn->close();

Hier verwendest du ? als Platzhalter und bind_param(„s“, $username) für das Binden eines Strings. Bei Integern wäre das bind_param(„i“, $wert).

5. Bindungstypen (mysqli)

Bei mysqli unterscheidest du anhand von bind_param, welche Datentypen du erwartest:

  • „s“ = string
  • „i“ = integer
  • „d“ = double
  • „b“ = BLOB (Binärdaten)

6. Vorteile von Prepared Statements

  • SQL-Injection-Schutz: Werte werden als reine Daten behandelt, nicht als Teil der Befehlsstruktur.
  • Performance (theoretisch): Der Server kann das Statement vorbereiten. Bei mehrfacher Ausführung desselben Statements mit anderen Parametern kann das etwas Zeit sparen.
  • Klarere Trennung: Der Query-Aufbau (Struktur) und die konkreten Daten sind getrennt. Das macht den Code lesbarer.

7. Best Practices

Um Prepared Statements effizient einzusetzen, beachte:

  • Nutze PDO oder mysqli mit Prepared Statements für alle DB-Zugriffe.
  • Verwende niemals direkt übergebene $_GET/$_POST-Werte in einem SQL-String ohne Bindung.
  • Achte auf richtige Datentypen (String vs. int), besonders in mysqli (bind_param(„s“, …) vs. („i“, …)).
  • Ermittle mit fetch(), fetchAll() oder get_result() (mysqli) die Daten. Prüfe ggf. die Anzahl der Zeilen und Fehler.

Fazit

Prepared Statements sind der Schlüssel, um SQL-Injection und damit verbundene Sicherheitsrisiken in PHP abzuwehren. Indem du Platzhalter für Werte verwendest und sie getrennt vom SQL-Befehl bindest, verhinderst du, dass ein Angreifer schädlichen Code in deine Abfragen einschleusen kann. Mit PDO oder mysqli ist die Umsetzung recht einfach und gehört zum Grundrepertoire sicherer PHP-Programmierung.