10.02.19 3464 Views 9

credit: ©peshkov

PHP Tutorial #28

SQL-Injections mit Prepared Statements abwehren

Da Besucher einer Website über Formulare Daten senden können, sind sie Einfallstore für Hackerangriffe auf unsere Datenbank. Es ist darum sehr wichtig, die Formulare sicher zu entwerfen und vor Angriffen zu schützen. Denn diese Angriffe sind ohne Schutzmaßnahmen einfacher durchzuführen, als man glaubt!

Download als eBook

Beispiel für ein Prepared Statement:

// Prepared Statement vorbereiten
$sql = $connect->prepare("SELECT * FROM Nutzer WHERE Username=? AND Password=?");

// Parameter binden
$sql->bind_param("ss", $eingabeUser, $eingabePW);

// Statement ausführen
$sql->execute();

    Inhaltsverzeichnis

  1. Das Problem
  2. Die Lösung

Das Problem

SQL Injections sind eine der am meist verbreiteten Web-Hacking-Techniken überhaupt. SQL Injection bedeutet, dass schädlicher Code in Form von SQL-Statements über die Nutzereingabe in Websiten-Formularen eingeschleust wird, um Zugriff auf die Datenbank zu erhalten. Im schlimmsten Fall kann so die Datenbank zerstört oder Daten gestohlen werden.

Wir zeigen Euch zunächst, wie SQL Injections funktionieren. Anschließend kümmern wir uns um die Lösung zur Abwehr solcher Angriffe.

Das Prinzip

Wie gesagt: SQL Injections nutzen Benutzereingabe-Felder aus, um auf diesem Weg Code zur Schädigung unserer Datenbank einzuschleusen.

Simulieren wir das Ganze also einmal. Hierzu haben wir eine Mini-Tabelle namens benutzer mit ein paar Datensätzen in einer Datenbank angelegt:

MariaDB [falconbyte]> SELECT * FROM benutzer;
+----------+----------+
| Username | Password |
+----------+----------+
| Admin    | Tz12P%y  |
| Stefan   | 12345    |
| MrBailey | miau1    |
| Neo      | Trinity1 |
| BurnXout | faustP$  |
+----------+----------+

Über ein HTML-Eingabeformular und ein PHP-Programm (beides in der Datei sicherheit.php), können wir eine Login-Passwort-Abfrage erzeugen:

PHP SQL Injection

Wenn die eingegebene Login/ Passwort-Kombination des Formulars mit einem Datensatz aus der Datenbank übereinstimmt, ist ein Login "erfolgreich" und wir erhalten die Nutzerdaten (Username und Passwort) als Bestätigung auf dem Bildschirm anzeigt.

Der vollständige Code von sicherheit.php sieht folgendermaßen aus:

<html><head></head><body>
<h1>Login Bereich</h1>

<form action="sicherheit.php" method="post">
    <p><input name="username"> Login</p>
    <p><input name="password"> Password</p>
    <input type="submit" name="submit">
</form>

<?php

$connect = mysqli_connect("", "root", "", "falconbyte");

if(isset($_POST["submit"])){

    $eingabeUser = $_POST["username"];
    $eingabePW = $_POST["password"];

    $sql = "SELECT * FROM benutzer WHERE Username = '$eingabeUser' AND Password = '$eingabePW'";
    $result = mysqli_query($connect, $sql);

    while($dsatz = mysqli_fetch_assoc($result)) {

        echo "Hallo " . $dsatz["Username"] . " - PW: " .
            $dsatz["Password"] . "<br>";

    }}
?>
</body></html>                

In den Variablen $eingabeUser (Zeile 16) und $eingabePW (Zeile 17) speichern wir die Nutzereingabe. Auf dieser Grundlage wird dann in Zeile 19 der String für die SQL-Abfrage ($sql) zusammengesetzt:

"SELECT * FROM benutzer WHERE Username = '$eingabeUser' AND Password = '$eingabePW'"

Das liest man dann so:

  • Zeige alle Datensätze der Tabelle benutzer an, wo der Username gleich $eingabeUser UND das Password gleich $eingabePW ist.

Geben wir in das Eingabeformular nun Stefan und 12345 ein, erhalten wir am Bildschirm erwartungsgemäß das hier angezeigt:

Hallo Stefan - PW: 12345

Jetzt hacken

Jetzt knacken wir das Teil, indem wir eine SQL Injection einsetzen 😈

Der Hacker kann nämlich über das HTML-Eingabeformular den SQL-Abfrage-String manipulieren.

So kann er zum Beispiel Zugriff auf alle Nutzerdaten der Tabelle erhalten, wenn er in das zweite Eingabefeld folgenden Code eingibt:

bla' OR '1'='1

Der SQL-Abfrage-String $sql wird dann so zusammengesetzt:

"SELECT * FROM benutzer WHERE Username = 'Stefan' AND Password = 'bla' OR '1'='1'"

Das ist richtig bad-ass! Denn diese SQL-Abfrage bedeutet knallhart:

  • Zeige alle Datensätze der Tabelle benutzer an, (wo der Username gleich $eingabeUser ist UND wo das Password gleich $eingabePW ist) ODER wenn '1' gleich '1' ist.

Entscheidend ist: '1'='1' ist immer eine wahre Aussage. Damit ist die gesamte WHERE-Bedingung auch wahr. Das hat zur Folge, dass alle Datensätze der Tabelle angezeigt werden.

Wir wurden gehackt; alle Nutzerdaten wurden preisgegeben:

Hallo Admin - PW: Tz12P%y
Hallo Stefan - PW: 12345
Hallo MrBailey - PW: miau1
Hallo Neo - PW: Trinity1
Hallo BurnXout - PW: faustP$

Da wir als Seitenbetreiber für die uns anvertrauten Nutzerdaten verantwortlich sind, müssen wir deren Sicherheit garantieren. Ohne Schutz vor SQL Injections ist die Datensicherheit aber nicht gewährleistet. Beheben wir also diese Sicherheitslücke.

Die Lösung

Um fiese SQL Injections abzuwehren, können wir auf "die beste Erfindung der Datenbankprogrammierung" (Jan Teriete) zurückgreifen: Die Prepared Statements.

Der PHP-Code der Datei sicherheit.php wird sich nun komplett ändern. Wir zeigen Euch jetzt den neuen PHP-Code, der mit Prepared Statements arbeitet, und besprechen ihn dann schrittweise:

<html><head></head><body>
<h1>Login Bereich</h1>

<form action="sicherheit.php" method="post">
    <p><input name="username"> Login</p>
    <p><input name="password"> Password</p>
    <input type="submit" name="submit">
</form>

<?php

$connect = mysqli_connect("", "root", "", "falconbyte");

if(isset($_POST["submit"])){

    $eingabeUser = $_POST["username"];
    $eingabePW = $_POST["password"];

    $sql = $connect->prepare("SELECT * FROM Nutzer WHERE Username=? AND Password=?");
    $sql->bind_param("ss", $eingabeUser, $eingabePW);
    $sql->execute();
    $sql->bind_result($eingabeUser, $eingabePW);

    while ($sql->fetch()) {
        echo $eingabeUser . " " . $eingabePW;
    }

    $sql->close();
}

?>
</body></html>

  • SQL-Statement preparen: Mit ->prepare() bereiten wir das SQL-Statement in Zeile 16 vor. Die Fragezeichen bei Username=? und Password=? werden erst später mit den entsprechenden Werten belegt.
    Das SQL-Statement ist jetzt schon komplett. Der Clou ist, dass das, was als Fragezeichen später hinterherkommt (also die aufgegriffene Nutzereingabe), in jedem Fall als String behandelt wird - gleichgültig, was der Nutzer eingibt. Das Einschleusen von schädlichen Code ist damit unmöglich gemacht.
  • Parameter binden: Als nächstes binden wir in Zeile 17 mit ->bind_param() die Parameter. Das erste Argument in Klammern ist der String "ss" (also zweimal "s"), denn wir fangen zwei String-Benutzereingaben auf. Anschließend rufen wir das $_POST-Array auf, um die beiden Nutzereingaben aufzugreifen.
  • Statement ausführen: Nun führen wir das Statement mit ->execute() aus.
  • Ergebnis binden: Wir binden jetzt Variablen ($result_username und $result_password) an die vorbereitete Anweisung (prepared statement). Damit wird das Ergebnis der Abfrage abgelegt.
  • Ausgabe: Mit Hilfe der ->fetch()-Funktion in Kombination mit einer while-Schleife, können wir das Ergebnis ausgeben.

Wenn wir jetzt versuchen, eine SQL Injection durchzuführen, wird dies schlicht und einfach nicht mehr funktionieren. Unsere Datenbank ist damit erst einmal ausreichend gegen unbefugte Zugriffe gesichert.

Ein weiterer positiver Nebeneffekt von prepared statements ist, dass der Code einfacher zu lesen ist. Insbesondere die nervige String-Verkettung, die wir anwenden, um Variablen in den Abfrage-String einzubauen ist kein Thema mehr.

Java lernen

text text

PHP Lernen

zur PHP

JavaScript lernen

move nove move

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2023 Stefan E. Heller

Impressum | Datenschutz | Changelog

Falconbyte Youtube Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen