TU Chemmnitz

Universitätsrechenzentrum

TU Chemnitz > URZ > Zeitung > Ausgabe 1/2004

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

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.

Start-Tag

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.

Tipp! Ü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

Umgang mit globalen Variablen

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.

Tipp! Ü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

Überprüfung von Variablenwerten

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