After some good discussions on CF-Talk about blocking spam bots from submitting web forms, I thought I would give it another go. Jacob Munson pointed out some issues with my previous solutions (v.1 and v.2) and Bobby Hartsfield made some great suggestions that I thought I would try out.
This solutions takes advantage of the fact that Spam Bots are just not that smart when it comes to form submissions (at least not yet). They tend to take a form and just submit all the fields. The smartest move they make is trying to figure out which form fields take which data (ie. urls, email address, names, etc.). But, even that, we can use against them.
The goal of this solution is to get the Spam Bot to submit data that a user would never submit. We then use this data to invalidate the form submission. To start with, I come up with a random key:
- <!--- Get the de-spamming key. --->
- <cfset REQUEST.TestKey = (
- "KS" &
- RandRange( -30, 30 ),
- ) />
This just creates a randomly generated HEX key that is prepended with "KS" (for "Kinky Solutions"... and I didn't want it to start with a number - but just personal). Then, in the comment form I have:
- <input type="hidden" name="test_key" value="#REQUEST.TestKey#" />
- <input type="hidden" name="test_email" value=" #REQUEST.TestKey# " />
- REST OF THE FORM GOES [[ HERE ]].
- THIS IS THE FORM THAT THE USER ACTUALLY SEES.
- <!--- THIS IS AFTER THE VISIBLE FORM FIELDS. --->
- <div style="display: none ; visibility: hidden ;">
- NOTE: Do Not Alter These Fields:
- Contact Email:
- value="<cfif StructKeyExists( FORM, "contact_email" )>#FORM.contact_email#</cfif>"
- Contact Email:
- value=" #REQUEST.TestKey# "
- Contact Url:
- value="<cfif StructKeyExists( FORM, "contact_url" )>#FORM.contact_url#</cfif>"
- Subscribe to Blog:
- <cfif StructKeyExists( FORM, "contact_subscribe" )>checked="true"</cfif>
- Remember my Info:
- <cfif StructKeyExists( FORM, "contact_remember_info" )>checked="true"</cfif>
The form starts out by submitting the test key value (the randomly generated value). Then there is another hidden field titled "test_email". This is supposed to be the same value as the test key. I have padded the value with two spaces (on either side). When my forms get submitted, all fields automatically get Trim()'ed. This means that on the server, these two values will be the same, but in the HTML these two values will be different. I am just trying to do anything that might be confusing to the Spam Bot. Additionally, this field has "email" in the name which hopefully should entice the Spam Bot to alter its value.
Then, at the end of the form, I have a hidden DIV that has a number of fields that should remain untouched. For most users, the style attribute on the DIV will stop these fields from being displayed. For users who can't handle style, I have added the note "NOTE: Do Not Alter These Fields:". This should also prevent blind people from messing up.
I have ColdFusion CFIF statements for the FORM values in the hidden area because I have not CFParam'ed these values so they shouldn't exist in the FORM scope until the form has been submitted. I am taking the trouble to display any submitted value just in case the Spam Bot is actually looking at return value of the form, I want to persist the submitted value to make it seem like these fields are actually valid fields. If they kept showing up with the same default value after each form submission, a Spam Bot might be able to deduce that they were not being used????
I also tried to make the fields and field labels seems as valid as possible. I'm doing everything I can to trick the Spam Bot into changing the value of the form fields.
Ok, then on the server, I have some simple logic to check for a valid form submission. This is in my form data validation code (I have added spacing for better readability):
- <!--- The following is all for de-spamming. --->
- <cfif (
- Compare( FORM.test_key, FORM.test_email ) OR
- (NOT StructKeyExists( FORM, "contact_email" )) OR
- Len( FORM.contact_email ) OR
- (NOT StructKeyExists( FORM, "contact_email2" )) OR
- Compare( FORM.contact_email2, FORM.test_email ) OR
- (NOT StructKeyExists( FORM, "contact_url" )) OR
- Len( FORM.contact_url ) OR
- StructKeyExists( FORM, "contact_subscribe" ) OR
- StructKeyExists( FORM, "contact_remember_info" )
- <!--- Set error message. --->
- <cfset REQUEST.FormErrors.Add(
- "There was a problem submitting the form"
- ) />
Then, of course, I don't allow the form to be processed if there are any error messages in my REQUEST.FormErrors collection. As you can see from this CFIF statement, none of the hidden form fields can be altered. Also, none of the hidden form checkboxes can be submitted.
This solutions has the following benefits:
- It does NOT require CSS to be supported.
- It does NOT require any graphics (ie. CAPTCHA or other).
- It does NOT block blind people from being able to participate.
- It does NOT require any specific actions (ie. clicking on the submit button).
- It does NOT require the user to do any additionally thinking!!!!
I think I implemented this about three days ago and I am happy to say that I did not get a single Spam Bot submission. Well, that's not true... I did get ONE, but it wasn't through the comments form. Some spam bot must have cached my old form and tried to submit it. Oddly enough this would work fine until I added the (NOT StructKeyExists( FORM, "XXXXX" )) conditions. This made sure that no old version of the form would pass the test (as old forms would not have the required fields). But as far as THIS form, no spam has gotten through.
So, so far, it seems to be working out quite nicely! Thanks to everyone on CF-Talk for their suggestions.
Looking For A New Job?
- Software Development Engineer - REQ20003869 at Express Scripts
- Mobile Application Developer at Xorbia Tickets
- 7 Year + Lead ColdFusion Developer at Atprime Media Services
- Full Time ColdFusion Developer Needed at InterCoastal Net Designs
- Developer-focused job platform at Honeypot
This is an excellent solution to an ugly problem. I hate graphic CAPTCHA on web forms. Thanks!
Don't thank me, there were a lot of people involved in this discussion... I am just posting the code that I used to implement it :)
Congrats! After our discussion on cf-talk, I've decided to modify CFFormProtect too. I don't want to spill the beans yet, but my main goal is the same as yours, to let the user just fill out the form while stopping the evil spammers. I'm really glad we had that discussion, it really opened my eyes to a lot of issues.
Yeah, it was a good conversation. Hopefully this comparatively simple solution should work fairly well.
I like this idea a lot. As in that CF-talk discussion, I like the idea of a single field that says "Leave Blank" next to it rather than a whole section to be ignored though. If you know how blind users navigate (by headings and links and form elements often), that text could very well be missed. But if it's the form field's label it won't likely.
The best extension of the idea is that it can serve a dual-purpose: spam protection AND honeypot. You can easily add that user's IP to a blacklist (practically futile) and the comment to a quarantined area to be examined for content or patterns.
Lastly bots are often talked about like they are learning things. I think the way many "bots" get around countermeasures is because they are in fact user-configured scripts. That's why plain security through obscurity doesn't work. A spammer can check out your source and tell the script/bot what to plug in and then let it go to town on your site. So this could surely be defeated, but I think it's a great start.
If you banned all comments from a known list of anonymous proxies, then banning their IP would be even more effective.
I think the best solution would be to randomly select the form field names before the form itself was outputted. That way, on one load it might be "blogauthor" and on the next load it might be "author" and on the next load it might be "boogiemonster"... then I suppose you could send an encypted list (hidden form field) of the form fields that are valid.
But, I am not going to get that complicated until the spamming becomes an issue.
Good ideas. I am with you though - I'd rather start simple and expand it if needed. It's actually a good learning experience to see if the spammers do defeat the easier to implement countermeasures. But it's nice to have a plan B for when that does happen too. Thanks for your thoughts on this matter.
Has anyone else implemented this and seen great success? We just converted a contact form that was heavily spammed to a Flash Form and that seems to have helped. I would be interested in trying this approach as well.
Are you're still bot-free after a month now? Good article!
I actually had some stuff making it through! I ended up blocking all comments that use the Link (A) tag in the comments. That with the combo of the other stuff have stopped all spamming so far.
I am implementing this solution on our website, www.3dscanco.com on our main contact form as we've been getting a bunch of spam. One comment:
Shouldn't the last two "StructKeyExists" (for contact_subscribe and contact_remember_info) have a NOT in front of them?
Also, how is it working after 1+ years? Any holes yet?
Actually, I just realized why it is like this. The checkbox being 0 is a good thing!
So far, it has been pretty decent. I get spam sometimes, but I think that when it comes now, it is a person. Every now and then, I'll get like a string of 30 spams, but it's spread out over like 15 minutes, so I figure it's someone manually copy-n-pasting into the comments fields.
I understand what you are doing to keep spammers at bay. But not sure I understand the java class thing. You say ,"If an error exists" dont do any form processing, like we should know what to do tfrom there. I need help with the code. for stopping form processing.
I have one question: the second part of the code does it go into a separate file or at the end of the above file? Thanks for the help.
I was wondering if you had a sample of the code in an actual contact form? I'm not too savvy on copying and pasting code when I'm not sure if it's supposed to go in the head tags or body and I'd like to test out your method to see if it works.
I'm currently having the problem where a spammer has cached my old form and even though I implemented new code and changed up the form, I'm still getting spam emails from the old one :(
I'd appreciate your help greatly!