Prevent php web contact form spam
Asked Answered
S

12

41

I am an amateur web designer, I have searched on stackoverflow.com and other websites and have found many fixes for this issue I'm having, but none of them have worked (probably because I implement them incorrectly). I'm hoping someone with more knowledge can help me with a simple fix or show me how to implement one of the fixes I've found.

The problem: I have a very simple php contact form on my business's website. It has worked great for years, but in the last week has been hacked. I now receive hundreds of contact form submissions a day with no comments, they only have (apparently valid) email addresses, and a string of characters in the name field (like "58ee8b52eef46").

I have tried several techniques to prevent this spam, and they either break my php form, or they don't prevent the spam. If possible I would like a solution that does NOT require a Captcha distorted text test, and does NOT require all fields of the form to be filled.

Here is my full PHP code:

<?php
if(isset($_POST['email'])) {
  $email_to = "[email protected]";
  $email_subject = "website form submission";

  function died($error) {
    echo "We are very sorry, but there were error(s) found with the form you submitted. ";
    echo "These errors appear below.<br /><br />";
    echo $error."<br /><br />";
    echo "Please go back and fix these errors.<br /><br />";
    die();
  }

  if (!isset($_POST['name']) ||
    !isset($_POST['email']) ||
    !isset($_POST['telephone']) ||
    !isset($_POST['comments'])) {
    died('We are sorry, but there appears to be a problem with the form you submitted.');       
  }

  $name = $_POST['name'];
  $email_from = $_POST['email'];
  $telephone = $_POST['telephone'];
  $comments = $_POST['comments'];

  $error_message = "";
  if(strlen($error_message) > 0) {
    died($error_message);
  }
  $email_message = "Form details below.\n\n";

  function clean_string($string) {
    $bad = array("content-type","bcc:","to:","cc:","href");
    return str_replace($bad,"",$string);
  }

  $email_message .= "Name: ".clean_string($name)."\n";
  $email_message .= "Email: ".clean_string($email_from)."\n";
  $email_message .= "Telephone: ".clean_string($telephone)."\n";
  $email_message .= "Comments: ".clean_string($comments)."\n";

  $headers = 'From: '.$email_from."\r\n" .
             'Reply-To: '.$email_from."\r\n" .
             'X-Mailer: PHP/' . phpversion();
  @mail($email_to, $email_subject, $email_message, $headers);  
?>

Thank you for contacting us. We will be in touch with you soon. You will now be redirected back to example.com.
<META http-equiv="refresh" content="2;URL=http://www.example.com">

<?php
}
die();
?>
Solubilize answered 12/4, 2017 at 20:32 Comment(11)
Have you looked at captchaTransducer
Im sure i have mentioned this to you before, but google recaptcha is invisable for most legitimate visitors now.Broach
I have considered using Captcha. But I would prefer a fix that does not use captcha to keep a clean look on my website.Solubilize
More like form spoofingQuitrent
I have considered reCaptcha as well, but again I would prefer a backend fix so I can keep a clean look on my website.Solubilize
Its invisible, how much cleaner can you get?Broach
I've done more research on reCaptcha, I didn't realize it was invisible. I'm reading about it on developers.google.com, and it seems very complicated to implement (for my simple mind).Solubilize
Fair enough its not the simplest to implement, i created an answer with a simpler (though less robust) solutionBroach
Possible duplicate of How to prevent robots from automatically filling up a form?Marandamarasca
Nobody should use reCaptcha ever. It's unethical because in order to use it, you are required to purposely infect your website with spyware targeting your users. Anyone who doesn't consent to the tracking (such as by blocking third-party scripts from Google) is prevented from using any part of your site "protected" by reCaptcha.Patronizing
Too many times I have had to do over a dozen clicks to get past a Captcha, as they now replace some pictures with new ones. Real PITA. Please avoid using them!Seidel
B
79

A simple trick is to create a honeypot field:

html

<!-- within your existing form add this field -->
<input type="text" id="website" name="website"/>

css

/*in your css hide the field so real users cant fill it in*/
form #website{ display:none; }

php

//in your php ignore any submissions that inlcude this field
if(!empty($_POST['website'])) die();
Broach answered 12/4, 2017 at 20:55 Comment(13)
Thank you! This is very simple and easy to implement. It meets all of my criteria. However, currently the spam I'm getting has the 'comments' field empty. What if it also leaves the ''websites' field empty, won't I still get the spam?Solubilize
Yes, website is a good general choice, because most dumb spambots are looking to drop a link. You can add as many honeypot fields as you like. You could even add a honeypot called email, and rename your real email field to something else (like emailaddress).Broach
I tried to do that and inserting the fields and hiding them worked fine, but I cannot find the correct position where to enter the PHP code (with the die) into my PHP file. It just stops anywhere, even when those fields are not filled in.Phlegmatic
@Phlegmatic change from isset to !empty that was a mistake in my answer - empty form fields are submitted as empty strings, so will always be setBroach
Actually, form spammers will be able to 'find' that hidden field. So the technique is not very effective, IMHO. You are better off using some sort of 'human detection', like checking for focus on a field. That (along with some other things) is the technique I used on my FormSpammerTrap code. Much more effective technique, IMHO.Steak
@rickhellewell sure it's not foolproof by any means, but it is very quick to implement and still blocks a good 90% if form spanbots, which are not JavaScript or CSS awareBroach
This post is 2 years old and no longer seems to do anything. I have tried this on a few websites and the bots seem to take this technique into account.Tenorite
@Tenorite yes, it only protects against dumb bots - the OP specifically wanted a simple to implement solution, thats invisible to users. My prefered solution is recaptcha v2Broach
Had the problem of Browser auto-completing one or more of my honeypots. Fix was to add " autocomplete='off' " attribute to each honeypot element.Escarp
Combining honeypot with time taken to fill form (under 5 seconds highly likely to be spam) and random number generation had been highly effective. Captcha are too much for a simple contact form, although they have improved.Preamble
If the bot is designed for a specific website, it would not fall in this trapHeedful
@Preamble how do you use random number generation in securing the form? Do you refer to nonce?Dogleg
@S.W.G here's a simple example. #28338914Preamble
R
27

An even simpler approach that works for me. Literally all spam that I receive(d), had a url in the message. So I filter on that, and have not received any spam messages since. I used to get about 10 a week.

Add this under your line   $error_message = "";   in your php-file:

if(preg_match('/http|www/i',$comments)) {
    $error_message .= "We do not allow a url in the comment.<br />";
  }

The /i in the preg_match makes it case independent. The 'http' also filters for 'https'.

Ruth answered 26/7, 2019 at 11:9 Comment(3)
I've been using this on a site that receives a lot of spam from bots and it works! How would you add a second input, for example, address? I tried below and neither worked. if(preg_match('/http|www/i',$company)) { and if(preg_match('/http|www/i',$message)) { if(preg_match('/http|www/i',$message,$company)) { echoOvation
Oh, brilliant !Bodoni
Except that some times you need to let a URL be in the message. My FormSpammerTrap solution (now at version 10, soon to be version 11) allows for multiple URLs plus a lot of other customization of the form. Very effective techniques used in the code - my sites don't get spam.Steak
P
5

Create a form field and hide it for the users. In the php script check if this field is submitted, but empty. Now you know the request is from your form and a user.

Spam will fill the hidden field, or if they use your php script direct the spam protection field is not set.

HTML

<input name="website" type="text" class="website"/>

CSS

form .website{ display:none; } /* hide because is spam protection */

PHP

# spam protection
if (isset($_POST["website"]) && $_POST["website"] == "") {
  # your php code to mail here
} else {
  http_response_code(400);
  exit;
}

You can find more method's how to protect a php form spam here: https://zinoui.com/blog/protect-web-forms-from-spam

Pozsony answered 27/5, 2018 at 11:44 Comment(1)
It's very easy for a robust spam bot to find and submit hidden fields. And those bots can be highly automated to find and exploit forms.Steak
B
5

Usually the bots submit a form very fast. So, based on that, another solution could be to add another hidden field that contain the number of seconds that passed from when the page was oppened. This can be done using JavaScript. Then check it in PHP. If the number of seconds is smaller than 5 seconds then it's spam (It's more likely that the real client needs more time to fit the form). You can adjust the number of seconds based on how many fields the form contain.

Blouse answered 22/10, 2019 at 9:48 Comment(2)
You can combine the two and generate a random number or similar. I've found a combination to be highly effective.Preamble
I would imagine most bots wouldn't execute JavaScript code, and so the field would be populated either with random garbage or not at all. This could also happen if a legit user accesses the form with JS disabled. How about doing this on the server side, with a hidden field containing a timestamp for when the form was delivered and this checked when the user (or spambot) submits it?Ternate
S
4

Hidden fields, silly questions (what is 3+4?), etc, are not very effective at blocking spam on forms.

I researched this several years ago, and came up with a solution I call "FormSpammerTrap". It uses JavaScript code to 'watch' for focus/onclick on required fields. Automated processes, unless highly customized for a specific site (which takes more time than spambot owners want to take), can't 'focus/onclick' a required field.

I have a free solution at my www.FormSpammerTrap.com site. And there's a form there that spambots can try to spam...and they haven't, for more than 3 years. You are welcome to try it out...it's all open source, so you can see how it works. (And, if you use the form, I don't harvest your email. I reply once, then delete your email.)

My technique is much more effective in blocking spambots. They haven't been able to spambot the contact form on that site.

Steak answered 25/4, 2018 at 2:27 Comment(5)
Not sure why the downvote: **Added 12 Jul 2018 ** The trick is to add an on-click/on-focus event that changes the action parameter to the actual processing page. Otherwise, the default value I use is a honeytrap-type site. I think it's hard for a spammer to simulate those events, although possible perhaps. The technique blocks a lot of bot-spammers. And still, after a couple of years using the technique on that site, the form hasn't been spammed by bots. (I define a bot spammer that sends multiple submits via the attack, not just one submit.) Works for me.Steak
Is your system compatible with legit form fillers such as 1Password?Americanism
Should be, although I've never tried it. It just has some Javascript to change things with an on-click and on-focus event. Since spam bots can't do Javascript (easily), this blocks a lot of them. You can easily add reCaptcha to the form if you want. Nothing fancy about it, just implemented into the forms so all you need to do is change a few variables, include a file, and call the function that 'builds' the form. It's free to try out and use; full featured. Check out the site for details.Steak
Form fillers work with FormSpammerTrap (FST). And much customization is available to build a form with all types of fields. Bot protection is excellent; I've never gotten automated spam with my solution. Version 11 to be released soon. All the details on the FST site.Steak
Up to version 14 now; working on version 15. V14 has tons of new features (languages and more) and there are tons of customizable features. You can add FST spam blocking to any form. Still all free. See v14 in use on my FormSpammerTrap site (link above). Been around since 2011 - still the best solution, IMHO.Steak
P
3

For my answer, since i got through this i will simply add some new possibilities because the honeypot by Steve is extremely good but got hijaked (perhaps new softwares for spamming these days) and the handmade captcha like (what is 3+4) did not work for me even with random numbers, it worked for a while but stopped working after some time. Don't know how they got past this but i had to add some codes...

So i managed to get the IP of the spammer like this:

$ip = $_SERVER['REMOTE_ADDR'];

Then added $ip to the sent email by my php code in the subject then added this code to check the results without spamming my inbox:

if ($ip == '1.1.1.1') /*<-- this is an example*/
{
 $fp = fopen('spam_log.txt', 'a');
 fwrite($fp, 'Inputname: '.$inputname.' IP: '.$ip."\n");
 fclose($fp); die;
}

With this, i was safe for a while because the ip of the spammer did not change very often.

What i got the most were urls so i added this:

if (strstr($inputname, 'http')){die;} /*<-- did that for each input i had*/
if (strstr($inputname, 'www')){die;} /*<-- did that for each input i had*/

But sometimes i received spams without urls... by chance i got essentially cyrillic spam emails so i used this code:

$inputname_cyrillic = (bool) preg_match('/[\p{Cyrillic}]/u', $inputname);
if ($inputname_cyrillic){die;}

You can also add Arabic or Greek if needed that works fine since i cannot read these languages and am not interested in it.

If you're Russian or Arabic or Greek you can do the reverse and simply add the {Common} code which is A-Z related if you only want to receive cyrillic or Arabic, or Greek characters.

This thread helped me a lot so i wanted to contribute of my experience.

Progression answered 3/1, 2022 at 0:0 Comment(0)
S
2

I have analyzed the spam I get for some time now. I have noticed that:

  1. There is an overlap in time to submit form between spam and non-spam.

    I don't know if the slow spam is human submitted or the bots have been programmed to wait, but the slowest spam I get takes 26 seconds, and some non-spamming humans are faster than that (8 seconds).

    I often compose messages in a text editor and mostly don't submit them right away, so when I submit the message I visit the website and then copy-paste the message to the form, and submitting the message only takes a few seconds. I guess that's what these non-spammers do.

    7% of the spam is slower than 5 seconds.

  2. Only about half the spam fills in the hidden honeypot field (48%).

  3. Most spam contains an URL in the message body, but about 3% does not and some non-spam does.

Together, these three methods filter out the vast majority of the spam I get, but there are quite a few false negatives and some false positives.

But I have found one single indicator that detects 100% of my spam and does not mistake any non-spam for spam and that is

the language of the message.

I run a non-English website and all my spam is in English, while none of the non-spam is in English.

Therefore, all I have to do is search the message body for words like "you" that are common to all messages and do not exist in my own language.

This might not help those that run a website for an English-speaking audience, but many websites aren't in English, and for them this might be an additional indicator of spam.

Silurian answered 17/4, 2021 at 5:35 Comment(0)
H
0

If the spam you're getting does not have a comment, why not simply add a check for that? There's absolutely no reason for a real, human visitor to submit your contact form without a comment.

Since you don't want to add a captcha, the easiest solution in the short term would be to check that the comment is a minimum number of characters and contains at least a certain number of words.

For example:

$comments = trim($_POST['comments']); // trim() to strip off whitespace from beginning and end, like spaces and linebreaks

if (strlen($comments) < 20 || substr_count($comments, " ") < 3) {
    died('Your comment is too short.');
}

This is a very simple check to see that the comment contains at least 20 characters and at least 3 spaces (4 words). Tweak as needed.

Hoyt answered 12/4, 2017 at 20:42 Comment(3)
Hi, thank you for this answer. I have implemented another fix similar to yours. However we occasionally get a form submission from someone who simply wants to know more information, and only includes and email or phone number with no comments. We have actually turned leads like this into sales so I don't want to remove the possibility of receiving forms without comments. I hope that makes sense.Solubilize
Makes sense, but that really opens the door for spam bots. In that case you may want to consider adding a "just send me information" checkbox and disabling the submit button by default (so that the bots can't submit the form), then enabling it using javascript when the comment has been entered or the user checks that checkbox.Hoyt
Not sure that disabling the submit button will work with spambots that use automated techniques (like CURL). Their techniques will be able to 'see' a hidden field - even a hidden submit button, and submit the form.Steak
W
0

Honeypot method is simply better. But still we can do best as a developers. Particularly filtering SPAM words..

  • We did SPAM words filtering as porn, sex, http/www, money etc in our clients sites & it works well.

  • You may try those links here lively and check..

    We used the below script for EMAIL & MESSAGE fields. It will block mail spam of from MAIL.RU domains too.

if (!preg_match("/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,5})$/i", $email));if (preg_match('/mail.ru/', $email)){$errors .= "\n Error: Invalid Email Address";}

if (preg_match("/^abc|jpg|png|dating|funding|inbound|www|viagra|porn|sexy|honey|game|и|д|й|л|à/i", $message)){$errors .= "\n Error: Spammy message";}
Willhite answered 9/11, 2021 at 9:3 Comment(0)
A
0

I managed to solve my problem of spam coming through a web email form by generating, on the html form page using js, a random number between 0-9, and then created a corresponding image for each number in text form (eg, "one", "two", "three"...). The text of the number is light coloured (more difficult to OCR scan) and a bit obfuscated by other artifacts I put in the image. The user must convert these text numbers (I have 3 but more could be easily added) to real numbers and submit them on the form. The PHP on the server detects whether that submitted number corresponds to the random generated number and if does not it rejects the submission.

However, there were a couple spams that were still getting through. To find out how, I decided to include in the email that was sent to me the number that the user typed in when submitting the form to see if OCR had been used on the webpage, or if the form was actually filled out manually. To my surprise, no number appeared which is impossible if the form were submitted from the html form page. This meant that the spam email was being generated by the php itself, bypassing the html form.

There is a lot of information on the Internet about how easily external data can be injected into the php, specifically the variables in the php mail() function. To counter that, I limited the file access permission on the server to 600 for this php file, and also placed this file in its own directory with another tight file access permission of 710. A bot would have great difficulty reading this file with these permission constraints. This seems to have solved the problem so far.

I should also mention that I hid any email address in the html and php files. For spammers, getting valid emails is profitable. For the former I created an image of the email address and displayed it on the html page as an image. Sure, OCR scanning of the web page could decode it, but so far not. For the latter, I divided up into pieces any email address in the file, and re-assembled them at the point where mail() needs it. That way, text scanning these files on the server, if that should still happen, will not directly reveal any email addresses.

Abuzz answered 7/3, 2022 at 21:53 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Addict
O
0

6 years have passed since this question was asked. The best solutions at the time were to implement a honeypot and ReCaptcha.

Today, because of the advent of LLMs, there is a much better way to handle contact form spam: Use one of the APIs like BotButcher or Akismet to send the message to have it classified. Only pass on messages that have been classified as not spam. These services are very cheap and very effective. The additional benefit is you can now remove Recaptcha or any other "Are you a human?" challenges - which makes for a better user experience.

Orthohydrogen answered 24/7, 2023 at 15:26 Comment(0)
C
-2

I have another method which I have used successfully on several web sites for over ten years, without so much as a single successful spam robot attack. I know from the server logs that the forms are spammed hundreds of times per day, but none of it has gotten through to me. This method doesn't need Captcha or any other purchased software. I will try not to give all the details here, but I'll say enough that most people can implement it.

First, you will need a simple text-based graphic which shows an anti-spam "code' and, critically, the prompt for the form field with which it is used.

Second, you will need an email script which will accept aliases (e.g. FormMail and many others). Create on your form a required field with the name of "recipient" (or whatever field name to which your email script expects to send the form input). Display your "code" graphic, with the embedded prompt next to it. Make sure that you set up your email script to accept whatever code you have used in the graphic as the alias for the real email recipient (you).

When this is implemented, a human can easily read and enter the code you have chosen in your graphic (i.e. the email alias set up in the email script). A spam robot sees only a blank field.

If the robot chooses to fill that field randomly, it will generate an error in the email script saying, in effect, that the recipient doesn't exist. You never see that error, nor do you ever get any spam. I have tried most of the other approaches described in other posts here, but none has been 100% effective as this one has been.

Of course, a human being can defeat this, but if he does you simply change the graphic and the email alias in your script setup. Then, he must start over with a manual submission.

Cerate answered 27/10, 2019 at 18:57 Comment(1)
Can you re-write this to provide more step-by-step processes and break out the wall of text? This is really difficult to parse.Prince

© 2022 - 2024 — McMap. All rights reserved.