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" & Encrypt( RandRange( -30, 30 ), "SECRET_KEY_GOES_HERE", "CFMX_COMPAT", "HEX" ) ) />
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:
<form> <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 ;"> <p> NOTE: Do Not Alter These Fields: </p> <label> Contact Email: <input type="text" name="contact_email" value="<cfif StructKeyExists( FORM, "contact_email" )>#FORM.contact_email#</cfif>" /> </label> <label> Contact Email: <input type="text" name="contact_email2" value=" #REQUEST.TestKey# " /> </label> <label> Contact Url: <input type="text" name="contact_url" value="<cfif StructKeyExists( FORM, "contact_url" )>#FORM.contact_url#</cfif>" /> </label> <label> Subscribe to Blog: <input type="checkbox" name="contact_subscribe" value="1" <cfif StructKeyExists( FORM, "contact_subscribe" )>checked="true"</cfif> /> </label> <label> Remember my Info: <input type="checkbox" name="contact_remember_info" value="1" <cfif StructKeyExists( FORM, "contact_remember_info" )>checked="true"</cfif> /> </label> </div> </form>
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" ) /> </cfif>
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.
Want to use code from this post? Check out the license.