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!