#native_company# #native_desc#
#native_cta#

Preventing Spam When Using PHP’s Mail Function Page 2

By Ian Gilfillan
on March 27, 2008

How to avoid all of this spam

It’s not just bcc that should worry us. There are other strings that spammers can use. These include: content-type:, mime-version:, multipart/mixed, cc as well as bcc. Spammers prefer bcc, but they don’t always mind if everyone sees the full list of addresses they’ve spammed. I’m not going to focus on demonstrating all the possible exploits, rather, I’ll focus on preventing the exploits. Suffice to say there are all sorts of possibilities, including sending HTML attachments and the like. This next addition checks for the existence of certain suspicious strings in any of the submitted values. If they exist, the mail is not sent. Similarly, the existence of newline characters is usually an indication of something unusual going on.

mail.php (4)

<?php
$to      = "bob@domain_example.co.za";
$subject = $_REQUEST["subject"];
$body = $_REQUEST["body"];
$email = $_REQUEST["email"];

function is_valid_email($email) {
  return preg_match('#^[a-z0-9.!#$%&'*+-/=?^_`{|}~]+@([0-9.]+|([^s]+.+[a-z]{2,6}))$#si', $email);
}

function contains_bad_str($str_to_test) {
  $bad_strings = array(
                "content-type:"
                ,"mime-version:"
                ,"multipart/mixed"
		,"Content-Transfer-Encoding:"
                ,"bcc:"
		,"cc:"
		,"to:"
  );
  
  foreach($bad_strings as $bad_string) {
    if(eregi($bad_string, strtolower($str_to_test))) {
      echo "$bad_string found. Suspected injection attempt - mail not being sent.";
      exit;
    }
  }
}

function contains_newlines($str_to_test) {
   if(preg_match("/(%0A|%0D|n+|r+)/i", $str_to_test) != 0) {
     echo "newline found in $str_to_test. Suspected injection attempt - mail not being sent.";
     exit;
   }
} 

if (!is_valid_email($email)) {
  echo 'Invalid email submitted - mail not being sent.';
  exit;
}

contains_bad_str($email);
contains_bad_str($subject);
contains_bad_str(body);

contains_newlines($email);
contains_newlines($subject);

$headers = "From: $email";
mail($to, $subject, $body, $headers);
echo "Thanks for submitting.";
?>

Further hardening

I’m partly hesitant to discuss the next part, as these additions are not strictly speaking necessary. However, I’m in favour of doing them in principle, and they’re worth mentioning to get developers thinking along those sorts of lines. They have uses beyond just mail injection. They certainly don’t provide a foolproof technique to avoid abuse. They involve doing what you can to ensure that a script is only accessed in the right context. If it should only ever be called as part of a POST procedure, ensure that it tests for this and doesn’t permit access if it’s anything else. Here’s the script with this addition.

mail.php (5)

<?php
$to      = "bob@domain_example.co.za";
$subject = $_REQUEST["subject"];
$body = $_REQUEST["body"];
$email = $_REQUEST["email"];

$dodgy_strings = array(
                "content-type:"
                ,"mime-version:"
                ,"multipart/mixed"
                ,"bcc:"
);

function is_valid_email($email) {
  return preg_match('#^[a-z0-9.!#$%&'*+-/=?^_`{|}~]+@([0-9.]+|([^s]+.+[a-z]{2,6}))$#si', $email);
}

function contains_bad_str($str_to_test) {
  $bad_strings = array(
                "content-type:"
                ,"mime-version:"
                ,"multipart/mixed"
		,"Content-Transfer-Encoding:"
                ,"bcc:"
		,"cc:"
		,"to:"
  );
  
  foreach($bad_strings as $bad_string) {
    if(eregi($bad_string, strtolower($str_to_test))) {
      echo "$bad_string found. Suspected injection attempt - mail not being sent.";
      exit;
    }
  }
}

function contains_newlines($str_to_test) {
   if(preg_match("/(%0A|%0D|n+|r+)/i", $str_to_test) != 0) {
     echo "newline found in $str_to_test. Suspected injection attempt - mail not being sent.";
     exit;
   }
} 

if($_SERVER['REQUEST_METHOD'] != "POST"){
   echo("Unauthorized attempt to access page.");
   exit;
}

if (!is_valid_email($email)) {
  echo 'Invalid email submitted - mail not being sent.';
  exit;
}

contains_bad_str($email);
contains_bad_str($subject);
contains_bad_str(body);

contains_newlines($email);
contains_newlines($subject);

$headers = "From: $email";
mail($to, $subject, $body, $headers);
echo "Thanks for submitting.";
?>
Similarly, if your mail script should only ever be called from a particular page, check to see if the user is coming from the right referer. Any attempt to directly access the page, or call it from any other page, should also result in an error, with further access denied. This sort of thing can be done with PHP, but is perhaps best done through the web server itself. However, the referer can also be faked. A further suggestion, which I’ve never implemented myself but makes a lot of sense, is to do some form of IP checking. If the IP has not visited the specific page shortly prior to calling the script, deny access. This avoids the problem of using a fake referer and going directly to the mail script.

Conclusion

Of course there are all sorts of things that you can do to enhance this script. You may want to log spam attempts either to a file or database. Useful data to capture includes the IP, referrer, user-agent, request string and request method. The request string can provide great insight into the minds of the spammers, as there will usually be quite a few attempts from the same source. The effort is worth it for the spammer, as they’re putting in the work hoping to get a long-term benefit – a script that they can continue to exploit for a long time. Implement these changes, and your scripts should be a lot more secure – but don’t sit back after that. Keep your eyes open. Security needs constant attention! Good luck.