Preventing email header injection

More than five years have passed since New York PHP published an extremely useful article warning developers of the dangers of email header injection, and providing detailed instructions of how to prevent it. Yet barely a week goes by without PHP newcomers posting requests for help with email scripts that still contain this vulnerability. Unfortunately, many beginners seem to use outdated scripts or tutorials that ignore basic security, and have never heard of email header injection. When they’re told about it, the NYPHP article is too technical for them to understand. So, this is an attempt to make it simple.

What is email header injection?

The PHP mail() function takes up to five arguments, the first three of which are required: the address(es) the mail is being sent to, the subject line, and the body of the message. The fourth argument allows you to specify additional headers, such as From, Reply-to, Cc, and Bcc. Email header injection usually targets weaknesses in the way this fourth argument is handled to insert bogus headers to turn your online form into a spam relay.

One of the most common uses of the fourth argument is to insert the user’s email address into the From or Reply-to header. It’s extremely convenient, because it allows you to reply directly to an email received from an online form just by clicking the Reply button in your email program. It’s also extremely convenient for an attacker. All that’s necessary is to add a string of spurious headers into the email field of your contact form, and your website becomes an instant spam relay—unless, of course, you take suitable precautions.

Preventing email header injection

The data filters introduced in PHP 5.2 make it easy to check that the value submitted through the email field of an online form contains a single email address and nothing else. So, if you want to put the user’s email address in a Reply-to header, you can check it like this (assuming the input field is named “email”):

$validemail = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($validemail) {
$headers .= "Reply-to: $validemail\r\n";
}

Note that INPUT_POST and FILTER_VALIDATE_EMAIL are PHP constants, and must be in uppercase.

Using filter_input() like this is a simple, and effective way to prevent the email field of a contact form being exploited for header injection. Of course, you would probably want to take further action if the email address fails validation (the filter checks only that it’s in a valid format—it doesn’t check whether it’s a genuine address), such as redisplaying the form with an error message. This blog post is concerned only with basic prevention.

Is that all that’s needed?

Although the email field of a contact form is the main target, you also need to be careful about allowing user input in the first and second arguments to mail(): the address the message is being sent to, and the subject line. If both values are hard-coded in your script, there’s nothing to worry about. However, if your form allows the user to select from a choice of destination addresses, or insert a custom subject line, you need to sanitize those values before passing them to mail(). Don’t be fooled by thinking that a drop-down menu in your form limits the values that can be entered. It’s very easy for an attacker to bypass your preselected values.

One way of handling the destination addresses is to create an array of valid addresses, and use in_array() to check that the submitted value is among them.

$destinations = array('me@example.com', 'you@example.com');
if (in_array($_POST['to'], $destinations) {
// the address is OK to use
} else {
// don't send the mail
}

You can do the same with a choice of subjects. But if you want to allow the user to enter a custom subject line, you need to check that it doesn’t contain newline characters or common headers. The following code uses a regular expression to detect characters and values likely to be used in an attack:

$pattern = '/[\r\n]|Content-Type:|Bcc:|Cc:/i';
if (preg_match($pattern, $_POST['subject'])) {
// reject the post, as it's most likely an attack
}

I go into much more detail in my books, but hopefully this help PHP newcomers to adopt more secure coding practices with mail().

This entry was posted in PHP. Bookmark the permalink.

7 Responses to Preventing email header injection

  1. awahid says:

    nice article, recently we had a small debate on the same topic. validating email is an important thing, you have used in_array() which is not case sensitive I would recommend to use a custom function eg

    you might find it silly but people do write emails in capitals as well :)

  2. awahid says:

    sorry my code got filtered

    public static function in_arrayi($needle,array $haystack) {
    //case insensitive
    return in_array(strtolower($needle), array_map('strtolower', $haystack));
    }

  3. David Powers says:

    @awahid, I think you have misunderstood the purpose of using in_array(). My suggestion is that you create your own array to check values that are submitted from a <select> drop-down menu. Values submitted any other way would be rejected as potential attacks. Your proposed solution is also likely to confuse beginners (the main target of this article), since it involves creating a static method, which would involve the creation of a class.

  4. thank you i will try and add this code to the other code and see what happens
    adalem63
    allen
    one other question. i am trying to upload my first progran in dreamweaver cs5.5 to alsjunk.info from a file c:alsjunk my host server is on godaddy and is bayviewdropincenter.org/alsjunk.info can you in very simple terms tell me how i do this. after i do one i am good enough to clone it for other sites i am working on
    thank you again adalem63
    allen

  5. Tina says:

    Allen

    I am having a problem that I need help with. I have built my own website, but the last thing I need to do is get the forms that I put in it to send me email. Here,s the thing, emails make it to me but there is no information when it is recieved! It doesnt say who its from, the subject, or the body! What am I doing wrong? Is there a tutorial that you have that shows us how to submitt a form properly? I need to know ASAP!

    Thanks!

    -Tina

  6. David Powers says:

    My book, PHP Solutions, 2nd Edition, has whole chapter on working with forms and sending user feedback by email.

  7. Scott Romans says:

    I found the email validation helpful. If I wanted to go about validating other items in a user form, such as ensuring that a US Zip Code consisted of 5 numerical digits, how would I incorporate that into the flow between the form and the processmail script?