Hi All,

Hope you're well - sorry it's been a while since I posted here, but I'm back because you were all so helpful last time!

I've been getting SPAM attacks on my PHP forms on my website. The emails I get are similar to the following and tend to come in in batches of about 5, hitting in the middle of the night:

CUSTOMER REQUEST: Request Free Consultation
Full Name:
Select... Kaitlyn Kaitlyn

Daytime Telephone:
48018230576

Evening Telephone:
23430470775

E-Mail:
freelife@yahoo.com

Full Address:

tQQFatWLHTzMZwHadO
mEADgmWHnTeTuhrRiBS
NY
USA
4360

Estimated House Value:
ZOoNFiApJkd

Mortgage Value:
2rand[0,1,1]

Mortgage Arrears:
Yes

Non-mortgage Loans (blank if unsure):
2rand[0,1,1]

Sale Speed Required:
Select...

Sale Reason:
Select...

Other Sale Reason:

How did you hear about us?
Select...

Accepts T&Cs:

Message:
How long have you lived here? Purchase Stendra after the ten calendar days has elapsed. Fees for the individual background checks are included in the PharmCAS Erythromycin Ophthalmic Ointment Rx List A Certified True Copy must be used only: Buy Bimatoprost Online Drug X has been shown to cause green rash with purple spots. Maxalt 10 Mg their area of practice): practice ethically and with compassion for patients accept personal responsibility for patient outcomes have professional training, experience, and competence commensurate with their utilize clinical and scientific publications in clinical care decision making and evidence- have a desire to educate others (patients, care givers, other health care professionals, have an aptitude to facilitate learning be able to document and assess student performance have a systematic, self-directed approach to their own continuing professional collaborate with other health care professionals as a member of a team be committed to their organization, professional societies, and the community

My code for this particular form is as follows:

<?php

/* Subject and e-mail variables */

$emailSubject = 'Request Free Consultation';
$webMaster = 'me@mydomain.com';
$formSuccess = "http://www.mydomain.co.uk/form-success.html";
$formError = "http://www.mydomain.co.uk/form-error.html";

/* Gathering data variables */

$titleField = $_POST['title'];
$firstnameField = $_POST['first_name'];
$surnameField = $_POST['surname'];
$emailField = $_POST['email'];
$daytimetelField = $_POST['daytime_tel'];
$eveningtelField = $_POST['evening_tel'];
$address1Field = $_POST['address1'];
$address2Field = $_POST['address2'];
$townField = $_POST['town'];
$countyField = $_POST['county'];
$postcodeField = $_POST['postcode'];
$housevalueField = $_POST['house_value'];
$mortgagevalueField = $_POST['mortgage_value'];
$mortgagearrearsField = $_POST['mortgage_arrears'];
$nonmortgageloansField = $_POST['non_mortgage_loans'];
$salespeedField = $_POST['sale_speed'];
$salereasonField = $_POST['sale_reason'];
$otherreasonField = $_POST['other_reason'];
$marketingsourceField = $_POST['marketing_source'];
$messageField = $_POST['message'];
$termsandconditionsField = $_POST['terms_and_conditions'];

/* Security */

if ( preg_match( "/[\r\n]/", $firstnameField ) || preg_match( "/[\r\n]/", $surnameField ) || preg_match( "/[\r\n]/", $emailField ) ) {
	header( "Location: $formError" );
	exit ;
}

/* Message to WebMaster */

$body = <<<EOD
<br><hr><b>CUSTOMER REQUEST:</b> Request Free Consultation<hr><br>
<b>Full Name:</b> <br>$titleField $firstnameField $surnameField <br><br>
<b>Daytime Telephone:</b> <br>$daytimetelField <br><br>
<b>Evening Telephone:</b> <br>$eveningtelField <br><br>
<b>E-Mail:</b> <br>$emailField <br><br>
<b>Full Address:</b><br> <br>$address1Field <br>$address2Field <br>$townField <br> $countyField <br> $postcodeField <br><br>
<b>Estimated House Value:</b> <br>$housevalueField <br><br>
<b>Mortgage Value:</b> <br>$mortgagevalueField <br><br>
<b>Mortgage Arrears:</b> <br>$mortgagearrearsField <br><br>
<b>Non-mortgage Loans (blank if unsure):</b> <br>$nonmortgageloansField <br><br>
<b>Sale Speed Required:</b> <br>$salespeedField <br><br>
<b>Sale Reason:</b> <br>$salereasonField <br><br>
<b>Other Sale Reason:</b> <br>$otherreasonField <br><br>
<b>How did you hear about us?</b> <br>$marketingsourceField <br><br>
<b>Accepts T&Cs:</b> <br>$termsandconditionsField <br><br>
<b>Message:</b><br> $messageField
EOD;

$headers = "From: $emailField\r\n";
$headers .= "Content-type: text/html\r\n";
$success = mail($webMaster, $emailSubject, $body, $headers);

/* Successful Submission Redirect */

header( "Location: $formSuccess" );
exit ;

?>

If I'm correct in my thinking that the SPAM-bots are using my site to send SPAM emails, my question is, in the interests of keeping the web a nicer place, how can I make my code no longer work for them?

Many thanks for your help,

Max

    Yes, it is possible to use your site to send spam. The reason is the way the header fields are constructed. In short, every header field ends with \r\n (CRLF), and the headers are separated from the body by \r\n

    headerfield1: value1\r\n
    ..
    headerfieldn: valuen\r\n
    \r\n
    body
    

    This means that anything going into the email headers need to be properly escaped or validated to prevent header field injection. The 4th formal parameter of the mail function is used to set headers. This corresponds to your $headers variable. But subject and recipient are also set in the headers which in your code corresponds to $webMaster and $emailSubject.

    Since you only ever assign fixed string literals to $webMaster and $emailSubject, and neither of those contain \r\n, they are safe

        $emailSubject = 'Request Free Consultation';
        $webMaster = 'me@mydomain.com';
    

    And if they weren't safe, you'd obviously change the code directly since these are set by you, not by user input.

    This leaves the stuff assigned to $headers

        $headers = "From: $emailField\r\n";
        $headers .= "Content-type: text/html\r\n";
    

    Once again, the second line is a string literal which you have specified, and it's all good. All that remains is to make sure $emailField is safe.

    To get a full understanding of how this all works and what you need to do, you'd need to read the RFCs which specify the format of emails, or more specifically the header fields. And then you'd also need to know exactly how your server treats emails. For example, while \r and \n are not allowed to ever occur separately (i.e. they must always occur as the two byte sequence \r\n) it is entirely possible that your email server would happily send an email where one or more header fields are separated by \n only.

    Assuming $emailField contains the string

    john@doe.com\r\nbcc:spamreciever1@example.com,spamreciever2@example.com
    

    which would turn into (not showing the CRLF characters but representing them as new lines instead)

    john@doe.com
    bcc:spamreciever1@example.com,spamreciever2@example.com
    

    and thus two blind carbon copies are sent.

    In this case, since the from header field is supposed to take one single email address, you can simply use filter_var to make sure that it is

    $filtered = filter_var($emailField,FILTER_VALIDATE_EMAIL);
    
    if (!$filtered)
    {
        # not an email address. do not send email
    }
    else
    {
        # safe to use 
        $headers = "from: $filtered\r\n";
    }
    

    Once this is done, you may still get spam - but you will be the only one to get it. Annoying but you do not pollute the world. If you wish to prevent even this, you might think about implementing captcha a user has to solve before sending an email. reCAPTCHA is one solution.

      a month later

      Hi johanafm,

      Thanks so much for your help - I'm sorry it's taken so long to follow up (hope you got my PM).

      Is it sufficient to simply copy the code at the bottom of your message above into my existing code, or will I have to edit it first? You'll have to excuse me, I'm a PHP novice.

      Best wishes,

      Max

        4 days later

        You will have to edit it slightly. In my code above, send the email in the else case while doing nothing in the if (!$filtered) case.

          7 days later

          Hi Johana,

          Would the following work? Have to apologise for my lack of PHP knowledge!

          Many thanks,

          Max

           <?php
          
          /* Subject and e-mail variables */
          
          $emailSubject = 'Request Free Consultation';
          $webMaster = 'me@mydomain.com';
          $formSuccess = "http://www.mydomain.co.uk/form-success.html";
          $formError = "http://www.mydomain.co.uk/form-error.html";
          
          /* Gathering data variables */
          
          $titleField = $_POST['title'];
          $firstnameField = $_POST['first_name'];
          $surnameField = $_POST['surname'];
          $emailField = $_POST['email'];
          $daytimetelField = $_POST['daytime_tel'];
          $eveningtelField = $_POST['evening_tel'];
          $address1Field = $_POST['address1'];
          $address2Field = $_POST['address2'];
          $townField = $_POST['town'];
          $countyField = $_POST['county'];
          $postcodeField = $_POST['postcode'];
          $housevalueField = $_POST['house_value'];
          $mortgagevalueField = $_POST['mortgage_value'];
          $mortgagearrearsField = $_POST['mortgage_arrears'];
          $nonmortgageloansField = $_POST['non_mortgage_loans'];
          $salespeedField = $_POST['sale_speed'];
          $salereasonField = $_POST['sale_reason'];
          $otherreasonField = $_POST['other_reason'];
          $marketingsourceField = $_POST['marketing_source'];
          $messageField = $_POST['message'];
          $termsandconditionsField = $_POST['terms_and_conditions'];
          
          /* Security */
          
          if ( preg_match( "/[\r\n]/", $firstnameField ) || preg_match( "/[\r\n]/", $surnameField ) || preg_match( "/[\r\n]/", $emailField ) ) {
              header( "Location: $formError" );
              exit ;
          }
          
          $filtered = filter_var($emailField,FILTER_VALIDATE_EMAIL);
          
          if (!$filtered)
          {
              exit ;
          }
          else
          { 
          
          /* Message to WebMaster */
          
          $body = <<<EOD
          <br><hr><b>CUSTOMER REQUEST:</b> Request Free Consultation<hr><br>
          <b>Full Name:</b> <br>$titleField $firstnameField $surnameField <br><br>
          <b>Daytime Telephone:</b> <br>$daytimetelField <br><br>
          <b>Evening Telephone:</b> <br>$eveningtelField <br><br>
          <b>E-Mail:</b> <br>$emailField <br><br>
          <b>Full Address:</b><br> <br>$address1Field <br>$address2Field <br>$townField <br> $countyField <br> $postcodeField <br><br>
          <b>Estimated House Value:</b> <br>$housevalueField <br><br>
          <b>Mortgage Value:</b> <br>$mortgagevalueField <br><br>
          <b>Mortgage Arrears:</b> <br>$mortgagearrearsField <br><br>
          <b>Non-mortgage Loans (blank if unsure):</b> <br>$nonmortgageloansField <br><br>
          <b>Sale Speed Required:</b> <br>$salespeedField <br><br>
          <b>Sale Reason:</b> <br>$salereasonField <br><br>
          <b>Other Sale Reason:</b> <br>$otherreasonField <br><br>
          <b>How did you hear about us?</b> <br>$marketingsourceField <br><br>
          <b>Accepts T&Cs:</b> <br>$termsandconditionsField <br><br>
          <b>Message:</b><br> $messageField
          EOD;
          
          $headers = "From: $filtered\r\n";
          $headers .= "Content-type: text/html\r\n";
          $success = mail($webMaster, $emailSubject, $body, $headers);
          }
          
          /* Successful Submission Redirect */
          
          header( "Location: $formSuccess" );
          exit ;
          
          ?>

            Yes, that seems to be correct. But you may wish to change one thing.

            if (!$filtered)
            {
                exit ;
            } 
            

            Assuming the user makes an honest mistake, you may want to re-show the form again with all values prefilled and indicate that the email address is not valid. Or at the very least take them to the form-error page. Just doing "exit;" and leaving them with a blank page is not very user friendly.

              Write a Reply...