|
|
Universitätsrechenzentrum |
|
|
|
 |
Sicheres Programmieren mit PHP
PHP-Programmierern werden einige Hinweise zur sicheren Programmierung gegeben. Zugleich kündigen wir Änderungen in der Konfiguration der WWW-Server der TU Chemnitz an.
Die zentralen WWW-Server der TU Chemnitz
www.tu-chemnitz.de und www-user.tu-chemnitz.de
unterstützen die Skriptsprache PHP.
Diese Programmiersprache ist recht einfach zu erlernen und bietet
einen großen Funktionsumfang (siehe
http://www.tu-chemnitz.de/urz/www/php/).
Mit PHP lassen sich komplexere Aufgaben recht einfach lösen,
sei es die Formularauswertung oder
Zugriffe auf Datenbanken zur Erzeugung dynamischer WWW-Inhalte.
Die in Dokumenten integrierten PHP-Befehle werden dabei auf dem WWW-Server
abgearbeitet.
Sicherheitsaspekte spielen auch bei der PHP-Programmierung
eine immer größere Rolle, weil unsere Server
- von einer großen Zahl von WWW-Autoren benutzt werden und
- schützenswerte Daten enthalten, die nur Berechtigten
zugänglich gemacht werden sollen.
In diesem Artikel werden an Hand von Beispielen
einige Hinweise zur sicheren Programmierung gegeben.
Gleichzeitig kündigen wir Änderungen in unserer PHP-Konfiguration an,
die mit Einführung von Apache 2 (vermutlich Frühjahr 2004) wirksam werden.
Diese Änderungen dienen der Erhöhung der Sicherheit, bedingen u.U. aber
Änderungen in Ihren PHP-Skripten.
Einen Hinweis möchte ich voran stellen:
Eine absolute Sicherheit werden wir auf den allgemeinen WWW-Servern nicht
erreichen können.
Sensible Daten dürfen deshalb nur über geeignete dedizierte Server
und geeignete Verschlüsselungsverfahren zugänglich gemacht werden.
Der WWW-Server interpretiert nur PHP-Anweisungen, die innerhalb spezieller
Tags stehen:
<?php echo "Hallo"; ?>.
Bislang unterstützen wir auch die Kurzform <? ... ?>
Da dies jedoch nicht XML- und XHTML-konform ist, werden wir mit Apache 2
nur noch die obige Langform zulassen.
 |
Überprüfen Sie Ihre Skripte dahingehend:
Tragen Sie zum Test in eine Datei .htaccess im Verzeichnis des
PHP-Skriptes ein:
php_flag short_open_tag off
|
Aus historischen Gründen ist PHP auf den zentralen WWW-Servern so
eingestellt, dass z.B. Formularwerte oder Cookies gleich als globale
Variablen im Skript zur Verfügung stehen.
Dies ist zunächst sehr bequem - auf Formulardaten kann man z.B. sehr
einfach zugreifen.
Beispiel:
Das folgende Skript ist aufrufbar via
http://www.tu-chemnitz.de/urz/www/php/ex/1.php
# Formulareingabe aus <input name="vorname" ... >
echo "Hallo $vorname \n"; # eingetippter vorname wird ausgegeben
# Nur wenn die Abfrage vom Rechner 134.109.1.1 kommt, sollen sensible
# Daten gesendet werden.
# Umgebungsvariable REMOTE_ADDR enthält IP-Adresse des abfragenden Rechners:
if ($REMOTE_ADDR == '134.109.1.1') {
$ok = 1; # globale Variable $ok
}
# ...
if ($ok == 1) {
# sende sensible Daten ...
} else {
print "Der Zugriff ist nicht erlaubt!";
}
Dieser "Schutz" ließe sich sehr leicht aushebeln, indem man folgenden URL im
Browser angibt:
http://www.tu-chemnitz.de/urz/www/php/ex/1.php?ok=1
Der Wert der globalen Variable ok wird aus dem URL übernommen, der Test
auf $ok wäre positiv - die sensiblen Daten werden gesendet!
Erste Hilfe:
Alle verwendeten Variablen initialisieren - am Anfang: $ok = 0;
Da die Übernahme von Variablen aus "unsicherer Quelle"
in den globalen Namensraum von den PHP-Autoren und anderen Experten
generell als Sicherheitsrisiko eingestuft wird, werden wir dies mit Einführung
von Apache 2 abschalten!
Zum Zugriff auf Formulardaten, Cookies usw. bietet PHP neue
Felder (sog. Superglobals), die immer verfügbar sind:
| Bisher | Neu | Beschreibung |
| $name | $_GET['name'], $_POST['name'] | Zugriff auf Formular-Daten, die per GET oder POST übermittelt wurden |
| $cname | $_COOKIE['cname'] | Werte aus Cookies |
| $name | $_REQUEST['name'] | Zusammenfassug der 3 o.g. Felder |
| $VARIABLE | $_SERVER['VARIABLE'] | Umgebungsvariable des WWW-Servers |
Unser unsicheres Beispiel müssen wir ändern:
# Formulareingabe aus <input name="vorname" ... >
# Eingetippter vorname wird ausgegeben. Da der Benutzer hier auch HTML
# eingeben kann, kodieren wir es sicherheitshalber mit htmlspecialchars:
print "Hallo " . htmlspecialchars($_REQUEST['vorname']) . " \n";;
$ok = 0;
# Nur wenn die Abfrage vom Rechner 134.109.1.1 kommt, sollen sensible
# Daten gesendet werden.
if ($_SERVER['REMOTE_ADDR'] == '134.109.1.1') {
$ok = 1; # globale Variable $ok
}
# ...
if ($ok) {
# sende sensible Daten ...
} # ...
$ok ist zwar auch hier eine globale Variable, kann jedoch von Angaben im
URL usw. nicht mehr überschrieben werden.
 |
Überprüfen Sie Ihre PHP-Skripte, ob Sie auch ohne
Übernahme von Werten "vom Browser" in den globalen Namensraum von PHP
noch funktionieren.
Tragen Sie zum Test in eine Datei .htaccess im Verzeichnis des
PHP-Skriptes ein:
php_flag register_globals off
|
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
<input type="hidden" ...> oder Auswahlmenüs kommen, müssen einer
Plausibilitätsprüfung unterworfen werden,
bevor sie im Programm verwendet werden.
Das betrifft alle Werte der Felder _GET, _POST, _COOKIE, sowie _REQUEST.
Dazu bietet PHP eine Vielzahl von Möglichkeiten.
# Wert aus Formular-Eingabefeld "zahl" muss numerisch sein:
$zahl = (is_numeric($_REQUEST['zahl']) ? $_REQUEST['zahl'] : 0);
Weitere Beispiele finden Sie in:
http://www.dclp-faq.de/q/q-security-variablen.html
Vermeiden Sie beim Zugriff auf Dateien Variablen, insbesondere dann,
wenn sie aus Bestandteilen des URLs oder Cookies bestehen.
Betrachten Sie das folgende schlechte Beispiel:
# datei ist Bestandteil des URLs
include($_REQUEST['datei']);
Unerwünschte Effekte treten ein, wenn der URL so aussieht:
.../script.php?datei=/etc/passwd
Wie kann man es besser machen?
Wenn eine Variable nur einen Dateinamen im gleichen Verzeichnis wie das
PHP-Skript enthalten darf (aber keinen Pfadnamen), dann reicht
häufig die Püfung auf / im Dateinamen:
if (isset($_REQUEST['datei']) && !ereg('/', $_REQUEST['datei'])) {
# datei ist gesetzt und enthält kein /
include($_REQUEST['datei']);
} else {
# Fehler ...
}
Wenn Variablen nur Werte aus einer vorgegebenen Menge annehmen dürfen (z.B.
aus einem <select> Menü), sollte dies abgeprüft werden:
# nur diese drei Werte sind möglich:
$seiten = array('index.html' => 1, 'kontakt.html' => 1,
'lehre.html' => 1);
if (isset($_REQUEST['datei']) && isset($seiten[$_REQUEST['datei']])) {
# datei ist gesetzt und erlaubt
include($_REQUEST['datei']);
} else {
# Fehler ...
}
Gründlich prüfen sollten Sie unbedingt auch die Parameter für Funktionen
zum Starten von externen Programmen, wie
system(), exec(), passthru(), popen() und den `Backtick` Operator.
Zum Maskieren von Shell-Metazeichen (z.B. Stern oder Semikolon,
die möglicherweise
unerwünschte Effekte haben können) eignet sich die Funktion escapeshellcmd().
if (isset($_REQUEST['datei']) && !ereg('/', $_REQUEST['datei'])) {
# datei ist gesetzt und enthält kein /
# evtl. Shell-Metazeichen maskieren:
$datei = escapeshellcmd($_REQUEST['datei']);
system("/bin/ls -l $datei");
} else {
# Fehler ...
}
Weitere Hinweise:
http://www.dclp-faq.de/ch/ch-security.html
Frank Richter, Januar 2004