Spam-Mail über Kontaktformulare

Wie man den massenhaften Versand von Spam über Mailformulare eindämmt

Das Problem

Seit kurzem werden durch automatisierte Angriffe Kontaktformulare zum Versand von Spam missbraucht. Durch sogenannte "Mail-Injection" geraten Inhalte in den Mail-Header, die zusätzlich zur ursprünglich beabsichtigten Mail eine Mail mit beliebigem Inhalt an Spam-Opfer verschicken.

Dabei ist es nicht entscheidend, dass der Empfänger hartcodiert im Quelltext der Applikation steht; durch das Einfügen einer neuen Zeile wird eine neue Header-Zeile erzeugt. (Natürlich enthalten einzeilige Formularfelder eigentlich keine Zeilenumbrüche, es ist aber kein Problem, diese per Skript zu schicken!)

Die beim ursprünglichen Empfänger ankommende Mail sieht dann in etwa so aus:


To: someone@example.com
Subject: Kontaktanfrage
From: hnkordlpx@example.com hnkordlpx@example.com <hnkordlpx@example.com>

Name: hnkordlpx@example.com
Strasse: hnkordlpx@example.com
PLZ: hnkordlpx@example.com
Ort: hnkordlpx@example.com
Mail: hnkordlpx@example.com

weitere Nachricht des Absenders:
hnkordlpx@example.com
Content-Type: multipart/mixed;
boundary=\"===============0036415912==\"
MIME-Version: 1.0
Subject: 8bc3efef
To: hnkordlpx@example.com
bcc: jrubin3546@aol.com
From: hnkordlpx@example.com

This is a multi-part message in MIME format.

--===============0036415912==
Content-Type: text/plain; charset=\"us-ascii\"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

qjdz
--===============0036415912==--

Wie man sieht, ist hier die Adresse des Opfers im bcc: noch enthalten (in diesem Fall ist es die Adresse des Spammers, der sich zur Erfolgskontrolle selbst eine Mail schickt). Diese Mail ist misslungen. Bei einem gelungenen Versuch spaltet sich der injizierte Header samt Mail-Content ab. Der ursprüngliche Empfänger bekommt nur noch eine Mail wie folgende zu sehen (Quelltext):

To: Empfaenger <empfaenger@example.com>
Subject: Mail von example.com
From: ojqtjk@example.com
Content-Type: multipart/mixed; boundary=\"===============0962927048==\"
MIME-Version: 1.0
Subject: 45cc2bf5
To: ojqtjk@example.com
From: ojqtjk@example.com

This is a multi-part message in MIME format.

--===============0962927048==
Content-Type: text/plain; charset=\"us-ascii\"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

dbg
--===============0962927048==--
<ojqtjk@example.com>
Return-Path: <ojqtjk@example.com>

ojqtjk@example.com
Kontaktdaten: ojqtjk@example.com

Die Lösung

Alle vom User abgesandten Inhalte, die in der Mail verwendet werden, müssen noch einmal zusätzlich auf die Merkmale dieser Spam-Attacken geprüft werden. Dies sind:

  • Bei einzeiligen Eingabefeldern (<input type="text|checkbox|radio|submit"> ):
    Eine Prüfung, ob ein Zeilenumbruch (<NL>, <CR> ) enthalten ist.
  • Bei mehrzeiligen Eingabefeldern (<textarea></textarea> ):
    Eine Prüfung auf den String "MIME-Version:" .
    (Eine Prüfung auf Zeilenumbrüche ist hier natürlich sinnlos.)
Folgende Funktion liefert (bool) false , wenn Spam-Merkmale vorliegen:
<?php
/**
* bool ht_spamcheck( str input, str type )
* prueft die Eingabefelder eines Mailformulars auf
* Merkmale eines Spamangriffs.
*
* rhorbas ( at ) hypotext.de
*
* Referenzen:
http://de.php.net/manual/de/ref.mail.php#56287
http://securephp.damonkohler.com/index.php/Email_Injection
*
*/
function ht_spamcheck($input,$type='')
{
# diese Sachen duerfen nicht enthalten sein

# einzeilige Eingabefelder koennen UND
# duerfen keine Returns enthalten:
$bad_str1="/[\n\r]/";

# fuer textareas verboten:
# (wenn auch nicht ganz unproblematisch)
$bad_str2="'MIME-Version:'";

switch($type)
{
case'':
case'input':
case'radio':
case'submit':
case'checkbox':
if( preg_match($bad_str1,$input)
||
                preg_match($bad_str2,$input)
)
{
return(bool)false;
}
break;
case'textarea':
if( preg_match($bad_str2,$input))
return(bool)false;
break;
}

return(bool)true;
}
?>

Die Funktion kann man dann z.B. so einsetzen:

<?php
if(false== ht_spamcheck($_POST['name']))
{
die('Schleich dich, Spammer');
}
?>

Die oben beschriebene Methode funktioniert soweit zuverlässig. Wer auf Nummer sicher gehen will, kann noch eine ID in einem versteckten Formularfeld verwenden, da der Spambot (im Moment!) keine hidden-fieds überträgt.
Das ist ein bisschen, als ob man zum Gürtel auch noch einen Hosenträger anzieht. ;-)

<?php
# ein zufaelliger String, den wir als "Salz" fuer den md5-hash
# verwenden und als Name fuer das hidden-field, damit sich der
# Spammer nicht auf den Namen einstellen kann:
$spamcheck='ein_String';
?>

<?php
/* ein hidden-field im Formular, das aus $spamcheck gebildet
wird. Im Moment kuemmert sich der Spambot nicht um hidden-
fields! (kann sich aendern)*/
?>
<html>
<form...>
<inputtype="hidden"
name="<?=$spamcheck?>"
value="<?=md5($spamcheck.$_SERVER['HTTP_HOST'])?>"
/>
        ...
</html>

Beim Empfang der Daten aus dem Formular prüft man dann auf das Vorhandensein des hidden-fields:

<?php
if($_POST[$spamcheck]!=
        md5($spamcheck.$_SERVER['HTTP_HOST']))
{
die('Annahme verweigert.');
}
?>

Diskussion

Das Prüfen auf den String "MIME-Type:" ist arg brachial; im Moment ist mir aber keine andere Möglichkeit bekannt. Bei Vorliegen weiterer Informationen aktualisiere ich die Seite; über Rückmeldungen und Verbesserungsvorschläge freue ich mich.

Quellen

  1. PHP Manual: Mail-Funktionen
    http://de.php.net/manual/de/ref.mail.php#56287

    Anmerkungen von oryan at zareste dot com et al.
  2. Email Injection
    http://securephp.damonkohler.com/index.php/Email_Injection

    (Englische) Beschreibung des Problems für einzeilige Eingabefelder
  3. RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types
    http://www.faqs.org/rfcs/rfc2046.html

    RFC zum Einbetten von MIME-Inhalten in Mails