PHP Blog

Sichere Formulare in PHP

Da die Besucher einer Website über Formulare aber Daten senden können, sind sie Einfallstore für Hackerangriffe. 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!

Stefan 03.01.2019

Infos zum Artikel

Kategorie PHP
Autor Stefan
Datum 10.02.2019

Thema in Kurzform:

  • Cross-Site-Scripting (XSS) wird mit der PHP-Funktion htmlentities bekämpft.
  • SQL Injections können wir mit sog. Prepared Statements abwehren.

Cross-Site-Scripting (XSS)

Eine große Gefahr, wenn Sie Formulare mit Benutzereingaben auf einer Website ermöglichen, besteht im Cross-Site-Scripting (XSS).

Worum geht es? Bei XSS-Attacken versucht der Hacker, fremden Code (oftmals: JavaScript) auf die Website einzuschleusen.

Ohne Schutz vor XSS-Angriffen, benutzt der Angreifen einfach eines Ihrer Eingabefelder und jagt den Code rein, der auch gleich ausgeführt wird.

Der folgende Quelltext zeigt ein einfaches HTML-Eingabeformular, das vollkommen ungeschützt ist:

<html><head></head><body>

<h1>Namen eingeben</h1>

<form action="sicherheit.php" method="post">
    <input name="eingabe">
    <input type="submit" name="submit">
</form>

<?php

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

    $eingabe = $_POST["eingabe"];
    echo $eingabe;

}
?>

</body></html>                                

Wird in das Eingabefeld (input name="eingabe") anstatt des Namens etwa folgender Code eingegeben:

<script>
   window.location="https://falconbyte.net";
</script>                

Dann wird dieser Code auf der Seite ausgeführt (hier: leitet auf eine andere URL um - das kann sehr übel ausgehen und ist nur ein Beispiel für die Möglichkeiten eines Angriffs!).

Die Lösung: htmlentities

Die PHP-Funktion htmlentities wandelt alle Sonderzeichen wie < und > in HTML-Codes (Entities) um. Damit kann JavaScript nicht mehr ausgeführt werden. Wenn wir zusätlich auch einfache Anführungszeichen ' in HTML entities umwandeln wollen, tragen wir als zweites Argument der Funktion den Wert ENT_QUOTES ein:

<?php
    if(isset($_POST["submit"])){
        $eingabe = htmlentities($_POST['eingabe'], ENT_QUOTES);
    }
?>

Der ins Eingabefeld getippte JavaScript-Code wird dann folgendermaßen erkannt und als harmloser String behandelt (die Sonderzeichen sind umgewandelt:):

&lt;script&gt; window.location=&quot;https://falconbyte.net&quot; &lt;/script&gt;

Damit kann einer, der Euch Böses will, in Eure Web-Formulare eingeben, was er will: Es wird als harmloser String gewertet.

Man muss dazu sagen, dass alle modernen Browser heute einen eingebauten Schutz vor XSS-Attacken haben. Google-Chrome etwa erkennt Script-Code in Formularfeldern. Der eingebaute XSS-Auditor blockiert dann die Ausführung ("Chrome hat auf dieser Seite ungewöhnlichen Code erfasst und diese Seite daher blockiert").

Trotzdem: Wenn Sie sich darauf nicht verlassen und die Sicherheit der Website selbst in die Hand nehmen wollen, sollten Sie die PHP-Funktion htmlentities verwenden.

SQL Injection

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.

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: Prepared Statements

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"])){

    $sql = $connect->prepare("SELECT * FROM benutzer WHERE Username=? AND Password=?");
    $sql->bind_param("ss", $_POST["username"], $_POST["password"]);
    $sql->execute();
    $sql->bind_result($result_username, $result_password);

    while ($sql->fetch()){
        echo $result_username . " " . $result_password;
    }

    $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.

Die Klasse Object

Erfahren Sie alles über die Mutter aller Klassen

Schleifen in Java

Schleifenstrukturen gehören zu den wichtigsten Grundlagen in der Programmierung überhaupt.

Verzweigungen in Java

Eine zentrale Notwendigkeit der Programmierung sind Verzweigungen.

FALCONBYTE.NET

Handmade with 🖤️

© 2018, 2019 Stefan E. Heller

Impressum | Datenschutz

facebook programmieren lernen twitter programmieren lernen youtube programmieren lernen