Securing PHP Contact Forms

One of the great benefits of PHP is that it is quick and easy for non-programmers to learn the basics of the language and begin to add server-side logic to their websites. This simplicity is a double edged sword, as many novice programmers are unaware of PHP’s security vulnerabilities and inadvertently create web applications that are an easy target for hackers and spammers. Most PHP security holes are well documented, but a newer and lesser known vulnerability is header injection, a cunning exploit whereby a spammer hijacks a website’s contact form and uses it to send bulk unsolicited email.

How they do it

An email is composed simply of raw text that is formatted using a standardized syntax. When we add a contact form to a website we allow our visitors to supply data such as their name, email address, subject and message, which we then format and send using PHP’s mail function. For instance:

<?php

  $recipient = 'recipient@mydomain.com';
  $subject = $_POST["subject"];
  $message = $_POST["message"];
  $headers = "From: ".$_POST["email"];
  mail($recipient,$subject,$message,$headers);

?>

will produce the following raw email data:

To: recipient@mydomain.com
Subject: Hello
From: sender@theirdomain.com


Hi, I love your site.

Seems harmless enough, right? We have hard coded the recipient’s email address in PHP, so it would be natural to assume that a visitor couldn’t use our contact form to send an email to anyone except the intended recipient.

Wrong!

Because we include the visitor’s email address in the email header, it is surprisingly simple for a malicious user to insert additional headers in the email field. They can do this in the following manner:

sender@theirdomain.com%0ABcc:recipient@anotherdomain.com

%0A is the hexidecimal representation of a linefeed, and forces a line break in the email’s header block. Now the raw email will look like this:

To: recipient@mydomain.com
Subject: Hello
From: sender@theirdomain.com
Bcc: recipient@anotherdomain.com


Hi, I love your site.

In this basic example the spammer has simply inserted a single additional recipient. In practice they will use more advanced injection techniques to hide your original message and display their own in its place, and will also insert the email addresses of thousands of hapless recipients. Now your form is being used to send spam.

As the hard coded recipient of the form, the website owner (your client) will become aware that something fishy is going on when they begin to receive large amounts of spam, apparently originating from their own site. Shortly your web host will also notice the high volume of spam emails being sent from your client’s domain, and shut the site down. They will send you an irate email requesting that your patch your insecure PHP scripts or find another hosting company. Needless to say, this chain of events is bad for your relationship with both your web host and your client!

The solution

Protecting against header injection attacks is a simple matter of validating user supplied data. Primarily, header injection relies on the attacker being able to include linebreaks in strings that will appear in an email’s header. By testing for the presence of these linebreaks in user-supplied data we can stop the spammer in their tracks. I have written a small function to simplify this process – it accepts as its parameter an array of the header fields we need to test:

<?php

function checkFields($values){
	$injection = false;
	for ($n=0;$n<count($values);$n++){
		if (eregi("%0A",$values[$n]) || eregi("%0D",$values[$n]) || eregi("\\r",$values[$n]) || eregi("\\n",$values[$n])){
			$injection = true;
		}
	}
	return $injection;
}

$from = $_POST['from'];
$email = $_POST['email'];
$result = checkFields(Array($from,$email));
if ($result==true){
	die("Header injection detected!");
}

?>

How to tell if your form is being exploited

To test a form to see if it is vulnerable, a spammer will first try and inject a single bcc header containing a throwaway email address (usually an AOL address). They then check that AOL address to see if they received the test email. If so, they will begin using your form as a spam relay.

The only flaw in this otherwise perfect plan is that the email’s intended recipient (whose email address is hard coded into your PHP form handling script) will also be receiving these probe emails.

So if one of your clients complains they are getting a lot of ‘spam’ sent to them via their contact form, sit up and pay attention. There is a chance the ‘spam’ they mention is actual a spammer’s probe emails. Ask your client to send you a few examples of the emails they are receiving. A probe will contain text that looks something like this:

said

Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: an wan that makes me proud iv ailey f
bcc: onemoreaddress@hotpop.com

934522c25fb7a2507489e97c4043af14

I’m still getting annoying probe emails

Even though your form validation may already be filtering header fields for line breaks, you may still receive a spammer’s probe emails. This is because the spammer – or more precisely their ‘spambot’ – is attempting to blindly inject into every form field it finds, including the ‘message’ field (the probe example I gave above was actually the result of a spammer’s attempt to inject into the message field of a client’s contact form). The message field doesn’t pose a security risk since it does not appear in the email’s header, but unless you validate it as well then the form’s intended recipient will continue receiving probe emails. Of course the message field can legitimately contain linebreaks, so you will need to validate it for other telltale strings such as “Bcc:” or “Content-Type:” both of which are common in injection attacks. Make sure your validation ignores case, since an email header is case insensitive.

Conclusion

Validate, validate, validate. And don’t trust your website visitors, not all of them are good guys!

Further reading

Email Injection

For a really detailed description of email header injection in PHP, check out the Secure PHP wiki page on the topic.

Form Post Hijacking

When header injection attacks became widespread a couple of my sites were compromised. I found this article useful in helping me understand the problem.

UPDATE 12 Dec 2006: I realized that I had forgotten to double escape the \ characters in my validation function when I posted this article, so they weren’t showing up. Whoops! I have updated the function accordingly.

9 thoughts on “Securing PHP Contact Forms

  1. Thanks again, I had no idea of this one (sadly). Together with SQL injections these two probably are the biggest security hole on the net today.

  2. Clive Walker says:

    Very timely for me because of a client having the problem you describe. Your function looks like a good solution.

  3. Clive Walker says:

    Thank for the email re escaping characters. FYI, I tried to reply to your email but I received an undelivered message email back [relay access denied] Not sure if this is a problem with your server but thought you’d want to know.

  4. cctech says:

    Really great post Jonathan. We have run into this very thing a few times and have been doing research on a fix :|. Awesome job! :)

  5. This is a very good post and warning. Good news for those of you who uses Contact Form plugin for WordPress: it includes a function that checks for a malicious input.
    function wpcf_is_malicious($input) {…}

    The concept is very similar to Jonathan’s, but it checks the input slightly differently:
    $bad_inputs = array(“\r”, “\n”, “mime-version”, “content-type”, “cc:”, “to:”);

    I think if we add Jonathan’s values to it as well, it will be a rock solid protection from a header injection.

  6. Jonathan says:

    @inspirationbit: On this site I’ve been using Dagon Design’s “Secure Form Mailer Plugin” for WordPress, which has inbuilt header injection protection, and I’ve found it to be very solid. I think it’s great that most developer’s are now aware of this exploit and are writing scripts that patch against it.

  7. Jonathan, thanks for the link to another Plugin for Contact page. I think I like this one better, so will be changing mine.
    Yes, it’s great that the plugin writers too are aware of these exploits and write such a solid and professional code.

  8. Regge says:

    Jonathan, thank you for your very interesting article and also for your Formbuilder script, which is so cool.
    One question, do you advise to use “full” headerinjectioncheck for the message part of a contact form? Don’t users often add breaks to there message? I know I do. I see that the “light” headerinjectioncheck works line returns, how different is it? how less secure?
    Thanks again for all your work and efforts to share your knowledge. As a beginner programmer, I really appreciate it.
    Regge

  9. Jonathan says:

    Regge – That’s a good question. No, definitely only use ‘light’ checking on the message field. Even that isn’t necessary, since the message never appears in the email header, which is the only vulnerable part of an email. The only reason I included ‘light’ checking in FormBuilder is because spambots often try any inject into every field of a form, which means the recipient still gets their probe emails, which is annoying. If you find header injection probes are an issue for you, then the ‘light’ check might be useful to nip them in the bud.

Comments are closed.