1

I have a website with a contact form on PHP and a mail server. Email are sent with the help of PHP mail function like so

$name = cleanInput($_POST["name"]);
$e = $_POST["email"];
$email = filter_var(cleanInput($e), FILTER_SANITIZE_EMAIL);
if (strpos($name, "@") !== false) {
    $msg = 'Invalid name!';
    exit();
}
else if (strcmp($e, $email) !== 0 ||
    filter_var($email, FILTER_VALIDATE_EMAIL) === false ||
    !checkdnsrr(substr(strrchr($email, "@"), 1))) {
    $msg = 'Invalid email address!';
    exit();
}
else
{
    $message = nl2br(cleanInput($_POST['message']));

    $subject = 'Support request';
    $to = 'support@domain.com';
    $sender = 'webmaster@domain.com';
    $headers = array(
        'From' => $name . '<' . $email . '>',
        'MIME-Version' => '1.0',
        'Content-type' => 'text/html; charset=iso-8859-1'
    );
    $result = mail($to, $subject, $message, $headers, '-r' . $sender);
}

function cleanInput($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}

Recently I've been attacked by a spammer who is posting emails with From field value like this

check@mydomain.com, this@mydomain.com, link@mydomain.com, 
"US:http"@mydomain.com://www.somedomain.com/page <somename@mail.com>

So I prohibited the @ character in the name field like so

if (strpos($_POST["name"], "@") !== false)
    exit()

I've tried sending a POST request with a name like name@ from Postman and it was rejected successfully but am still getting the same spam emails.

Any ideas please how the spammer is bypassing the validation check?

Update

Here is an example of a spam email with error_log of the raw input fields https://pastebin.com/29ppLGuX

Update2

Thanks to @mti2935 it was figured out that there is no injection peformed. The spammer is just putting a simple line of text with a link into the name field and the MTA (Postfix) to some reason is appending the domain name to all words in that text. I've tried playing with the data and found out that it is the colon which causes all this trouble.

If $name="one two three" then the header of the received email looks good
From: one two three<name@mail.com>

If $name="one: two three" then
From: one@mydomain.com:two three <name@mail.com>

If $name="one: two: three" then
From: "one:two"@mydomain.com:three <name@mail.com>

I've tried converting the header array into a string prior to feeding it into the mail and got the same results.

6
  • Side note: For a contact form, it's safer to not populate From with a user-controlled value at all.
    – Arminius
    Dec 22, 2019 at 23:11
  • Not sure if this is related, but something looks squirelly with the fifth argument to your call to the mail() function (additional_parameters). Are you trying to set the envelope sender here? If so, this should be -f, not -r. Also, you need a space after -f. Finally, $sender is not defined anywhere in your script. See stackoverflow.com/questions/17890134/… for an example of how to set both the envelope sender and the sender in the message headers when calling the php mail() function.
    – mti2935
    Dec 23, 2019 at 1:22
  • @mti2935, yes i'm setting the envelope sender. $sender value is hardcoded. There is no difference between the -r and -f flags. see this docs postfix.org/mailq.1.html and the space after the flag does not seem to matter because the emails are delivered as expected.
    – yaugenka
    Dec 23, 2019 at 10:09
  • In that case, it might be helpful if we could see the full source (including the headers) of one of the spam messages. Can you please post the full source of one of the messages on pastebin, and then post a link to that here?
    – mti2935
    Dec 23, 2019 at 11:30
  • Here it goes pastebin.com/qbUEsPzb. I replaced my actual domain name by domain
    – yaugenka
    Dec 23, 2019 at 21:12

3 Answers 3

1

The pastebin posts help a lot. First of all, we see from the unsanitized user inputs that you posted, that the spammer actually isn't entering inputs containing the @ character, after all, in 'name' field of your form. So that explains how it's getting past that check in your script.

It seems that what's happening is that your MTA is appending @yourdomain.com to these inputs, which is what MTA's default to doing when they encounter an email address without a domain.

So, that brings us to the question of why your MTA is treating these values, in the From: header, where you would normally have the sender's name, as email addresses instead of a name. I think it's related to the way that you are constructing the $headers array that you are passing as the fourth input to the mail() function. I think you need "\r\n" after each line in the headers, and the array needs to be converted to a string. There may be other things to look out for as well. See https://stackoverflow.com/questions/27478307/having-issue-on-setting-array-of-headers-in-php-mail-function for more info on how to get this right.

Update: It turns out that in addition to the issues described above, problems were being caused if the user included a colon in the sender's name field when filling out the form, as this character later manifested itself in the From: header of the message. See comments below by yaugenka for more info and for the solution.

7
  • You are partially right. According to the docs mail can accept headers as array php.net/manual/en/function.mail. Please take a look at the Update2 in the question.
    – yaugenka
    Dec 24, 2019 at 15:07
  • I think you're on the right track with your last attempt in Update2, where you created the headers as a string instead of using an array. But, you need to use double quotes to enclose \r\n, not single quotes, so that the backslash is interpreted as an escape character, instead of taken literally. See stackoverflow.com/questions/3446216/…. Also, you need a \r\n after the last line of the headers.
    – mti2935
    Dec 24, 2019 at 15:48
  • Yes, I have figured that out already and removed it from the question to illiminate confusion. Thanks anyway. Both array and string versions perform identically. But the colon issue remains.
    – yaugenka
    Dec 24, 2019 at 15:51
  • Looks like progress. I think the colon is causing problems, because the colon is used as the delimiter in the headers to separate the field name and field body. See rfc2822, section 2.2. As a test, I inserted a colon in my name in my mail client settings (Thunderbird) to see what it would do. Looking at the headers of the message that it sent, I see that it enclosed my name in the double quotes. So, the From header was like so: From: "my:name" <me@mydomain.com>. I think if you modify your code so that it builds the From: header the same way, it should solve the problem.
    – mti2935
    Dec 24, 2019 at 18:35
  • So the solution is either to prohibit colon at all or quote the value like so if (strpos($name, ":") !== false) $name = '"'.$name.'"'; with htmlspecialchars() applied beforehand.
    – yaugenka
    Dec 24, 2019 at 20:01
0

You're combining both $name and $email in the from header, but according to your post you only filter $name. The attacker (automated spam bot) is probably injecting into the $email variable.

3
  • I do filter the $email field as well. I did not show that code just for brevity. The From field of all those spam emails always ends with <$email> with a well formed email format which means it is clean.
    – yaugenka
    Dec 23, 2019 at 10:17
  • 1
    How do you expect informed answers if you don't post all the details?
    – wireghoul
    Dec 23, 2019 at 10:59
  • Sorry, I assumed that the provided example of the spam From field shows clearly that the $email part is well-formed.
    – yaugenka
    Dec 23, 2019 at 11:12
0

Out of the box, PHP provides direct access to various interfaces without a framework of checks. Sometimes its good to restrict people to operating within a strictly controlled sandbox with strict rules about what goes in and comes out. PHP is not such an environment.

Based on the code you have shown us, it would be trivial to exploit this. If you allow any user contributed text within email headers then an attacker can control the recipient, and several of the sender addresses. Restricting field length to 70 characters and disallowing '<', '>', ',', \r and \n from any field which might end up in an SMTP header would help, but still fall a long way short of a robust solution.

That you don't know how they did this also highlights that your code has insufficient logging.

1
  • Do you mean he might be injecting such a text which rewrites theFrom header?
    – yaugenka
    Dec 23, 2019 at 21:45

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.