• Sicherheit in PHP-Codes schaffen

    Achtung : Die MySQL-Erweiterung für PHP ist schon seit langer Zeit als veraltet gekennzeichnet! Diese sollte auf keinen Fall mehr genutzt werden.

    Man sollte unbedingt entweder auf MySQLi oder PDO umsteigen!


    Dieses Tutorial beschreibt, wie man häufig gemachte Fehler in puncto Sicherheit vermeidet oder ganz umgeht:

    1.) Request-Variablen vor Ausgabe "entschärfen"
    2.) Keine Request-Variablen in include-Anweisungen verwenden
    3.) Externe Variablen in SQL-Queries vorher escapen
    4.) Zugriff auf Request-Variablen nur über $_GET und $_POST


    1.) Request-Variablen vor Ausgabe "entschärfen"

    Man sollte immer alle Variablen aus externen Quellen - darunter auch Request-Variablen von $_GET, $_POST oder $_COOKIE - vor irgendeiner Ausgabe von eventuellen Schadcode befreien.

    Beispielsweise Konsequenz:

    Es gibt einen Artikel mit einer Kommentarfunktion. Jemand schleust jetzt aber als Kommentartext keinen normalen Text ein, sondern JavaScript. Wenn diese nicht escapt werden, also unschädlich gemacht werden, dann wird bei jedem Besuch von dieser Seite JavaScript ausgeführt, der dann z.B. Viren einschleusen kann.

    Entgegenwirkung:

    Je nach Art der Behandlung der Variable kann man anders vorgehen.

    Wenn z.B. eine Zahl übertragen werden sollte kann man mit der Funktion intval() die Variable zu einer Zahl konvertieren.

    Oder wenn man die Verwendung von HTML unterdrücken will, sollte man die Funktion htmlspecialchars() verwenden, wobei man als zweiten Parameter noch das Flag ENT_QUOTES angeben sollte, da sonst nur die doppelten Anführungsstriche umgewandelt werden (und nicht die einfachen).

    Außerdem ist noch die Funktion filter_input() (und deren "verwandte" Funktionen) zu erwähnen, die seit PHP Version 5.2 existiert.
    PHP-Code:
    $zahl $_GET['zahl'];
    $zahl intval($zahl); // Jetzt wirklich eine Zahl!

    $text $_POST['text'];
    $text htmlspecialchars($textENT_QUOTES); // Jetzt Text ohne evtl. HTML

    $text2 filter_input(INPUT_GET"text2"FILTER_SANITIZE_SPECIAL_CHARS); 
    Wichtig: Auch die Variablen, die man eigens per Weiterleitung geschaffen hat, sollten überprüft werden. Denn es besteht keine Garantie, dass diese jemand nicht verändert hat!


    2.) Keine Request-Variablen in include()-Anweisungen verwenden

    Beispielsweise Konsequenz:

    Es gibt eine GET-Variablen namens "tpl", die einen Dateinamen enthält, und im PHP-Skript wird diese mittels include() eingebunden.
    Jetzt verändert jemand diese Variable so, dass im PHP-Skript eine (PHP-)Datei von einem fremden Server eingebunden und ausgeführt wird.
    Diese kann jetzt z.B. alle Datenbanktabellen löschen, falls eine Datenbankverbindung besteht.

    Entgegenwirkung:

    Ein Whitelist-Array einführen, in dem alle erlaubten Dateinamen beinhaltet sind, und diese mit der GET-Variable vergleichen:
    PHP-Code:
    $allowed_sites = array("main.html""other.html", ...);

    // ...
    $tpl $_GET['tpl'];
    if (  !
    in_array$tpl$allowed_sites )  ) // wenn Seite nicht in Whitelist vorhanden,
      
    $tpl $allowed_sites[0]; // Standard nehmen

    include("templates/".$tpl); 



    3.) Externe Variablen in SQL-Queries vorher escapen

    Bevor man Variablen aus externen Quellen wie aus $_POST in Queries verwendet, sollte man sie vorher escapen!

    Beispielsweise Konsequenz (vgl. PHP-Manual):

    Es gibt einen Login und jemand meldet sich an:

    Benutzername : Mister X
    Passwort : ' OR ''='

    Wenn der Aufbau des MySQL-Query im PHP-Skript so ausschauen würde:
    PHP-Code:
    $query "SELECT * FROM users WHERE user='".$_POST['username']."' AND password='".$_POST['password']."'"
    Dann würde folgendes Query entstehen und es zulassen, dass sich Mister X anmelden kann:
    Code sql:
    1
    
    SELECT * FROM users WHERE USER='aidan' AND password='' OR ''=''


    Entgegenwirkung:

    Bei den meisten Datenbanksystem gibt es eine eigene Funktion dafür, z.B. bei MySQL mysql_real_escape_string()
    PHP-Code:
    $data $_GET['data'];
    $data mysql_real_escape_string($data);

    $query "...$data..."
    Wenn man nur Formularwerte für die Datenbank überträgt, kann man die o.g. Funktion relativ leicht auf alle Werte anwenden:
    PHP-Code:
    $data array_map('mysql_real_escape_string'$_GET); 
    Allerdings werden dadurch alle Werte durchlaufen. Weitere Möglichkeiten wären die DB-relevanten Daten zu extrahieren z.B anhand der Namenspräfix db_.


    4.) Zugriff auf Request-Variablen nur über $_GET und $_POST

    Man sollte auf Request-Variablen nur über die superglobalen Arrays $_GET und $_POST zugreifen!

    In älteren PHP-Versionen, wurde automatisch eine Variable mit dem Request-Namen angelegt, sofern register_globals aktiviert war.
    Somit konnte man von außen den Inhalt von Variablen in gewissem Maße verändern.

    Mittels dem Array $_REQUEST, das sowohl GET- als auch POST-Daten enthält, sollte man im Normalfall auch nicht auf irgendwelche Daten zugreifen, da man unter anderem sonst nicht sicherstellen kann auf welchem Wege die Daten übertragen werden. Einen Ausnahmefall stellt z.B. die Bereitstellung einer API dar.

    Entgegenwirkung:

    PHP-Code:
    $var $_GET['var'];
    $var2 $_POST['var2']; 

    Hier gibt es auch nützliche Tips und Ratschläge rund um PHP.

    Falls Ihr Anregungen oder Kritiken habt, schreibt sie einfach als Kommentar!
    =>Danke an rd4eva, yaslaw, Bratkartoffel, byf-ferdy, peper und tutorials.de


     
    Kommentare 11 Kommentare
    1. Avatar von rd4eva
      rd4eva -
      Zu 1. Man sollte bei htmlspecialchars das flags Argument ENT_QUOTES mit angeben da Standardmäßig nur doppelte nicht aber einfache Anführungszeichen "übersetzt" werden.
      Zu 2.
      if ( in_array( $tpl, $allowed_sites ) ) // wenn Seite nicht in Whitelist vorhanden
      Das Kommentar passt nicht zum Code
      Zu 4.
      In den neueren PHP-Versionen gibt es sowieso keinen anderen Weg auf diese Variablen zuzugreifen
      Es gibt noch $_REQUEST. Was man auch nicht verwenden sollte.
    1. Avatar von Yaslaw
      Yaslaw -
      filter_input() und filter_input_array() eignen sich sehr gut zum testen ob das was aus $_GET oder $_POST kommt mit den Erwartungen übereinstimmt.
      Wie man diese Funktionen um einen Default-Wert erweitert haben will, habe ich vor geraumer Zeil mal diese Funktionen erweitert
      filterInputDefaults
    1. Avatar von Bratkartoffel
      Bratkartoffel -
      $_REQUEST kann man schon verwenden, wenn es einem egal ist ob die Daten per POST oder GET kommen, zum Beispiel bei einer API.

      Ansonsten: Gute Zusammenfassung der gröbsten Punkte, Danke.

      Gruß
      BK
    1. Avatar von ComFreek
      ComFreek -
      Danke an alle für die Kritiken.

      Ich habe es jetzt verbessert.
    1. Avatar von byf-ferdy
      byf-ferdy -
      vllt könnte man noch nennen, dass man mit

      PHP-Code:
      $_POST array_map('mysql_real_escape_string'$_POST);
      $_GET array_map('mysql_real_escape_string'$_GET); 
      sehr einfach alle Werte absichern kann.

      mfg
      FRED
    1. Avatar von ComFreek
      ComFreek -
      @byf-ferdy:
      Danke. Habe ich mit reingeschrieben und dich auch noch unter den Danksagungen
    1. Avatar von peper
      peper -
      Danke Ist wirklich gut. Ich denke wenn das die meisten beachten würden wären einige Seiten sicherer

      Aber den Satz fand ich irgendwie merkwürdig...Oder liegt es an der Zeit?
      Fehlt da nicht ein "nicht"?
      Wichtig: Auch die Variablen, die man eigens per Weiterleitung geschaffen hat, sollten überprüft werden. Denn es besteht keine Garantie, dass diese jemand verändert!
    1. Avatar von ComFreek
      ComFreek -
      Hallo peper,

      (sorry das ich erst jetzt antworte, aber ich habe nur durch Zufall erfahren, dass jemand geschrieben hat)

      vielen Danke für deinen Kommentar !
      Ja, im Satz fehlte die Verneinung, habe sie ergänzt. Danke!


      MfG
      ComFreek
    1. Avatar von Frezl
      Frezl -
      @ComFreek: Danke für das Tutorial! Ist ne tolle Gedankenstütze, wenn ich mir wieder in Erinnerung rufen will, was ich alles beachten muss, damit mein PHP-Code sicher ist

      @byf-ferdy: Danke für den Tipp! Ich hab mir dafür extra ne Funktion geschrieben, die das ganze Array Eintrag für Eintrag durchläuft :-P Deine Variante ist deutlich einfacher

      Grüße,
      Frezl
    1. Avatar von ComFreek
      ComFreek -
      Vielen Dank Frezl für deinen Kommentar. Freut mich
    1. Avatar von genodeftest
      genodeftest -
      Danke! Aber das Beispiel zu filter_input ist unvollständig oder?