Was ist SQL-Injection und wie kann man es in PHP-Anwendungen verhindern?

Sie glauben also, Ihre SQL-Datenbank ist performant und sicher vor sofortiger Zerstörung? Nun, SQL Injection ist anderer Meinung!

Ja, wir sprechen von sofortiger Zerstörung, denn ich möchte diesen Artikel nicht mit der üblichen lahmen Terminologie von „Sicherheitserhöhung“ und „Verhinderung böswilligen Zugriffs“ eröffnen. SQL Injection ist ein so alter Trick im Buch, dass jeder, jeder Entwickler, ihn sehr gut kennt und weiß, wie er verhindert werden kann. Abgesehen von dem einen Mal, wenn sie einen Fehler machen, und die Ergebnisse können geradezu katastrophal sein.

Wenn Sie bereits wissen, was SQL Injection ist, können Sie gerne zur zweiten Hälfte des Artikels springen. Aber für diejenigen, die gerade im Bereich der Webentwicklung aufsteigen und davon träumen, höhere Positionen zu übernehmen, ist eine Einführung angebracht.

Was ist SQL-Injection?

Der Schlüssel zum Verständnis von SQL Injection liegt in seinem Namen: SQL + Injection. Das Wort „Injektion“ hat hier keine medizinischen Konnotationen, sondern ist die Verwendung des Verbs „injizieren“. Zusammen vermitteln diese beiden Wörter die Idee, SQL in eine Webanwendung zu integrieren.

SQL in eine Webanwendung einfügen . . . Hmmm . . . Tun wir das nicht sowieso? Ja, aber wir wollen nicht, dass ein Angreifer unsere Datenbank steuert. Lassen Sie uns das anhand eines Beispiels verstehen.

Angenommen, Sie erstellen eine typische PHP-Website für einen lokalen E-Commerce-Shop und entscheiden sich daher, ein Kontaktformular wie dieses hinzuzufügen:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

Und nehmen wir an, die Datei send_message.php speichert alles in einer Datenbank, damit die Ladenbesitzer später Benutzernachrichten lesen können. Es kann einen Code wie diesen haben:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Sie versuchen also zuerst zu sehen, ob dieser Benutzer bereits eine ungelesene Nachricht hat. Die Abfrage SELECT * from messages where name = $name scheint einfach genug, oder?

FALSCH!

In unserer Unschuld haben wir die Türen zur sofortigen Zerstörung unserer Datenbank geöffnet. Dazu müssen beim Angreifer folgende Bedingungen erfüllt sein:

  • Die Anwendung läuft auf einer SQL-Datenbank (heute fast jede Anwendung)
  • Die aktuelle Datenbankverbindung hat die Berechtigungen „Bearbeiten“ und „Löschen“ für die Datenbank
  • Die Namen der wichtigen Tabellen können erraten werden

Der dritte Punkt bedeutet, dass Sie jetzt, da der Angreifer weiß, dass Sie einen E-Commerce-Shop betreiben, die Bestelldaten sehr wahrscheinlich in einer Bestelltabelle speichern. Mit all dem bewaffnet, muss der Angreifer nur Folgendes als seinen Namen angeben:

  So verwenden Sie die neue Kompass-App auf der Apple Watch

Joe; Bestellungen abschneiden;? Jawohl! Mal sehen, was aus der Abfrage wird, wenn sie vom PHP-Skript ausgeführt wird:

SELECT * FROM Nachrichten WHERE Name = Joe; Bestellungen kürzen;

Okay, der erste Teil der Abfrage hat einen Syntaxfehler (keine Anführungszeichen um „Joe“), aber das Semikolon zwingt die MySQL-Engine, mit der Interpretation eines neuen zu beginnen: Truncate Orders. So ist auf einen Schlag die gesamte Bestellhistorie weg!

Nachdem Sie nun wissen, wie SQL Injection funktioniert, ist es an der Zeit, sich anzusehen, wie Sie es stoppen können. Die beiden Bedingungen, die für eine erfolgreiche SQL-Injection erfüllt sein müssen, sind:

  • Das PHP-Skript sollte über Änderungs-/Löschberechtigungen für die Datenbank verfügen. Ich denke, das gilt für alle Anwendungen, und Sie werden Ihre Anwendungen nicht schreibgeschützt machen können. 🙂 Und raten Sie mal, selbst wenn wir alle Änderungsrechte entfernen, kann die SQL-Einschleusung immer noch jemandem erlauben, SELECT-Abfragen auszuführen und die gesamte Datenbank anzuzeigen, einschließlich sensibler Daten. Mit anderen Worten, das Reduzieren der Datenbankzugriffsebene funktioniert nicht, und Ihre Anwendung benötigt es trotzdem.
  • Benutzereingaben werden verarbeitet. Die SQL-Injection kann nur funktionieren, wenn Sie Daten von Benutzern akzeptieren. Auch hier ist es nicht praktikabel, alle Eingaben für Ihre Anwendung zu stoppen, nur weil Sie sich Sorgen über die SQL-Einschleusung machen.
  • SQL-Injection in PHP verhindern

    Nun, da Datenbankverbindungen, Abfragen und Benutzereingaben Teil des Lebens sind, wie können wir SQL-Injection verhindern? Zum Glück ist es ziemlich einfach und es gibt zwei Möglichkeiten, dies zu tun: 1) Benutzereingaben bereinigen und 2) vorbereitete Anweisungen verwenden.

    Benutzereingaben bereinigen

    Wenn Sie eine ältere PHP-Version verwenden (5.5 oder niedriger, und das passiert häufig bei Shared Hosting), ist es ratsam, alle Ihre Benutzereingaben über eine Funktion namens mysql_real_escape_string() laufen zu lassen. Grundsätzlich entfernt es alle Sonderzeichen in einer Zeichenfolge, sodass sie ihre Bedeutung verlieren, wenn sie von der Datenbank verwendet werden.

    Wenn Sie beispielsweise eine Zeichenfolge wie I’m a string haben, kann das einfache Anführungszeichen (‚) von einem Angreifer verwendet werden, um die erstellte Datenbankabfrage zu manipulieren und eine SQL-Injection zu verursachen. Wenn Sie es durch mysql_real_escape_string() laufen lassen, wird I’m ein String erzeugt, der dem einfachen Anführungszeichen einen umgekehrten Schrägstrich hinzufügt und es maskiert. Infolgedessen wird der gesamte String nun als harmloser String an die Datenbank übergeben, anstatt an der Abfragemanipulation teilnehmen zu können.

      Automatisieren Sie HR-Aufgaben mit Freshteam und verbessern Sie die Effizienz

    Dieser Ansatz hat einen Nachteil: Es handelt sich um eine sehr, sehr alte Technik, die mit den älteren Formen des Datenbankzugriffs in PHP einhergeht. Ab PHP 7 gibt es diese Funktion gar nicht mehr, womit wir bei unserer nächsten Lösung wären.

    Verwenden Sie vorbereitete Anweisungen

    Vorbereitete Anweisungen sind eine Möglichkeit, Datenbankabfragen sicherer und zuverlässiger zu machen. Die Idee ist, dass wir, anstatt die rohe Abfrage an die Datenbank zu senden, der Datenbank zuerst die Struktur der Abfrage mitteilen, die wir senden werden. Das meinen wir mit dem „Vorbereiten“ einer Erklärung. Sobald eine Anweisung vorbereitet ist, übergeben wir die Informationen als parametrisierte Eingaben, damit die Datenbank „die Lücken füllen“ kann, indem sie die Eingaben in die zuvor gesendete Abfragestruktur einfügt. Dadurch wird den Eingaben jegliche besondere Macht genommen, was dazu führt, dass sie im gesamten Prozess als bloße Variablen (oder Nutzlasten, wenn Sie so wollen) behandelt werden. So sehen vorbereitete Anweisungen aus:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $firstname = "John";
    $lastname = "Doe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Mary";
    $lastname = "Moe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Julie";
    $lastname = "Dooley";
    $email = "[email protected]";
    $stmt->execute();
    
    echo "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Ich weiß, dass der Prozess unnötig komplex klingt, wenn Sie mit vorbereiteten Anweisungen noch nicht vertraut sind, aber das Konzept ist die Mühe wert. Hier ist eine schöne Einführung dazu.

    Für diejenigen, die bereits mit der PDO-Erweiterung von PHP vertraut sind und damit vorbereitete Anweisungen erstellen, habe ich einen kleinen Ratschlag.

    Warnung: Seien Sie beim Einrichten von PDO vorsichtig

    Wenn wir PDO für den Datenbankzugriff verwenden, können wir uns in ein falsches Sicherheitsgefühl versetzen. „Ah, nun, ich verwende PDO. Jetzt brauche ich an nichts anderes mehr zu denken“ – so denken wir im Allgemeinen. Es ist wahr, dass PDO (oder vorbereitete MySQLi-Anweisungen) ausreicht, um alle Arten von SQL-Injection-Angriffen zu verhindern, aber Sie müssen bei der Einrichtung vorsichtig sein. Es ist üblich, einfach Code aus Tutorials oder aus Ihren früheren Projekten zu kopieren und einzufügen und weiterzumachen, aber diese Einstellung kann alles rückgängig machen:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Diese Einstellung weist PDO an, vorbereitete Anweisungen zu emulieren, anstatt tatsächlich die Funktion für vorbereitete Anweisungen der Datenbank zu verwenden. Folglich sendet PHP einfache Abfragezeichenfolgen an die Datenbank, selbst wenn Ihr Code so aussieht, als würde er vorbereitete Anweisungen erstellen und Parameter setzen und all das. Mit anderen Worten, Sie sind genauso anfällig für SQL-Injection wie zuvor. 🙂

      So laden Sie Microsoft Outlook-E-Mails herunter

    Die Lösung ist einfach: Stellen Sie sicher, dass diese Emulation auf „false“ gesetzt ist.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Jetzt ist das PHP-Skript gezwungen, vorbereitete Anweisungen auf Datenbankebene zu verwenden, wodurch alle Arten von SQL-Injection verhindert werden.

    Verwendung von WAF verhindern

    Wussten Sie, dass Sie Webanwendungen auch mit WAF (Web Application Firewall) vor SQL-Injection schützen können?

    Nun, nicht nur SQL-Injection, sondern viele andere Layer-7-Schwachstellen wie Cross-Site-Scripting, fehlerhafte Authentifizierung, Cross-Site-Fälschung, Datenoffenlegung usw. Entweder Sie können selbst gehostete wie Mod Security oder Cloud-basiert wie folgt verwenden.

    SQL-Injection und moderne PHP-Frameworks

    Die SQL-Injection ist so verbreitet, so einfach, so frustrierend und so gefährlich, dass alle modernen PHP-Webframeworks mit eingebauten Gegenmaßnahmen ausgestattet sind. In WordPress haben wir zum Beispiel die Funktion $wpdb->prepare(), während, wenn Sie ein MVC-Framework verwenden, es die ganze Drecksarbeit für Sie erledigt und Sie nicht einmal daran denken müssen, SQL-Injection zu verhindern. Etwas nervig ist, dass man in WordPress Statements explizit vorbereiten muss, aber hey, die Rede ist von WordPress. 🙂

    Wie auch immer, mein Punkt ist, dass die modernen Webentwickler nicht über SQL-Injection nachdenken müssen und sich daher dieser Möglichkeit nicht einmal bewusst sind. Selbst wenn sie eine Hintertür in ihrer Anwendung offen lassen (vielleicht ist es ein $_GET-Abfrageparameter und alte Gewohnheiten, eine schmutzige Abfrage abzufeuern), können die Ergebnisse katastrophal sein. Es ist also immer besser, sich die Zeit zu nehmen, tiefer in die Grundlagen einzutauchen.

    Fazit

    SQL Injection ist ein sehr unangenehmer Angriff auf eine Webanwendung, der jedoch leicht vermieden werden kann. Wie wir in diesem Artikel gesehen haben, reicht es aus, bei der Verarbeitung von Benutzereingaben (SQL Injection ist übrigens nicht die einzige Bedrohung, die die Verarbeitung von Benutzereingaben mit sich bringt) und der Abfrage der Datenbank vorsichtig zu sein. Allerdings arbeiten wir nicht immer an der Sicherheit eines Web-Frameworks, daher ist es besser, sich dieser Art von Angriffen bewusst zu sein und nicht darauf hereinzufallen.