Studieren in Chemnitz. Wissen, was gut ist.






Sicheres Programmieren mit PHP (6)

Versenden von E-Mails

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'];
# Antwort an - hier nicht gesetzt:
  $reply_to = '';

  $ok = tuc_mail($to, $from, $subject, $text, $reply_to);

  if ($ok === TRUE)
      echo "E-Mail wurde versendet.";
  else     
      echo "Fehler beim Versenden: " . htmlspecialchars($ok);
      
?>

Die Funktion tuc_mail() verlangt fünf Argumente - jeweils Zeichenketten für Empfänger, Absender, Betreff, Inhalt, und wenn gewünscht Antwort-Adresse. 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!