Avoiding Email Header Injection in PHP

Album Cover: Eyes Open

"It's hard to argue when you won't stop making sense."
Snow Patrol / Hands Open

Posted on May 16, 2007 11:21 PM in Web Development
Warning: This blog entry was written two or more years ago. Therefore, it may contain broken links, out-dated or misleading content, or information that is just plain wrong. Please read on with caution.

I received notification from my hosting provider today that at least one of my contact forms was susceptible to email header injection by spammers.

The first thing I did was look up some solutions to the problem, and I found Email Header Injection in PHP over at Jelly and Custard. The site provides some background on why email header injection is a problem, and gives some quick and dirty examples of how the particular vulnerability can be eradicated.

Luckily for me, I've had the same block of code for processing contact forms since way back when I was in college, and because I'm a firm believer in the "if it ain't broke, don't fix it" mantra, I've been using it on all of my websites that contain a contact form. This made fixing the problem much more straightforward, but there were a few kinks I had to work out.

First off, while the examples provided over at Jelly and Custard are great for conceptually conveying some approaches for removing the vulnerability, you can't really take the sample code at face value. Both the has_no_newlines() and has_no_emailheaders() functions are written in such a way that they aren't very easy to follow, logically speaking.

If you send in a chunk of text to a function called has_no_newlines(), you'd expect it to return true if that chunk of text contains no newlines, right? Well, the function they provide actually returns false if the text contains no newlines, because the preg_match() function returns the number of times the regular expression you're searching for was found in the text (either 0 or 1, since it stops after the first match). The fact that it's returning an integer value instead of a true boolean value is another topic, but that isn't as big a deal.

To make things clearer, I ended up writing the following function:

// function for checking a string for newlines
function contains_newlines($text)
 return preg_match("/(%0A|%0D|n+|r+)/i", $text);

This seems more logical to me, in that it will return true if $text contains any newlines.

That wasn't the biggest flaw, though. Another example provided is a function for determining whether or not the input is a valid email address or not. The regular expression it uses looks something like:


If you're familiar with regular expressions at all, the flow of this one is pretty easy to follow. It's basically saying that the string will contain a series of alphanumeric characters (plus a few specific non-alphanumeric characters), followed by the character @, followed by another series of alphanumeric characters (plus a few specific non-alphanumeric characters), followed by a period and then a series of 2-4 alpha characters (e.g. com or us or info, etc.).

The glaring flaw in the regular expression, though, is that the "dot" character is a reserved character in regular expressions. So where the intention of the regular expression is to match literal dots in three specific places throughout the string, the usage of unescaped dots actually just says match any character, which isn't good. The regular expression is fixed by escaping the dots with a backslash. The following is the function I came up with, with the regular expression corrected:

// function for verifying a string is an email address
function is_email($text)
 return preg_match("/^[A-Z0-9\._%-]+@[A-Z0-9\.-]+\.[A-Z]{2,4}$/i", $text);

I definitely don't want to sound like I'm knocking that site, because before I read the article I really had no clue what PHP email header injection was or how to avoid it.

I'm only providing some clarification in case others come along and want to add some logical, accurate form validation to their contact forms and take yet another tool away from the nuggets.


Ryan (Arcanius) on May 19, 2007 at 9:04 AM:

Do dots in [character classes] still count as any character? I did a quick test using grep in cygwin and it appears the answer is no. The backslash doesn't seem to hurt, though, so it is safe. And it is definitely needed outside of the character classes.


Bernie Zimmermann on May 19, 2007 at 10:26 AM:

Ryan, thanks for pointing that out. I tried to dig up a definitive explanation online as to which approach is preferred, but found mixed results. I wrote the following Perl script to test some input, and came to the same conclusion that you did using Grep:

#!perl -all

# see if the input is a valid email address
if ($ARGV[0] =~ /^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i)
  print "Valid email address.";
  print "Invalid email address.";

If it was up to me to personally decide, I'd suggest escaping the dots even in character classes just as a best practice. That way you always remember to escape the dot in all situations where you are expecting a literal match.


seb on June 16, 2007 at 4:32 PM:

why use preg_match and a complicated regular expression, though?

woudn't this work just the same?

$text = url_decode($text);
if(strstr($text,"\r") || strstr($text,"\n")){
// there is a line break


Graham on April 03, 2008 at 11:55 PM:

I've not tried this but it doesn't look as though it will work with the majority of domains worldwide which are in two parts, such as .com.au or .org.uk


Bernie Zimmermann on April 04, 2008 at 8:30 AM:

Graham, I believe you are right. Two options are to remove the $ at the end of the expression or to add an additional, optional check to the end for those extra extensions. Thanks for pointing that out.


David on May 08, 2008 at 8:33 PM:

Some of you might find this interesting. There is code available to validate an email address to the RFC2822 spec.

Checking for headers couldn't you just use the following?



Jacky on January 28, 2009 at 7:27 AM:

I like the link David posted and here is a way to check if the domain provided is a working MX domain. Not this will not work on Windows Servers.

list($userName, $mailDomain) = split("@", $email);
$ValidDomain = checkdnsrr($mailDomain, "MX");
if( $ValidDomain == TRUE )
//send mail


Post Comments

If you feel like commenting on the above item, use the form below. Your email address will be used for personal contact reasons only, and will not be shown on this website.


Email Address:



Check this box if you hate spam.