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!