Sending spam through contact forms
There's a wave of contact form spamming going around and it hit the Comic Marktplatz, too. The main purpose of this attack is to figure out websites providing forms that can be misused to send spam, effectivly turning them into open relays. This affects not only contact forms, but also blog comments.
Anders Brownworth has the details about all this (also see his blog entry). However his solution for PHP has some syntax errors. These are corrected by David Seah.
Shortly said, you should remove line feeds from every user entered string that becomes part of a mail's header. These usually are
- The sender (From:)
- The subject (Subject:)
David also suggests to remove common header tokens like "to:", "subject:", "mime-type:" etc. from this strings. I personally think, this is a good idea. I turned David's code into a neat function, replacing the original preg_replace with str_replace, for performance purposes:
<?php
/**
* Clears header field to avoid injection
* http://www.anders.com/projects/sysadmin/formPostHijacking/
* http://www.davidseah.com/archives/2005/09/01/wp-contact-form-spam-attack/
*/
function preprocessHeaderField($value)
{
//Remove line feeds
$ret = str_replace("\r", "", $value);
$ret = str_replace("\n", "", $ret);
// Remove injected headers
$find = array("/bcc\:/i",
"/Content\-Type\:/i",
"/Mime\-Type\:/i",
"/cc\:/i",
"/to\:/i");
$ret = preg_replace($find,
"**bogus header removed**",
$ret);
return $ret;
}
?>David also suggest to do this with the message text itself, which I personally regard as not beeing necessary, since the message text usually can do no harm.
It seems the robots are systematically scanning websites for input fields to provide an email, name and message. That's why they also try this out on comment forms. Since these forms usually also send a mail to the post author, they are also vunerable. So you better watch for updates of your blog software.
Which finally brings me to one of my favourite topics: Architecture. Regarding the Comic Marktplatz, I needed to apply the fix to exactly one place, since all mailing is done through one single class representing a mail. No need to look up every possible vulnerable form throughout the application.
Compare this - just for example and without any judging - to my blog software, Wordpress 1.2 (yes, still... upgrading takes too much time). Do a search for "@mail" in the wordpress source files and you will find several occurances, all of whom potentially are vulnerable. There are even more including the plugins.
In Wordpress 1.5, this hasn't been quite corrected. Though there is central function called wp-mail(), it acts just as a very small wrapper around the PHP mail() function. Basically it only adds a content type and MIME version. It howevers still requires the caller to prepare a basic header including the sender (From:), which exactly is where the attack takes place. All this calling code must be identified and fixed to secure the application, which is error prone.
Morale? Code duplication is bad, centralization is good. Think before you hack. Teach this, preach this!
PS: I always wondered why PHP mail() doesn't take the sender as a parameter - this seems weird.
Update: There's an article on Secure PHP investigating the full story.
Update: Corrected two mistakes, Alastair noted in the comments.

There is a parse error with your function. An extraneous semicolon.
Thanks for writing it BTW!
Also, I notice this, which is just daft (the first line is effectively ignored):
$ret = str_replace("\r", "", $value);$ret = str_replace("\n", "", $value);
Thanks Alastair, you're right. I corrected both of them.
I've developed a PHP patch that fixes this at PHP level:
http://www.titov.net/2005/12/01/php-forms-spam/
Hey Anton, that's cool. Actually the mail() function is definitly the place to stop this kind of attacks. Thumbs up for this!
However, your patch doesn't cover the full story. The problem lies in the additional headers. Your patch
This is still vulnerable, I think. Here's why:
The header is valid, nethertheless the attack was successfull. I myself stumbled upon this, when proposing a patch for Drupal.
Unfortunately there is nothing to do against this, except of checking user input before passing it to mail(). I think this is a design flow in the mail() function itself. There really is no reason why the sender can not be passed as a parameter, at least optional.
That's right, but currently spammers inject two new lines to all input fields of the forms. With this patch their scritps will need to be smart enough to find the from field, inject bcc there and be careful not to put new lines to other fields as sending will fail. The chances are that they will do this if my patch will be incorporated into PHP, but for a few months this will work. Actually I prefer it not to go to PHP sources, as this way my servers are protected from current spammers tools, and I have no problem maintaining patchsets for PHP as my mail function is already patched to include headers with sender's domain (I own a hosting company and need to know if my users are sending spam, which with mod_php is not quite possible). For last 10 days before applying the patch to all the servers I have 1000+ mails from AOL and few from SpamCop with complaints that spam was sent from my networks and now it is over. I know it is not a perfect solution, but it works currently, just like graylistig, which is also a working but easy to fool solution. Just warning users that their forms are vulnerable is not an option, there are at least 500 forms on my servers that was used to send spam, many of them are part of softwares like osCommerce (maybe outdated version), that fail to check input data. Convincing a user that he will need to rewrite/upgrade something working (for him) is not that easy, so for now the only solution for me is to try to patch PHP and I can go as far analyzing if part of additional headers came from POST/GET (maybe stripslashed) and to block mail if this part contains new lines - again not a perfect solution but it will work most of the time.
In general maybe it's a good idea for PHP to start accepting additional headers not only as string but also as an array of strings (and no one of them will be allowed to contain new lines) and to recommend this way of using it. After few versions to put a setting in php.ini that disables the old behavior and after few more versions to make it disabled by default. Checking $to and $subject for new lines should stay.
This solution doesn't apply to the kind of spamming discussed here, since it is not about getting a set of links published on another one's web side as it is with comment spam.
Instead, this exploit is about turning a contact-form (or a comment form or actually any form that sends e-mails to whomever) into an open relay, that is: the form is used to send spam e-mails to thousands of other people.
This spam doesn't necessary need links, but could also be some plain text. Over here in Germany, for example, the extreme political right does e-mail-spamming capaigns ever once in a while.
Cool! Thanks for turning my scribblings into something more practical. On a side note, there's an updated WP-Contact Form over on ryan duff's site. For some reason, I'm still getting weird emails with injected headers...I am not clever enough to figure out how it's happening...maybe some kind of alternate text encoding is going on.
Hello Dave! The thanks go right back for your initial work.
I wonder why you are still receiving injected mails through the contact form. Ryan's code seems OK to me....