Sicheres Programmieren mit PHP (Teil 4)

In den "Mitteilungen des URZ" 2004 haben wir bereits Hinweise zur sicherheitsbewussten PHP-Programmierung gegeben. In diesem Artikel soll nun die Mail-Funktion von PHP beleuchtet werden.

An dieser Stelle möchten wir die kleine Artikelserie (nachzulesen unter http://www.tu-chemnitz.de/urz/www/php/secure.html) fortsetzen. Denn "Sicheres Programmieren mit PHP" ist weiterhin ein brisantes Thema.

Mit Hilfe von PHP können E-Mails versendet werden. Hier ist bei der Programmierung große Sorgfalt nötig, weil sorglos programmierte Skripts häufig und erschreckend schnell von Spam-Versendern missbraucht werden. Die Folgen mussten wir schon mehrfach spüren: Unsere Mail-Server landen auf Sperrlisten, was alle Benutzer behindert.

Schauen wir uns zunächst wieder ein schlechtes Beispiel an. Über ein Formular wird eine E-Mail-Adresse abgefragt, welche dann an einen Mailing-Listen-Server gesendet werden soll:

<form action="..." method="post">
E-Mail-Adresse: <input name="adresse" /> <input type="submit" value="Eintragen" />
</form>

<?php
   if (isset $_POST['adresse'] && $_POST['adresse'] != '') {   # Formulareingabe gesetzt?
      mail('test-join@tu-chemnitz.de', '', '', 'From: ' . $_POST['adresse']);
   }
?>

Dem aufmerksamen Leser fällt sofort auf, dass die Variable $_POST['adresse'], die vom Browser (oder eben dem Angreifer) kommt, fast ungeprüft übernommen wird. Schlecht, denn genau dort ist der Angriffspunkt.

Betrachten wir die Argumente der PHP-Funktion mail(): Die ersten drei sind klar: Jeweils Zeichenketten für Empfänger, Betreff und Inhaltstext (in unserem Beispiel sind Betreff und Inhalt leer). Als viertes Argument kann eine Zeichenkette mit weiteren Kopfzeilen (Header) der E-Mail angegeben werden. Im Beispiel soll die Absender-Adresse (From:) gesetzt werden. Wenn $_POST['adresse'] eine gültige E-Mail-Adresse enthält, ist alles gut.

Ein Angreifer kann uns aber als vermeintliche Formulareingabe ganz andere Daten senden, wie z.B. egal%0ABcc:%20armes@spam.opfer,nochein@spam.opfer%0A%0AKlicken-kaufen

Dekodiert man dies (%0A ist Zeilenumbruch), ergibt sich folgende Zeichenkette, die als Kopfzeilen in die Mail eingefügt werden:

  From: egal
  Bcc: armes@spam.opfer,nochein@spam.opfer

  Klicken-kaufen
Das Ergebnis: Die E-Mail wird auch an die "...@spam.opfer"-Adressen geschickt, sogar mit Inhalt, da die zwei Zeilenumbrüche das Ende der Kopfzeilen bedeuten. Natürlich sollte die PHP-Funktion mail() dies abprüfen und verhindern, tut sie aber leider nicht.

Wir als PHP-Programmierer sind natürlich hier in der Pflicht, alle extern in unser Programm gelangenden Daten genau zu prüfen - ich wiederhole mich, nicht wahr? So könnte das für obiges Beispiel aussehen:

<form action="..." method="post">
E-Mail-Adresse: <input name="adresse" /> <input type="submit" value="Eintragen" />
</form>

<?php
   if (isset $_POST['adresse'] && $_POST['adresse'] != '') {   # Formulareingabe gesetzt?
      if (preg_match("/[\r\n]/", $_POST['adresse']) {
          # adresse enthält Zeilenumbruch: eines der Zeichen \r oder \n
          # -> nicht mit uns - das lehnen wir ab!
          exit;
      }
      mail('test-join@tu-chemnitz.de', '', '', 'From: ' . $_POST['adresse']);
   }
?>

Es gibt weitere "Fallen" beim Versenden einer E-Mail. So sollte z.B. die Absende-Adresse richtig gesetzt sein, damit auch Fehler-Mails richtig ankommen (und nicht an den Betreiber des WWW-Servers gehen). Außerdem müssen die verwendeten Zeichensätze bzw. Kodierungen gesetzt sein.

Um dies einem PHP-Programmierer abzunehmen, stellen wir für die zentralen WWW-Server der TU eine eigene E-Mail-Funktion tuc_mail() bereit, die Sie verwenden sollten. Das wird im folgenden Beispiel deutlich:

<?php
require_once('php/mail.inc');    # definiert tuc_mail()

# Empfaenger, mehrere durch Komma trennen
  $to = 'Max Mustermann <max.mustermann@hrz.tu-chemnitz.de>';

# Absender, muss aus *.tu-chemnitz.de sein!
  $from = 'Frank Richter <frank.richter@hrz.tu-chemnitz.de>';

# Subject = Betreff, möglich: Umlaute in UTF-8 oder Latin1- (ISO-8859-1)
  $subject = 'PHP-Test: äöü';

# Inhalt, auch hier Umlaute möglich
  $text = 'Dies ist eine Test-Nachricht, ausgelöst durch ' .
          $_SERVER['REMOTE_ADDR'];

  $ok = tuc_mail($to, $from, $subject, $text);
  if ($ok == TRUE)
      echo "E-Mail wurde versendet.";
  else     
      echo "Fehler beim Versenden: " . htmlspecialchars($ok);

      
?>

Die Funktion tuc_mail() verlangt vier Argumente - jeweils Zeichenketten für Empfänger, Absender, Betreff und Inhalt. Hat das Versenden funktioniert, liefert die Funktion TRUE zurück, im Fehlerfall eine Zeichenkette mit der Fehlerursache.

Unsere Funktion prüft zuerst einmal die Sicherheit der Argumente, setzt die Absende-Adresse, erkennt und kennzeichnet den Zeichensatz von Betreff und Inhalt (Latin-1 oder UTF-8). Also fast ein "Rundum-Sorglos-Paket" zum Nulltarif. Bitte benutzen!


Frank Richter, April 2006