PHP Mail Form: Secure and Protected

The one major downside to the ever-changing fast paced cyber world that we call the Internet is the speed at which code becomes outdated and new security issues are discovered. This tutorial covers the basics as set forth in previous tutorialtastic mail form guides with new, more advanced security techniques and spam-blocking measures.

Firstly, the key to any mail form is the actual form — this tutorial is pointless without it. So, we start with the <form> tag — this is where we specify how our data is sent and the file we’ll use to process that data. We’re going to use POST to send our data (GET reveals the info sent in the URL, leaving it open to easier theft) and we’ll be creating a file called form-process.php to sort out our stuff. So, this looks like:

<html>
<head>
    <title>My Mail Form</title>
</head>
<body>
    <form method="post" action="form-process.php">

    </form>
</body>
</html>

(Save this in a file called form.php or form.html)

The key to creating a form that you’ll process with PHP is properly naming the form fields. Using the POST superglobal array (the method we send our data with, as mentioned before) we access specific parts based on what they’re named in the HTML. For example, if we call our field “name”, we’d access it through $_POST['name'] — using this information we can create as little or as many fields as we like.

To give an example of a variety of form elements, I’m using the fields: Name, E-mail, Country and Comments. This allows us to use an input field, drop-down menu and textarea. First, we’ll add the HTML to what we’ve already got:

<html>
<head>
    <title>My Mail Form</title>
</head>
<body>
    <form method="post" action="form-process.php">

    Name:<br>
    <input type="text" name="name"><br>
    E-mail:<br>
    <input type="text" name="email"><br>
    Country:<br>
    <select name="country">
        <option>United Kingdom</option>
        <option>United States</option>
        <option>Other</option>
    </select><br>
    Comments:<br>
    <textarea name="comments" rows="10" cols="30"></textarea><br>

    <input type="submit" name="submit">

    </form>
</body>
</html>

Notice how each field has a name? This is important no matter what type of field/element you use; each name must also be unique. (This example is HTML 4.01 compliant: XHTML users will need to adjust to suit.)

Now that we have our HTML, we need to start on the PHP. First, we create our form-process.php as this is the file our form points to. In this file, we need to open our PHP block of code and perform the first check — checking that the submit button was actually pressed. This helps prevent people from accessing form-process.php directly.

To find out if the submit button is pressed we can use isset() (preceded by a “!” which effectively reverses the logic of the function, asking PHP if the POST variable is not set). Because the submit button is an input field and has a name, $_POST['submit'] is set when the form data is sent if the form is being used appropriately.

<?php
if (!isset($_POST['submit'])) {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
}
?>

We also need to check that data is being sent the right way (using the POST method), this is done by checking the REQUEST_METHOD variable set by the server using “!=” or “not equal to”. This is another way to make sure the page is not being accessed directly, and can be ‘added’ to our current if statement:

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
}
?>

Although potentially malicious users can’t access the process file directly now, they can still submit an empty form repeatedly, possibly getting you in trouble for spamming. To stop this, we need to check for empty POST values (or values set with a single space, which we get rid of with trim()). We could do this individually, but it’s much quicker to loop through the entire $_POST array and check them together. We do this with the foreach() function. This goes through each part of the array one ‘line’ at a time:

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        }
    }
}
?>

Unfortunately, it is still possible to create an identical copy of the form on an external host and send innappropriate data to our form. We can combat this by checking that the referrer matches the domain the original form is on, but a lot of common firewalls block referrer headers preventing normal users from leaving feedback. Instead, we need to make sure our data is sanitised (or ‘cleaned’). By cleaning the data, it helps us to ensure that no matter where it comes from, it will be as safe as possible.

Because we’re already looping through the data to check for empty fields, we can clean each part of the array at the same time. To do this, we need to strip HTML tags and check for references of JavaScript like “alert” and “document.cookie” that needn’t be there. While PHP has strip_tags() to get rid of HTML, we need to use a combination of an array() of malicious words and a non-case sensitive regular expression to look for mentions of script exploits.

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";

    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        }
        
        $_POST[$key] = stripslashes(strip_tags($value));
    }
}
?>

Not all ‘bad’ users are people who trying and inject code into forms — one of the biggest plagues of contact/mailer forms are spam bots. These are more likely to be entering the word “viagra” than “alert”, so to help get rid of spam and other nonsense stuff we can create another array full of common spam words and slang/rude words, and check our POST values against that too:

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";
    $profanity = "/(beastial|bestial|blowjob|clit|cock|cum|cunilingus|cunillingus|cunnilingus|cunt|ejaculate|fag|felatio|fellatio|fuck|fuk|fuks|gangbang|gangbanged|gangbangs|hotsex|jism|jiz|kock|kondum|kum|kunilingus|orgasim|orgasims|orgasm|orgasms|phonesex|phuk|phuq|porn|pussies|pussy|spunk|xxx)/i";
    $spamwords = "/(viagra|phentermine|tramadol|adipex|advai|alprazolam|ambien|ambian|amoxicillin|antivert|blackjack|backgammon|texas|holdem|poker|carisoprodol|ciara|ciprofloxacin|debt|dating|porn)/i";
    
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        } elseif (preg_match($profanity, $value) || preg_match($spamwords, $value)) {
            exit("<p>That kind of language is not allowed through our form.</p>");
        }

        $_POST[$key] = stripslashes(strip_tags($value));
    }
}
?>

Some spam bots are smart though, and it’d be impossible to block every variation of every bad word out there, so instead we can check the User-Agent. A normal user would have a normal user-agent string containing gobledeegook about Firefox or Internet Explorer, the version number etc. Some bots don’t replicate this info and use their own string. Let’s build another array of common bots and check for them (because the user-agent is past in the SERVER superglobal, we do this outside of the foreach loop):

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";
    $profanity = "/(beastial|bestial|blowjob|clit|cock|cum|cunilingus|cunillingus|cunnilingus|cunt|ejaculate|fag|felatio|fellatio|fuck|fuk|fuks|gangbang|gangbanged|gangbangs|hotsex|jism|jiz|kock|kondum|kum|kunilingus|orgasim|orgasims|orgasm|orgasms|phonesex|phuk|phuq|porn|pussies|pussy|spunk|xxx)/i";
    $spamwords = "/(viagra|phentermine|tramadol|adipex|advai|alprazolam|ambien|ambian|amoxicillin|antivert|blackjack|backgammon|texas|holdem|poker|carisoprodol|ciara|ciprofloxacin|debt|dating|porn)/i";
    $bots = "/(Indy|Blaiz|Java|libwww-perl|Python|OutfoxBot|User-Agent|PycURL|AlphaServer)/i";

    if (preg_match($bots, $_SERVER['HTTP_USER_AGENT'])) {
        exit("<p>Known spam bots are not allowed.</p>");
    }
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        } elseif (preg_match($profanity, $value) || preg_match($spamwords, $value)) {
            exit("<p>That kind of language is not allowed through our form.</p>");
        }

        $_POST[$key] = stripslashes(strip_tags($value));        
    }
}
?>

Phew. You’d think with all this ammunition against our army of spam we’d be ready to start doing some mailing! Unfortunately, you’d be wrong. There are few more checks that need to be put in place to help prevent mail manipulation and header injection (this is the process that involves adding the likes of “cc: someaddress@aol.com” to our fields to spam individuals). As the e-mail field is usually the one used to try and execute these attacks, we need to make sure our e-mail address is valid. We can do a basic check with another regular expression:

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";
    $profanity = "/(beastial|bestial|blowjob|clit|cock|cum|cunilingus|cunillingus|cunnilingus|cunt|ejaculate|fag|felatio|fellatio|fuck|fuk|fuks|gangbang|gangbanged|gangbangs|hotsex|jism|jiz|kock|kondum|kum|kunilingus|orgasim|orgasims|orgasm|orgasms|phonesex|phuk|phuq|porn|pussies|pussy|spunk|xxx)/i";
    $spamwords = "/(viagra|phentermine|tramadol|adipex|advai|alprazolam|ambien|ambian|amoxicillin|antivert|blackjack|backgammon|texas|holdem|poker|carisoprodol|ciara|ciprofloxacin|debt|dating|porn)/i";
    $bots = "/(Indy|Blaiz|Java|libwww-perl|Python|OutfoxBot|User-Agent|PycURL|AlphaServer)/i";

    if (preg_match($bots, $_SERVER['HTTP_USER_AGENT'])) {
        exit("<p>Known spam bots are not allowed.</p>");
    }
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        } elseif (preg_match($profanity, $value) || preg_match($spamwords, $value)) {
            exit("<p>That kind of language is not allowed through our form.</p>");
        }

        $_POST[$key] = stripslashes(strip_tags($value));       
    }
    
    if (!ereg("^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,6})$",strtolower($_POST['email']))) {
        exit("<p>That e-mail address is not valid, please use another.</p>");
    }
}
?>

Now we’re about ready to send the mail. We need to assign a recipient (that’s you), and create our mail headers/subject — these contain information on where the mail is coming from and who to reply to (the person filling in the form). We can also structure our message using the contents of the $_POST superglobal array (we use { } around variables and array keys in our message variable to prevent PHP throwing up errors; n adds a new line):

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";
    $profanity = "/(beastial|bestial|blowjob|clit|cock|cum|cunilingus|cunillingus|cunnilingus|cunt|ejaculate|fag|felatio|fellatio|fuck|fuk|fuks|gangbang|gangbanged|gangbangs|hotsex|jism|jiz|kock|kondum|kum|kunilingus|orgasim|orgasims|orgasm|orgasms|phonesex|phuk|phuq|porn|pussies|pussy|spunk|xxx)/i";
    $spamwords = "/(viagra|phentermine|tramadol|adipex|advai|alprazolam|ambien|ambian|amoxicillin|antivert|blackjack|backgammon|texas|holdem|poker|carisoprodol|ciara|ciprofloxacin|debt|dating|porn)/i";
    $bots = "/(Indy|Blaiz|Java|libwww-perl|Python|OutfoxBot|User-Agent|PycURL|AlphaServer)/i";

    if (preg_match($bots, $_SERVER['HTTP_USER_AGENT'])) {
        exit("<p>Known spam bots are not allowed.</p>");
    }
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        } elseif (preg_match($profanity, $value) || preg_match($spamwords, $value)) {
            exit("<p>That kind of language is not allowed through our form.</p>");
        }

        $_POST[$key] = stripslashes(strip_tags($value));       
    }
    
    if (!ereg("^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,6})$",strtolower($_POST['email']))) {
        exit("<p>That e-mail address is not valid, please use another.</p>");
    }
    
    $recipient = "YOU@YOUR-EMAIL.COM";
    $subject = "Contact Form Mail";
    
    $message = "You've received an e-mail through your website mail form: \n";
    $message .= "Name: {$_POST['name']} \n";
    $message .= "E-mail: {$_POST['email']} \n";
    $message .= "Country: {$_POST['country']} \n";
    $message .= "Feedback: {$_POST['comments']} \n";
    
    $headers = "From: YOUR WEBSITE NAME <$recipient> \n";
    $headers .= "Reply-To: <{$_POST['email']}>";
}
?>

After constructing our message we can actually send it. Sometimes message sending is not flawless, we’ll use an if statement to make sure it’s sent and give our visitor a message either way:

<?php
if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
    exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
} else {
    $exploits = "/(content-type|bcc:|cc:|document.cookie|onclick|onload|javascript|alert)/i";
    $profanity = "/(beastial|bestial|blowjob|clit|cock|cum|cunilingus|cunillingus|cunnilingus|cunt|ejaculate|fag|felatio|fellatio|fuck|fuk|fuks|gangbang|gangbanged|gangbangs|hotsex|jism|jiz|kock|kondum|kum|kunilingus|orgasim|orgasims|orgasm|orgasms|phonesex|phuk|phuq|porn|pussies|pussy|spunk|xxx)/i";
    $spamwords = "/(viagra|phentermine|tramadol|adipex|advai|alprazolam|ambien|ambian|amoxicillin|antivert|blackjack|backgammon|texas|holdem|poker|carisoprodol|ciara|ciprofloxacin|debt|dating|porn)/i";
    $bots = "/(Indy|Blaiz|Java|libwww-perl|Python|OutfoxBot|User-Agent|PycURL|AlphaServer)/i";

    if (preg_match($bots, $_SERVER['HTTP_USER_AGENT'])) {
        exit("<p>Known spam bots are not allowed.</p>");
    }
    foreach ($_POST as $key => $value) {
        $value = trim($value);

        if (empty($value)) {
            exit("<p>Empty fields are not allowed. Please go back and fill in the form properly.</p>");
        } elseif (preg_match($exploits, $value)) {
            exit("<p>Exploits/malicious scripting attributes aren't allowed.</p>");
        } elseif (preg_match($profanity, $value) || preg_match($spamwords, $value)) {
            exit("<p>That kind of language is not allowed through our form.</p>");
        }

        $_POST[$key] = stripslashes(strip_tags($value));       
    }
    
    if (!ereg("^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,6})$",strtolower($_POST['email']))) {
        exit("<p>That e-mail address is not valid, please use another.</p>");
    }
    
    $recipient = "YOU@YOUR-EMAIL.COM";
    $subject = "Contact Form Mail";
    
    $message = "You've received an e-mail through your website mail form: \n";
    $message .= "Name: {$_POST['name']} \n";
    $message .= "E-mail: {$_POST['email']} \n";
    $message .= "Country: {$_POST['country']} \n";
    $message .= "Feedback: {$_POST['comments']} \n";
    
    $headers = "From: YOUR WEBSITE NAME <$recipient> \n";
    $headers .= "Reply-To: <{$_POST['email']}>";
	
    if (mail($recipient,$subject,$message,$headers)) {
        echo "<p>Thank you! Your mail was successfully sent to the webmaster. Thank you for your time.</p>";
    } else {
        echo "<p>Sorry, there was an error and your mail was not sent. Please find an alternative method of contacting the webmaster.</p>";
    }
}
?>

…and that’s it! A mail form with spam checking, profanity block, basic header injection checks and malicious tag/exploit stripping. Don’t understand it? We recommend Jem’s pre-made free PHP mail form.

Comments

  1. Thanks for this Jem! Very useful and very easy to understand. I am new to PHP/MySQL and I am looking through the tutorials to understand more about the language.

    Just a heads up though, links to jem’s pre-made mail form is not working…

  2. Thanks Ren, I updated the link :)

  3. Hi Jem just found this “Gem”.

    However one thing I would like to do is have the thank you message in the same page once the form has been submitted. How do you do this? Have been looking around in PhP forums etc but there are many answers.

  4. There are two things you can do Den … first, and easiest, is use the premade form linked at the bottom of the tutorial, which is based on similar code but more up to date. That gives the option of both on and off page thanks.

    The second thing is to modify the code. You’d need to move the code from form-process.php to form.php above the form html. Then change:

    if (!isset($_POST['submit']) || $_SERVER['REQUEST_METHOD'] != "POST") {
        exit("<p>You did not press the submit button; this page should not be accessed directly.</p>");
    } else {

    to:

    if ($_SERVER['REQUEST_METHOD'] == "POST") {

    And in the form, the action=”form-process.php” will need to be changed to action=”form.php”

    This should work, but I’ve not tested it.

  5. focusfoxx says:

    I have copied the PHP script exactly from this site, so I know I didn’t make any typos. But I am having an issue. When I try to send a test email through my form, it says “You did not press the submit button; this page should not be accessed directly.” I have looked again and again to make sure everything looks correct, and it does. What could be the problem?

  6. It didnt work….here is the error i got below…….

    Deprecated: Function ereg() is deprecated in \\AMS1.GDHOSTING.GDG\N1NWHG800\hosting\66\9946066\html\form-process.php on line 27

    Warning: mail() [function.mail]: SMTP server response: 451 See http://pobox.com/~djb/docs/smtplf.html. in \\AMS1.GDHOSTING.GDG\N1NWHG800\hosting\66\9946066\html\form-process.php on line 43

    Sorry, there was an error and your mail was not sent. Please find an alternative method of contacting the webmaster.

  7. I am using your form and they keep getting sent to my spam folder. Is there anything I can do to prevent this?

  8. Noticed it the email address dosent check if the @ sign or .com * etc is included in the email address. Would that be ideal?

    Also wanting to use the inputted email address as the source address from my mail.server. is that easy to modify? Or is it a better option to look for some thing else more suitable?

    Thank for your time.

    • Yes, it does check for “@” and for an extension that matches .com, .co.uk etc.

      Your mails are likely to be rejected as spam if you set the from as the submitted email address. Use reply-to instead.

  9. Thanks for the simple introduction into creating a php form mail. I have little knowledge of php and had been relying on a perl based form, but it seemed very buggy.

  10. http://jemsmailform.com/
    found the form thank you

  11. Hi and thank you for this useful tutorial. I’m not really expert at php, but I managed to use the form in my web site (and it works!). The only thing is, I continuosly get spams from a couple of e-mail addresses. Is there any way to block a specific e-mail adress, like a “black list” or something similar?

Add Comment Register



Speak Your Mind

*