51

I have a simple PHP mailer script that takes values from a form submitted via POST and mails them to me:

<?php
$to = "me@example.com";

$name = $_POST['name'];
$message = $_POST['message'];
$email = $_POST['email'];

$body  =  "Person $name submitted a message: $message";
$subject = "A message has been submitted";

$headers = 'From: ' . $email;

mail($to, $subject, $body, $headers);

header("Location: http://example.com/thanks");
?>

How can I sanitize the input?

Matt Hampel
  • 5,088
  • 12
  • 52
  • 78
  • 1
    If you're using a straight-up text field, there shouldn't be any htmlentities in your $_POST data. If you're using some sort of rich text editor that generates html, use html_entity_decode(). Be sure to strip control characters from the subject -- newline chars in the subject can screw up your email headers – Frank Farmer Jun 28 '09 at 18:46
  • 19
    @Frank Farmer: are you suggesting he should trust that no offensive code would reach his code just because he used a "straight-up textfield"? That's terrible advice. – Carlos Lima Jun 29 '09 at 10:11
  • 10
    Heh, straight-up text field. Just did an XSS test on someone who did that. They didn't like the amusing picture sent via an img link entered in the "straight-up text field". – Fiasco Labs Jan 30 '12 at 04:14
  • 6
    For the record for anyone looking at this in the future, @FrankFarmer 's statement is incorrect. An attacker, or anyone else, is not limited to using the HTML form you construct to send your data. There are many ways to construct HTTP requests against the script that handles your form; you should always assume that any data could possibly come into any field. – Aaron Wallentine Apr 20 '16 at 21:32

5 Answers5

53

Sanitize the post variable with filter_var().

Example here. Like:

echo filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);   
scrowler
  • 24,273
  • 9
  • 60
  • 92
Haim Evgi
  • 123,187
  • 45
  • 217
  • 223
  • I like filter_var for this, sadly, not all hosting setups have 5.2 yet, but it appears to be a well-thought-out function. – artlung Jun 28 '09 at 18:55
  • 2
    Not all hosting setups have 5.2 yet, hehe, that's so 2009. Now it's more like if they're still running 5.4 they're fishy. – markus Aug 21 '14 at 22:48
  • 1
    Make sure that nothing you pass to the mail() function (**especially** the $headers parameter) can contain unfiltered text, especially `\r\n(header): (...);` – Aaron Wallentine Apr 20 '16 at 21:46
13

Since you're not building an SQL query or anything here, the only relevant validation that I can see for those inputs is an email validation for $_POST["email"], and maybe an alphanumeric filter on the other fields if you really want to limit the scope of what the message can contain.

To filter the email address, simply use filter_var:

$email = filter_var($email, FILTER_SANITIZE_EMAIL);

As per Frank Farmer's suggestion, you can also filter out newlines in the email subject:

$subject = str_replace(array("\r","\n"),array(" "," "),$subject);
Wadih M.
  • 12,810
  • 7
  • 47
  • 57
  • 3
    Newline characters in email subject lines are somewhat problematic. – Frank Farmer Jun 28 '09 at 18:49
  • 11
    Depending on exaclty how the email is constructed,newline characters can be injected into any email-header allowing the attacker to add any new or replacement email headers he likes. In addition to this, injecting a whole blank line (two newlines) then allows the attacker to insert an email body of his choice, completely overriding the generated email. – Cheekysoft Jun 29 '09 at 10:03
5

As others have noted, filter_var is great. If it's not available, add this to your toolchest.

The $headers variable is particularly bad security-wise. It can be appended to and cause spoofed headers to be added. This post called Email Injection discusses it pretty well.

filter_var is great, but another way to assure that something is an email address and not something bad is to use an isMail() function. Here's one:

function isEmail($email) {
    return preg_match('|^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]{2,})+$|i', $email);
};

So to use this, you could do:

if (isset($_POST['email']) && isEmail($_POST['email'])) {
    $email = $_POST['email'] ;
} else {
    // you could halt execution here, set $email to a default email address
    // display an error, redirect, or some combination here,
}

In terms of manual validation, limiting the length using substr(), running strip_tags() and otherwise limiting what can be put in.

artlung
  • 33,305
  • 16
  • 69
  • 121
  • 1
    Talking about filter_var, it has also a filter to validate emails so your `isEmail` function is bad and unnecessary (not to mention that regex can't properly validate emails). –  Jan 13 '15 at 11:29
4

You need to remove any newlines from input provided by users in $headers, which gets passed to mail() ($email in your case)! See Email injection.

PHP should take care of sanitizing $to and $subject, but there are versions of PHP with bugs (Affected are PHP 4 <= 4.4.6 and PHP 5 <= 5.2.1, see MOPB-34-2007).

blueyed
  • 27,102
  • 4
  • 75
  • 71
1

You can use the code from artlung's answer above to validate email..

I use this kind of code to prevent header injection ..

// define some mail() header's parts and commonly used spam code to filter using preg_match
$match = "/(from\:|to\:|bcc\:|cc\:|content\-type\:|mime\-version\:|subject\:|x\-mailer\:|reply\-to\:|\%0a|\%0b)/i";

// check if any field's value containing the one or more of the code above
if (preg_match($match, $name) || preg_match( $match, $message) || preg_match( $match, $email)) {

// I use ajax, so I call the string below and send it to js file to check whether the email is failed to send or not
echo "failed";

// If you are not using ajax, then you can redirect it with php header function i.e: header("Location: http://example.com/anypage/");

// stop the script before it reach or executing the mail function
die();

}

The mail()'s header filtering above is too strict, since some users may be using the filtered strings in their message without any intention to hijack your email form, so redirect it to a page that is explaining what kind of strings that is not allowed in the form or explain it on your form page.

Ari
  • 4,643
  • 5
  • 36
  • 52