16
\$\begingroup\$

I'm nowhere near a PHP expert, but I have dabbled a bit. I can make things work just fine, but I am terrible at figuring out what is wrong. I was informed by our server admin that we have a mail script that is being abused, and I am unsure of how to remedy the issue.

I am looking for help to see how someone is using this form to send out spam mail. The issue is that we got a bounce-back email sent from an address that doesn't exist to a hotmail address using this form. As far as I know, this form ONLY sends email to the $recipient which is neither this non-existent email or this Hotmail. I am trying to figure out a way to secure this form so that individuals cannot abuse this form and send mail to others through it.

<?php
$name = check_input($_POST['name'],"Enter your name.");
$email = check_input($_POST['email'],"Email address is required.");
$company = check_input($_POST['company']);
$phone = check_input($_POST['phone']);
$message = check_input($_POST['message'],"Please don't send us a blank message! Let us know what you're thinking.");
if (!preg_match("/([\w\-]+\@[\w\-]+.[\w\-]+)/",$email))
{
 show_error("E-mail address is not valid.");
}
$messageBody ="Hello!
Your contact form has been submitted by:
Name: $name
E-mail: $email
Company: $company
Phone: $phone
Message:
$message
End of message
";
$recipient = "[email protected]"; // removed real email for SE
$subject = "[Contact Form - Site Name]"; // removed site name for SE
$mailheaders = "From: [email protected]\n"; // removed real email for SE
$mailheaders .= "Reply-To:". $_POST['email']."\n\n";
mail($recipient, $subject, $messageBody, $mailheaders);
header('Location: http://www.example.com/contact/thankyou.html'); //removed real address for SE
exit();
function check_input($data, $problem='')
{
 $data = trim($data);
 $data = stripslashes($data);
 $data = htmlspecialchars($data);
 if ($problem && strlen($data) == 0)
 {
 show_error($problem);
 }
 return $data;
}
function show_error($mailError)
{
?>
// error message in here, removed for SE as unrelated to problem
<?php
exit();
}
?>

This is the message my server admin sent me:

-----Original Message-----
From: [email protected]
[mailto:[email protected]]
Sent: Monday, August 04, 2014 2:47 PM
To: [email protected]
Subject: failure notice
Hi. This is the qmail-send program at ss2.site-hosts.com.
I'm afraid I wasn't able to deliver your message to the following addresses.
This is a permanent error; I've given up. Sorry it didn't work out.
<[email protected]>:
65.55.37.104 does not like recipient.
Remote host said: 550 Requested action not taken: mailbox unavailable Giving up on 65.55.37.104.
--- Below this line is a copy of the message.
Return-Path: <[email protected]>
Received: (qmail 6348 invoked by uid 2523); 4 Aug 2014 14:47:01 -0500
X-Qmail-Scanner-Diagnostics: from
199.192.201.219.rdns.continuumdatacenters.com by ss2.site-hosts.com (envelope-from <[email protected]>, uid 2020) with qmail-scanner-2.10st
 (clamdscan: 0.98.4/19261. mhr: 1.0. spamassassin: 3.3.2. perlscan:
2.10st. 
 Clear:RC:1(199.192.201.219):. 
 Processed in 0.024127 secs); 04 Aug 2014 19:47:01 -0000
Received: from 199.192.201.219.rdns.continuumdatacenters.com (HELO
ssasi.site-hosts.com) (199.192.201.219)
 by ss2.site-hosts.com with ESMTPA; 4 Aug 2014 14:47:01 -0500
Received: (qmail 15903 invoked by uid 10024); 4 Aug 2014 14:47:00 -0500
X-Qmail-Scanner-Diagnostics: from by ssasi.site-hosts.com (envelope-from <[email protected]>, uid 48) with qmail-scanner-2.10st
 (clamdscan: 0.98.4/19261. mhr: 1.0. spamassassin: 3.3.2. perlscan:
2.10st. 
 Clear:RC:1(127.0.0.1):. 
 Processed in 0.013521 secs); 04 Aug 2014 19:47:00 -0000
Date: 4 Aug 2014 14:47:00 -0500
Message-ID: <[email protected]>
From: [email protected]
To: [email protected]
Subject: 
X-PHP-Originating-Script: 10021:vnbMail.php
asked Aug 5, 2014 at 16:02
\$\endgroup\$
3
  • 10
    \$\begingroup\$ Welcome to Code Review, and thanks for your patience. Community members have decided that it's a fine question. \$\endgroup\$ Commented Aug 5, 2014 at 19:09
  • 1
    \$\begingroup\$ Q: Using a regular expression to validate an email address on StackOverflow. \$\endgroup\$ Commented Aug 6, 2014 at 19:40
  • \$\begingroup\$ Please note that most of the time spammers do not need your script to send an email from your email address. Such mails are also bounced back to the your email address. In that case spf and dkim may help authenticating the email . \$\endgroup\$ Commented Aug 7, 2014 at 11:28

5 Answers 5

19
\$\begingroup\$

I'm not a php expert, but from reading up on the documentation it appears that it may be possible to insert additional from/to addresses using the "Additional Headers" field on the mail() function.

Since you're using a regular string concatenation to insert your POST variable into your additional headers field, that is very well possibly the vector for attack. The example in the php mail() documentation demonstrates this.

<?php
$to = '[email protected]';
$subject = 'the subject';
$message = 'hello';
$headers = 'From: [email protected]' . "\r\n" .
 'Reply-To: [email protected]' . "\r\n" .
 'X-Mailer: PHP/' . phpversion();
mail($to, $subject, $message, $headers);
?>
answered Aug 5, 2014 at 19:15
\$\endgroup\$
0
27
\$\begingroup\$

Your script is vulnerable to a header-splitting attack. Due to the poor design of PHP's mail() function, it is actually quite easy to introduce that kind of security hole.

In summary, if...

  • any part of the mail headers consists of user-supplied input,
  • and you didn't make any effort to prohibit newlines or escape that input,

then you will have a program that can send mail to any recipient of the attacker's choice.

Validation problems

As @EmanuelePaolini points out, you do make an attempt to validate $_POST['email']:

$email = check_input($_POST['email'],"Email address is required.");
# ...
if (!preg_match("/([\w\-]+\@[\w\-]+.[\w\-]+)/",$email))
{
 show_error("E-mail address is not valid.");
}

However, the regular expression is not anchored with ^ and $ at each end. Therefore, the code confirms that $_POST['email'] contains an e-mail address, but it fails to enforce that it contains nothing but a single e-mail address.

On the other hand, the regular expression is also too strict: it rejects many valid characters that can legally appear in an e-mail address. Read RFC 5322 Sec 3.4 for the specification, or this summary of the older RFC 822 standard.

answered Aug 5, 2014 at 19:15
\$\endgroup\$
2
  • \$\begingroup\$ The only user supplied input which goes into the headers is $email which is checked at line 8. \$\endgroup\$ Commented Aug 6, 2014 at 22:49
  • 1
    \$\begingroup\$ @EmanuelePaolini But the regular expression is not anchored! I have revised the answer. \$\endgroup\$ Commented Aug 6, 2014 at 23:06
9
\$\begingroup\$

Never, EVER directly use a $_GET or $_POST variable without checking its content first. Instead of:

$mailheaders .= "Reply-To:". $_POST['email']."\n\n";

You should write a function like:

function secure_input($s){
 return sanitized_user_input_that_is_exactly_what_you_expect_and_nothing_else;
}

and then do

$mailheaders .= "Reply-To:". secure_input($_POST['email'])."\n\n";

on all $_GET and $_POST. Even cookies you might want to check, since they also come from user.

Malachi
29k11 gold badges86 silver badges188 bronze badges
answered Aug 6, 2014 at 11:18
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Using the built-in function filter_input() is a great idea too. $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); \$\endgroup\$ Commented Aug 6, 2014 at 14:51
  • 1
    \$\begingroup\$ "since they also come from user." And they can be trivially modified. =) \$\endgroup\$ Commented Aug 6, 2014 at 16:21
5
\$\begingroup\$
$mailheaders .= "Reply-To:". $_POST['email']."\n\n";

That's it, right there. Unsanitized user content straight into the mail header.

Add the following, early on:

# only one address allowed
if( 1 != preg_match_all("/@/", $_POST['email'], $unused) ) die();
# and no funny characters either
if( preg_match("/[^a-z0-9@._-]/", strtolower($_POST['email']) ) die();
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
answered Aug 6, 2014 at 0:12
\$\endgroup\$
5
  • 6
    \$\begingroup\$ According to RFC822, the following characters are all also legal in e-mail addresses, even without quoting: !#$%&'*+/=?^`{|}~ \$\endgroup\$ Commented Aug 6, 2014 at 0:43
  • \$\begingroup\$ And when was the last time you saw a legit email containing any of those characters? \$\endgroup\$ Commented Aug 6, 2014 at 3:52
  • 16
    \$\begingroup\$ I regularly use + in e-mail addresses, as in the suggested usage in GMail. You shouldn't violate standards just because you like to. \$\endgroup\$ Commented Aug 6, 2014 at 3:58
  • \$\begingroup\$ # are probably used by Twitter posting E-mails??? I don't use twitter so I don't know. \$\endgroup\$ Commented Aug 6, 2014 at 13:48
  • \$\begingroup\$ My email address has a * in it... \$\endgroup\$ Commented Aug 6, 2014 at 16:54
4
\$\begingroup\$

Another, very easy way of killing spam bots is to add an extra field called something inocuois like "subject" and make it focusable, but not visible, in the form. For example

<input type="text" name="subject" style="height: 1px; width: 1px; color: transparent;">

Now whenever someone fills any value into that field, assume it's a spam bot and silently skip the mail() call. Just return whatever normal confirmation page you display so as not to let on that you're protected.

answered Aug 6, 2014 at 16:57
\$\endgroup\$
2
  • 1
    \$\begingroup\$ What about people using screen readers to interact with the website? \$\endgroup\$ Commented Jul 10, 2018 at 20:57
  • \$\begingroup\$ aria-hidden="true" all but the most sophisticated spam bots will likely ignore the attribute. \$\endgroup\$ Commented Jul 11, 2018 at 8:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.