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:
- Falsche Anzeige der WWW-Seite: Verunstaltung (z.B. unleserlich), Anzeige
fremder Inhalte (Texte, Bilder)
- Ausspähen von Daten, z.B. Zugangsdaten (insb. Cookies), durch Einbringen
von JavaScript-Kode
- Ausführen von fremden Programm-Kode (insb. JavaScript)
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:
 |
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 <h1>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
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