TU Chemmnitz

Universitätsrechenzentrum

TU Chemnitz > URZ > Zeitung > Ausgabe 2/2004

Sicheres Programmieren mit PHP (Teil 2)

In den "Mitteilungen des URZ" 1/2004 haben wir bereits Hinweise zum sicheren Programmieren mit PHP gegeben. In diesem Artikel sollen nun weitere Problemfälle und Lösungsmöglichkeiten diskutiert werden.

Cross Site Scripting (XSS)

Unter "Cross Site Scripting" versteht man das "Einschleusen" von fremden HTML-Kode auf eine WWW-Seite. Damit kann ein Angreifer folgende Ziele verfolgen:

Betroffen von solchen XSS-Problemen sind prinzipiell alle PHP- und CGI-Programme, die externe Parameter (wie Formulareingaben) verarbeiten, z.B. Gästebücher, Such- oder Anmeldeformulare.

Betrachen wir zunächst ein schlechtes Beispiel:

  <form action="..."><input name="name" ... > ... </form>
   <?php  # Gib den eingetippten Namen aus:
      print "Hallo " . $_REQUEST['name']; 
   ?>
Unerwünschte Effekte treten ein, wenn der URL so aussieht:

.../script.php?name=<h1>Gross-und-fett!

Dann würde durch die PHP-Anweisung ausgegeben:

     Hallo <h1>Gross-und-fett
Das HTML-Tag <h1> würde die Ausgabe ganz empfindlich stören. Natürlich könnten auch noch "wüstere" HTML-Tags (z.B. Laden externer Bilder) oder gar JavaScript-Kode abgearbeitet werden.

Erinnern wir uns an den "Merksatz" aus dem Artikel in 1/2004 - der kann nicht oft genug genannt werden:

Tipp! Vertrauen Sie keinen Werten, die über Browsereingaben, den URL oder Cookies in das PHP-Skript gelangen. Alle externen Parameter, selbst wenn sie aus versteckten Feldern oder Auswahlmenüs kommen, müssen einer Plausibilitätsprüfung unterworfen werden, bevor sie im Programm verwendet werden.

Also betrachten wir für unser Beispiel auch den Wert für name kritisch:

  <form action="..."><input name="name" ... > ... </form>
   <?php  # Gib den eingetippten Namen aus
          # evtl. HTML-Kode kodieren wir mit der Funktion htmlspecialchars:
      print "Hallo " . htmlspecialchars($_REQUEST['name']); 
   ?>
Wenn in diesem Fall der eingetippte Name HTML-Kode enthält, wird er "unschädlich" gemacht. In obigem Beispiel wird ausgegeben:
     Hallo &lt;h1&gt;Gross-und-fett
Und das bringt einen WWW-Browser nicht durcheinander - auch JavaScript wird so unschädlich gemacht. Alternativ können Sie die Funktion strip_tags einsetzen, um alle HTML- und PHP-Tags aus einer Zeichenkette zu entfernen.

SQL Injection

PHP eignet sich hervorragend, um mit WWW-Seiten Daten aus Datenbanken anzuzeigen oder zu ändern. Aber auch hier gilt es, Sicherheitsaspekte zu beachten, will man nicht böse Überraschungen erleben.

SQL Injection ist eine Technik, mit der böswillige Angreifer über WWW SQL-Kommandos erstellen oder existierende verändern, um versteckte Daten sichtbar zu machen, zu verändern oder zu löschen.

Betrachten wir wieder ein schlechtes Beispiel und ein Angriffs-Szenario:

   <?php 
     #Datenbankabfrage an Hand einer ID aus dem URL
     $id = $_REQUEST['id'];
     $result = mysql_query("SELECT * from tabelle WHERE id=$id");
     # Anzeige des Ergebnisse ...
   ?>

"Erwartet" wird ein URL der Form .../script.php?id=42, wodurch aus der Datenbank der Tabelleneintrag mit der id=42 gelesen wird. Was aber, wenn der URL so geschrieben wird: .../script.php?id=42;SHOW+TABLES
Die Pluszeichen wandelt der WWW-Server in Leerzeichen um, und der arglose mysql_query -Aufruf führt plötzlich noch eine zweite Aktion aus frown

Auch in diesen Fall erinnern wir uns an den o.g. Merksatz: Wir müssen alle Eingabewerte überprüfen, auch wenn das etwas Mühe macht. PHP hält dafür aber eine Menge an Möglichkeiten bereit.

Wird als Wert eine Integer-Zahl erwartet, so können Sie dies prüfen mit der Funktion is_numeric() oder Sie setzen den Typ mittels settype() explizit auf integer:

   <?php 
     #Datenbankabfrage an Hand einer ID aus dem URL
     $id = $_REQUEST['id'];
     settype($id, 'integer');   # Zwangsumwandlung in Zahl
     $result = mysql_query("SELECT * from tabelle WHERE id=$id");
     # Anzeige des Ergebnisse ...
     }
   ?>

Werden als externe Parameter Zeichenketten erwartet, gerät die Prüfung meist etwas aufwändiger. Hier ist eine Prüfung auf plausible Werte angebracht (siehe Beispiel mit Dateinamen aus vorgegebener Menge in Artikel aus 1/2004). Zumindest sollten Sie Zeichen kodieren, die für SQL eine Sonderbedeutung haben: Semikolon, einfacher und doppelter Anführungsstrich usw.

   <?php 
     #Datenbankabfrage an Hand eines eingetippten Suchwortes
     $suchwort = $_REQUEST['wort'];

     # Entferne rigoros alle Semikolons
     $suchwort = ereg_replace(';', '', $suchwort);
     if (get_magic_quotes_gpc() == 0) {
        # Falls die System-PHP-Einstellungen es noch nicht automatisch tun,
        # kodiert die Funktion addslashes weitere SQL-Sonderzeichen:
        $suchwort = addslashes($suchwort);
     }
     $result = mysql_query("SELECT * from tabelle WHERE name like \"$suchwort\"");
     # Anzeige des Ergebnisse ...
     }
   ?>

Am besten ist es, Sie lehnen dubios erscheinende Abfragen von vornherein ab:

   <?php 
     #Datenbankabfrage an Hand eines eingetippten Suchwortes
     $suchwort = $_REQUEST['wort'];

     $laenge = strlen($suchwort);
     # Lehne zu kurze/lange oder ein Semikolon enthaltende Suchwörter ab:
     if ($laenge <= 0 || $laenge > 100 || ereg(';', $suchwort)) {
        print "Fehler im Suchwort ...";
        exit;
     }
   ?>

Vor einer Abfrage von Daten muss zunächst die Verbindung zur Datenbank hergestellt werden, wozu neben Servernamen auch der Datenbank-Nutzer und das Passwort anzugeben ist, z.B. für MySQL:

   <?php 
     #Datenbank-Verbindung herstellen:
     # das @ vorm Funktionsaufruf verhindert die eingebaute Fehlermeldung
     $db = @mysql_connect('mysql.hrz.tu-chemnitz.de', 'dbnutzer', 'geheim')
           or die ("Keine Verbindung zur Datenbank möglich");
   ?>

Hier haben wir das Problem, dass das Passwort des Datenbank-Benutzers im PHP-Skript stehen muss. Das birgt natürlich eine Gefahr, wenn es in falsche Hände gelangt. Als Grundregel beachten Sie: Verwenden Sie einen Datenbank-Benutzer, dessen Rechte entsprechend Ihrer Anwendung limitiert sind. Wenn Sie via PHP-Skript nur SELECT-Anweisungen ausführen, verwenden Sie den DB-Nutzer, der nur leseberechtigt ist.

Auf unseren zentralen WWW-Servern haben wir keine sichere Methode, ein böswilliges Ausspähen durch andere PHP-Skripts auf dem gleichen Server zu verhindern. Wir setzen voraus, dass alle WWW-Autoren verantwortungsbewusst mit ihrer Aufgabe umgehen.

Für Anwendungen und Projekte mit höheren Sicherheitsanforderungen sind dedizierte WWW-Server vorzusehen. Wir erproben momentan ein solches System und planen, dies demnächst als Dienstleistung anzubieten.

Weitere Hinweise: http://www.tu-chemnitz.de/docs/php/security.database.html


Frank Richter, April 2004