My clients and I have been receiving increasing amounts of spam sent through our own contact forms. Not being a spammer myself, I’m left to speculate on how one sends spam through a webmail form, but I’ve come up with two ways of preventing it from happening. Both of these methods involve editing the contact form’s HTML and adding a JavaScript file. They also require that legitimate users of the contact form have DOM-compliant browsers with JavaScript enabled.
Defeating human-like robots
For a very long time, I suspected that the spammers’ bots were filling out and submitting forms just like regular human visitors. They would look for input fields with labels like ‘name’ and ’email’, and, of course, for textarea elements. The bots would enter values into the fields and hit the submit button and move on to the next form.
To combat this, one could institute a challenge-response test in the form of a question that must be correctly answered before the form is submitted. Eric Meyer wrote a very inspiring piece at WP-Gatekeeper on the use of easily human-comprehensible challenge questions like “What is Eric’s first name?” as a way to defeat spambots. There are a number of accessibility concerns and limitations with this method, mostly with respect to choosing a challenge question that any human being (of any mental or physical capacity, speaking any language, etc.) could answer, but that a robot would be unable to recognize as a challenge question or be unable to correctly answer. However, these issues also exist with the CAPTCHA method.
In this case, the challenge question will be What color is an orange? If answered correctly, the form is submitted. If answered incorrectly, the user is prompted to try again.
Here’s how to implement a challenge question method of form validation:
First, create a JavaScript file named ‘validate.js’ with the following lines:
function validateForm() { valid = true; if ( document.getElementById('verify').value != "orange" ) { alert ( "You must answer the 'orange' question to submit this form." ); document.getElementById('verify').value = ""; document.getElementById('verify').focus(); valid = false; } return valid; }
This script gets the value of the input field with an ID of ‘verify’ and if the value is not the word ‘orange’, the script returns ‘false’ and doesn’t allow the form to post. Instead, it pops up a helpful alert, erases the contents of the ‘verify’ field, and sets the cursor at the beginning of the field.
Add the JavaScript to your HTML with something like:
<head> ... <script src="validate.js" type="text/javascript"></script> ... </head>
Next, modify the form to call the function with an onSubmit
event. This event will be triggered when the form’s Submit button is activated. Add an input
field with the ID ‘verify’ and an onChange
event to convert the value to lowercase. Add the actual challenge question as a label.
<form id="contactform" action="../webmail.php" method="post" onsubmit="return validateForm();"> ... <div><input type="text" name="verify" id="verify" value="" size="22" tabindex="1" onchange="javascript:this.value=this.value.toLowerCase();" /></div> <label for="verify">What color is an orange?</label> ... </form>
A visitor to the site who fills out the form but does not correctly answer the challenge question will not be able to submit the form.
Defeating non-human-like robots
I believe that the challenge-response method is becoming less effective, however. According the article ‘Spamming You Through Your Own Forms‘ by Will Bontrager, the spammers’ bots are not using the form as it is intended.
This is what appears to be happening: Spammers’ robots are crawling the web looking for forms. When the robot finds a form:
- It makes a note of the form field names and types.
- It makes a note of the form action= URL, converting it into an absolute URL if needed.
- It then sends the information home where a database is updated.
Dedicated software uses the database information to insert the spammer’s spew into your form and automatically submit it to you.
His response is to stop the process at step 2 by eliminating the bots’ access to the webmail script. He suggests doing this by hiding the URL of the webmail script in an external JavaScript file, then using JavaScript to delay the writing of the form’s action
attribute for a moment. The robots parsing just the page’s HTML never locate the URL to the webmail script, so it is never available for the spammers to exploit.
While I like the idea, I think I’ve come up with a better way of implementing it.
First, rename the webmail script, because the spammers already know the name and location of that script. For example, if GoDaddy is your host, contact forms on your site may be handled by ‘gdform.php’, located in the server root. You’ll need to rename that to something else. For purposes of illustration, I’ll rename the script ‘safemail.php’, but a string of random hexadecimal characters would be even better.
Next, give your contact form an ID. If you are running WordPress or other blogging software, be sure to give the contact form a different ID than the comment form, or else the JavaScript will cause the comment form to post to the webmail script. I’ll give my contact form the ID ‘contactform’.
<form id="contactform" action="../gdform.php" method="post">
We want to prevent the spammers from learning about the newly renamed script. This is done by giving the URL to a fake webmail script as the form’s action attribute and using JavaScript to change the action
attribute of the form to the real webmail script only after some user interaction has occurred. I’ll use ‘no-javascript.php’ as my fake script.
To accommodate visitors who aren’t using JavaScript, the fake script could instead be a page explaining that JavaScript is required to submit the contact form and offering an alternate way to contact the author.
Edit the contact form’s action attribute to point to the fake script.
<form id="contactform" action="no-javascript.php" method="post">
Create a new, external JavaScript file called ‘protect.js’, with the following lines:
function formProtect() { document.getElementById("contactform").setAttribute("action","safemail.php"); }
The function formProtect, when called, finds the HTML element with ID ‘contactform’ and changes its ‘action’ attribute to ‘safemail.php’. Obviously, one could make this script more complex and potentially more difficult for spammers to parse through the use of variables, but I don’t see that as necessary at this point.
Add the JavaScript to your HTML with something like:
<head> ... <script src="formprotect.js" type="text/javascript"></script> ... </head>
Finally, call the script at some point during the process of filling out the form. Exactly how you want to do this is up to you, and it’ll be effective longer if you don’t share how you do it. Perhaps the most straight-forward way would be to call the script at the point of submission by adding onsubmit="return formProtect();"
to the <form>
element.
<form id="contactform" action="no-javascript.php" method="post" onsubmit="return formProtect();">
If you want to use both the challenge question and the action rewriting functions, you may want to combine them into a single file or trigger formProtect separately with an event on one of the required input fields. If you decide to trigger formProtect with an event other than onsubmit, consider usability/accessibility issues—not everyone uses a mouse.
In conclusion
By implementing both of these methods, it is possible to dramatically reduce or even completely stop contact form spam. In the two months since I implemented this system, I haven’t received a single spam email from any of my contact forms.
The challenge-response test should deter or at least hinder human spammers and robots that fill out forms as though they were human. The trade-off is some added work for legitimate users of the form.
The action attribute rewriting method should immediately eliminate all spam sent directly to your form by spammers who have the URL of your webmail script in their databases. It should also prevent the rediscovery of the URL. Visitors with JavaScript enabled won’t be aware of the anti-spam measures.
For WordPress users
Defeating WordPress comment spam explains how to apply the attribute rewriting method to your WordPress site.
Nice informative article. thanks
I pointed form’s action attribute to – …/cgi-bin/formmail.pl
– which I do not have installed on my server. Thought I’d give the bots something
to go away with. Don’t know if that is good or bad.
“To satisfy accessibility concerns for visitors who aren’t using javascript, the fake script could be a page explaining that javascript is required to submit the contact form.”
…that’s going to help those people a lot, e.g. if they are blind.
Well, ok, point taken. How about:
“To accommodate visitors who aren’t using JavaScript, the fake script could instead be a page explaining that JavaScript is required to submit the contact form and offering an alternate way to contact the author.”
Good work.
I’ve uploaded the “validate” script OK, but I’m still getting spam using the form fields.
Your “bad form / good form” script looks ideal. However, I’m have trouble with the code when I try to make both scropts work in the the same form action.
Any chnace you could post a simple form or text file with the double protection included.
Thanks anyway for the validate script.
Best regards
Jack
What’s to stop a bot reading the correct form action in the JavaScript file? I can easily see how such a system would initially block bots due to security through obscurity but what happens when your system becomes well known?
That’s when you build a new system, of course. Nothing lasts forever… especially not when people are trying to hack it all of the time.
Thanks for the tips, they were very helpful. I use an SaaS to capture sales leads submitted on a web page form; the data was going directly to the SaaS vendor’s database. The spammers copied the action URL and sent the string to our db with the required fields. As you suggested, I implemented a similarly named “formprotect.js” file, the fake php, and a note that appears if scripting is turned off on my site and reduced spam by about 95%. 🙂 We don’t want to use the challenge/response technique as some people just won’t submit the form if there is too much for them to input, so I am still getting some odd submissions from the web (still some kind of bypass of the required fields), but will research those further.
I just don’t understand what these bizarre people hope to accomplish by spamming. They must spend a lot of time doing it.
Thanks again for sharing with us your creativity. 🙂
Pingback: Contact Form Spam — Sumy Designs Web Design Blog
Here’s another possible addition to this equation: add a text field that is hidden via CSS. Name it “Subject” or something similarly-important-looking. Reject a submission if the field IS filled out; a human should never see it due to the CSS rule.
Quite right.
And I explain how to create a ‘trap’ field for spam-bots in a similar post:
http://www.ardamis.com/2007/09/12/defeating-wordpress-comment-spam/
I am having trouble combining these two functions to work together.
Can someone please show me the JavaScript code that I should use if I want to combine both methods into one JavaScript file, and call them with one single function?
@ ardamis
First up, this is a great strategy to help reduce spam DRAMATICALLY from contact form spam attacks! About three weeks ago, I had about 100 spam e-mails (all received at exactly 1PM) – and decided I had enough!
I got it to work initially on my website with no problems – just as it is outlined above – for over a week and a half now. I even used a “human respondent” question (1+1 = ?) to help further filter out the spam – and everything was working flawlessly.
However, this evening I went back in to remove my challenge question (as I kind of felt like my wedding clients might find it strange), and decided to replace it with a hidden spry form field box (which would be hidden with CSS) that would automatically prevent the form from being submitted if it WAS filled out…as no human should really be able to see it.
However, something got messed up… Now… I can’t get the spry validation AND the redirect to the proper PHP submission file to work at the same time.
I either get all my spry form fields to validate – but when I click on submit the contact page redirects me to the “No Java Script” decoy php file… OR I can get the form to submit properly to the real form mail php file…BUT the spry text fields are not validated first to assure that required form fields are filled out.
As soon as I add the ‘onsubmit=”return formProtect();” piece into the following code…
The spry validation no longer works…but the form does submit correctly (even if only one letter is typed into a field – and not all the required fields are filled out)
I can’t for the life of me figure out why it won’t all work now!!! Anyone have any suggestions? I would sure appreciate it!