5
\$\begingroup\$

I need feedback on this code regarding security issues.

<?php
if(!isset($_POST['submit']))
{
    //This page should not be accessed directly. Need to submit the form.
    echo "error; you need to submit the form!";
}
$name = $_POST['name'];
$visitor_email = $_POST['email'];
$message = $_POST['message'];

//Validate first
if(empty($name)||empty($visitor_email)) 
{
    echo "Name and email are mandatory!";
    exit;
}

if(IsInjected($visitor_email))
{
    echo "Bad email value!";
    exit;
}

$email_from = 'someemail@some.com';//<== update the email address
$email_subject = "New Form submission";
$email_body = "You have received a new message from the user $name.\n".
    "Here is the message:\n $message".

$to = "someemail@some.com";//<== update the email address
$headers = "From: $email_from \r\n";
$headers .= "Reply-To: $visitor_email \r\n";
//Send the email!
mail($to,$email_subject,$email_body,$headers);
//done. redirect to thank-you page.
header('Location: thank-you.html');

I'm using this IsInjected($str) function to validate email addresses:

// Function to validate against any email injection attempts
function IsInjected($str)
{
  $injections = array('(\n+)',
              '(\r+)',
              '(\t+)',
              '(%0A+)',
              '(%0D+)',
              '(%08+)',
              '(%09+)'
              );
  $inject = join('|', $injections);
  $inject = "/$inject/i";
  if(preg_match($inject,$str))
    {
    return true;
  }
  else
    {
    return false;
  }
}

?>
\$\endgroup\$
3
\$\begingroup\$

In your first validation, I think you forgot to exit, you probably meant:

if(!isset($_POST['submit']))
{
    //This page should not be accessed directly. Need to submit the form.
    echo "error; you need to submit the form!";
    exit;
}

To validate email addresses, you can use the filter_var method, like this:

function is_valid_email($str) {
    return filter_var($str, FILTER_VALIDATE_EMAIL);
}

For example:

function check_email($email) {
    printf("checking '%s': %s\n", $email, is_valid_email($email) ? 'OK' : 'INVALID');
}
check_email('joe@example.com');
check_email('bogus');

will output:

checking 'joe@example.com': OK
checking 'bogus': INVALID

For more details and (caveats!), see the docs. Also some more examples here.

Your original IsInjected method might still be useful for other purposes, that's why I created a new method for validating emails. If you keep it I would rename it to hasInjectedChars or has_injected_chars. And these lines can be improved:

  if(preg_match($inject,$str))
    {
    return true;
  }
  else
    {
    return false;
  }

you could write simply:

return preg_match($inject, $str);
\$\endgroup\$
  • \$\begingroup\$ should I get rid of this: // Function to validate against any email injection attempts function IsInjected($str) { $injections = array('(\n+)', '(\r+)', '(\t+)', '(%0A+)', '(%0D+)', '(%08+)', '(%09+)' ); $inject = join('|', $injections); $inject = "/$inject/i"; if(preg_match($inject,$str)) { return true; } else { return false; } } and replace it with this: function is_valid_email($str) { return filter_var($str, FILTER_VALIDATE_EMAIL); } \$\endgroup\$ – Joe T. Boka May 9 '14 at 1:29
  • \$\begingroup\$ I inserted the filter_var method as you suggested and tested it. It seems I can write anything in the form email input box and it will submit it and goes through. I wonder why that happens? \$\endgroup\$ – Joe T. Boka May 9 '14 at 3:46
  • \$\begingroup\$ I updated my post with more examples and explanation. I don't see how it's possible that is_valid_email would not work. I tested and works for me (see the example in my post). Btw filter_var was introduced in PHP 5.2.0, make sure your PHP is recent enough. \$\endgroup\$ – janos May 9 '14 at 5:32
  • \$\begingroup\$ thanks again. Just want to make sure I am doing this right, pls advise: 1, I add this to the script: function is_valid_email($str) { return filter_var($str, FILTER_VALIDATE_EMAIL); } right above the line: function IsInjected($str) 2, I keep the isInjected method but rename it: has_injected_chars Is this what I have to do? if so, then I just test it by typing something into the form email box and submit it. If this works, what should happen after I submit, how do I know it worked exactly? \$\endgroup\$ – Joe T. Boka May 9 '14 at 6:20
  • \$\begingroup\$ And change if(IsInjected($visitor_email)) to: if(!is_valid_email($visitor_email)). Notice the ! there, so that you will exit if the email is NOT valid. After this, invalid emails shouldn't work. \$\endgroup\$ – janos May 9 '14 at 6:26
1
\$\begingroup\$

Just a short answer, because I've already posted a number of detailed reviews that deal with mail injection.

To validate an email address, all you really need to do is this:

if (!filter_var($email, FILTER_VALIDATE_EMAIL))
    throw new RuntimeException('invalid email address');

Optionally sanitizing that same email address first, to remove some, possibly harmful characters:

$email = filter_var($email, FILTER_SANITIZE_EMAIL);

That said, checking the email for dangerous characters is all very well, but don't forget about the rest of the user input. I'm not going to repeat myself here, just a couple of links to previous answers that deal with mail injection in detail:

And a fairly detailed article on this topic can be found here. It discusses libs and tools that are already out there, free for you to use. It shows their features and, more importantly, the caveats.

On your code:

if (!isset($_POST['submit']))
    echo '';

This basically checks if the form was submitted, but if it wasn't your code still is executed as if the form was posted. Don't do that. Either redirect, throw an exception or exit.
isset is indeed the best way to check if a post parameter exists, which is why it's odd to see that you don't check the other paremeters:

$name = $_POST['name'];

Should be:

$name = !isset($_POST['name']) ? null : $_POST['name'];

and so on. If you don't put that isset language construct (not a function, constructs are faster), then your code might emit notices:

$foo = array();
echo $foo['nonExistantIndex'];

Will issue a notice, because you're trying to access a non-existing index. to see the notices, set your ini file to the only correct development settings:

display_errors=1
error_reporting=E_STRICT | E_ALL

that way, any problem that exists in your code will be visible to you. This enables you to develop better code. Good practice should be the default/norm, not the goal.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.