Quick and Dirty ColdFusion 8 CAPTCHA Guide
Many moons ago I wrote a blog entry on doing CAPTCHA's in ColdFusion (Quick and dirty CAPTCHA Guide). This guide discussed how to add CAPTCHA images to your forms using third party tools in ColdFusion 7. One of the new features in ColdFusion 8 is built-in CAPTCHA support, so I thought it would be nice to upgrade the guide.
As in the previous version, we will begin with a simple form. This is a contact form with fields for name, email, and comments. Validation is built in for all three fields. The complete, initial form is below:
<cfparam name="form.name" default="">
<cfparam name="form.email" default="">
<cfparam name="form.comments" default="">
<cfset showForm = true>
<cfif structKeyExists(form, "sendcomments")>
<cfset error = "">
<cfif not len(trim(form.name))>
<cfset error = error & "You must include your name, bozo.<br>">
</cfif>
<cfif not len(trim(form.email)) or not isValid("email", form.email)>
<cfset error = error & "Include a valid email address idiot!<br>">
</cfif>
<cfif not len(trim(form.comments))>
<cfset error = error & "It's called a Comment Form, stupid.<br>">
</cfif>
<cfif error is "">
<cfmail to="foo@foo.com" from="#form.email#" subject="Pointless comments from the public" wraptext="75">
From: #form.name# (#form.email#)
Comments:
#form.comments#
</cfmail>
<cfset showForm = false>
</cfif>
</cfif>
<cfif showForm>
<cfif structKeyExists(variables, "error")>
<cfoutput>
<p>
<b>Please correct these errors:<br>
#error#
</b>
</p>
</cfoutput>
</cfif>
<cfoutput>
<form action="#cgi.script_name#" method="post">
<table>
<tr>
<td>Your Name:</td>
<td><input type="text" name="name" value="#form.name#"></td>
</tr>
<tr>
<td>Your Email:</td>
<td><input type="text" name="email" value="#form.email#"></td>
</tr>
<tr>
<td>Your Comments:</td>
<td><textarea name="comments">#form.comments#</textarea></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="sendcomments" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput>
<cfelse>
<cfoutput>
<p>
Thank you for sending your comments, #form.name#.
</p>
</cfoutput>
</cfif>
Hopefully nothing there was new to you. (But I bet there are still a few of you who have not yet used isValid!). Now that the basic form is done - it won't take long for spammers to begin abusing it. How can we add a CAPTCHA to the form?
ColdFusion 8 makes it incredibly easy with the CFIMAGE tag. One of the many actions it contains is a CAPTCHA action. As you can imagine, this generates a CAPTCHA image. Here is a basic example:
<cfimage action="captcha" width="300" height="75" text="Hello" fonts="verdana,arial">
And this generates:

You have many options to customize the CAPTCHA. Please see the ColdFusion documentation for a complete list, but in general, the attributes you care about are:
- difficulty: This can be low, medium, and high. Low is the default. I find medium to be too strong. You will have to decide for yourself though.
- width/height: I normally use what you see above, 300x75. What's important here though is to ensure your image is big enough to fit the CAPTCHA text. What's cool though is that if you don't use a size big enough for the text, you will get an error telling you exactly how big it does need to be. (Although oddly they tell you one less then the minimum size. So they may say you need width of 100, but what they really mean is 101.)
- fonts: Specifies the fonts for the CAPTCHA. You want to specify this since most servers have a 'ding bats' or other fruity font that will be impossible for folks to decipher.
- text: This is the text of the CAPTHA and will be our focus next.
So in the example above, I used a hard coded text of "hello". While this was easy, it won't take long for spammers to notice that the CAPTCHA text doesn't change. You could always pick a random word from a list. What I do instead, though is use a simple UDF. This UDF will pick random letters and numbers, but will specifically avoid things like "I" (capital 'eye'), "l" (lower case 'el'), and 1. You could modify this UDF to allow for the min and max text strings to be arguments. For me - I thought 4-6 characters was enough. (For an insane CAPTCHA, try the Blingo service. Blingo (that's an affiliate link by the way) is a search engine provider that enters you in a contest for every search you do. When you win (I've won a few times), the CAPTCHA is - I kid you not - something like 20-30 characters. It's the Klingon of CAPTCHAs.)
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,6)>
<cfset var result = "">
<cfset var i = "">
<cfset var char = "">
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript>
<cfreturn result>
</cffunction>
Nothing special going on here in the UDF. As you can see it just loops and randomly picks characters. So in order to use this, we could now do this:
<cfimage action="captcha" width="300" height="75" text="#makeRandomString()#" fonts="verdana,arial">
While this works, it leads to our final problem. How do we validate the CAPTCHA text? We could save the text and put it in a hidden form field:
<cfset captcha = makeRandomString()>
<input type="hidden" name="captchatext" vale="#captcha#">
<cfimage action="captcha" width="300" height="75" text="#captcha#" fonts="verdana,arial">
But don't forget that hidden form fields aren't really hidden. It won't take long for spammers to find this either. (Just imagine if these jerks actually spent their time helping the poor?)
So we have a few options here. For this guide (and what I've demonstrated before in presentations) I'm going to use a Hash() of the CAPTCHA text. I could store the value in the Session, but I wanted something that folks could use even if they had session management turned off.
Just take that CAPTCHA string and store a hash of it, like so:
<cfset captcha = makeRandomString()>
<cfset captchaHash = hash(captcha)>
<input type="hidden" name="captchaHash" value="#captchaHash#">
<cfimage action="captcha" width="300" height="75" text="#captcha#" fonts="verdana,arial">
We now have the CAPTHA stored in a hash. On form submission, all we have to do is compare the hash of what you wrote to the hash in the hidden field:
<cfif hash(form.captcha) neq form.captchaHash>
<cfset error = error & "You did not enter the right text. Are you a spammer?<br />">
</cfif>
All in all - not very hard at all. I'm a bit surprised Adobe didn't include a simple 'makeRandomText' function as you really need it for CAPTCHAs. They could also have included it as an attribute to cfimage. But since they added 50 functions and an uber-tag, I can't complain too much. (Ok, I can, but I won't. Today.) Let me know if this guide is helpful. I've included the complete document for our form below. Enjoy.
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,6)>
<cfset var result = "">
<cfset var i = "">
<cfset var char = "">
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript>
<cfreturn result>
</cffunction>
<cfparam name="form.name" default="">
<cfparam name="form.email" default="">
<cfparam name="form.comments" default="">
<cfset showForm = true>
<cfif structKeyExists(form, "sendcomments")>
<cfset error = "">
<cfif not len(trim(form.name))>
<cfset error = error & "You must include your name, bozo.<br>">
</cfif>
<cfif not len(trim(form.email)) or not isValid("email", form.email)>
<cfset error = error & "Include a valid email address idiot!<br>">
</cfif>
<cfif not len(trim(form.comments))>
<cfset error = error & "It's called a Comment Form, stupid.<br>">
</cfif>
<cfif hash(form.captcha) neq form.captchaHash>
<cfset error = error & "You did not enter the right text. Are you a spammer?<br />">
</cfif>
<cfif error is "">
<cfmail to="foo@foo.com" from="#form.email#" subject="Pointless comments from the public" wraptext="75">
From: #form.name# (#form.email#)
Comments:
#form.comments#
</cfmail>
<cfset showForm = false>
</cfif>
</cfif>
<cfif showForm>
<cfif structKeyExists(variables, "error")>
<cfoutput>
<p>
<b>Please correct these errors:<br>
#error#
</b>
</p>
</cfoutput>
</cfif>
<cfoutput>
<form action="#cgi.script_name#" method="post">
<table>
<tr>
<td>Your Name:</td>
<td><input type="text" name="name" value="#form.name#"></td>
</tr>
<tr>
<td>Your Email:</td>
<td><input type="text" name="email" value="#form.email#"></td>
</tr>
<tr>
<td>Your Comments:</td>
<td><textarea name="comments">#form.comments#</textarea></td>
</tr>
<tr>
<td>Enter Text Below:</td>
<td><input type="text" name="captcha"></td>
</tr>
<cfset captcha = makeRandomString()>
<cfset captchaHash = hash(captcha)>
<input type="hidden" name="captchaHash" value="#captchaHash#">
<tr>
<td colspan="2"><cfimage action="captcha" width="300" height="75" text="#captcha#" fonts="verdana,arial"></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="sendcomments" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput>
<cfelse>
<cfoutput>
<p>
Thank you for sending your comments, #form.name#.
</p>
</cfoutput>
</cfif>
Comments
So for your solution, I take the user's value, add the prefix, then hash it.
How do you handle edge cases - like me hitting your form at 11:59PM? For dates you could get around this by checking for both today and yesterday though.
if(today + userentry -> hashed) neq the hash
then try (yesterday + userentry -> hashed)
You could even try yesterday conditionally - ie, only in the first hour after midnight.
Maybe another option would be to use unique hashes. Ie, as soon as you use it, don't use it again. You could store this in the app scope. It wouldn't persist forever, but would stop immediate attacks.
Hash = CFusion_Encrypt("ID-YYYYMMDD-CaptchaWord", KEY)
HashDecoded = CFusion_Decrypt(Hash, KEY)
ID = ListFirst(HashDecoded,"-")
ValidDate = ListGetAt(HashDecoded,2,"-")
CaptchaWord = ListLast(HashDecoded,"-"))
... and then perform any needed validation. Of course, you'd have to substitute CFusion_Encrypt with something else to work in CFMX8 and BlueDragon. You'd also have to ensure that the values passed don't contain quotation marks and choose a delimiter that works for your application.
This should not be an attempt to obfuscate the code, just make it more difficult to be abused and expire-enabled so that it can't be remotely stored for later posting.
Also a persistent scope is required to store the temporary Lyla hash which means that any internal hiccup could result in losing the hash. An encrypted/decrypted method would never be impacted by a server restart, require additional clean-up cycles, or have to work around sticky sessions in a clustered environment.
After setting the random text variable, we set that variable into a session. Like Session.Captcha=randomText
Is this safe?
If not safe, why not do an Application variable (yes, going crazy).
Create a Structure in an Application variable where you set the IP of the user with the value of the random text.
I, for instance, keep a an Application structure of online users (for 20 minutes) defined by a UUID for each user. I can store it there also and it gets deleted after 20 minutes of idle time from the user or it gets replaced when the user requests another Captcha.
Safer? More insane?
What do you think? What is the safest and easy way to check the Captcha value?
Again, if there are any hickups in the server, both session and application variables will be cleared. If they expire within 20 minutes and the user is still composing their message, please ensure that their message is reposted so they don't have to hit their back button. (I usually compose my messages in a separate text editor and then copy-and-paste to the webform in case there are any posting problems and I need to repost.)
Adding the IP is not a bad idea, but some ISPs (like AOL) change the IP address of the user mid-session. I would only integrate IP restrictions to a targeted audience when I know for a fact that their IP addresses will not change (ie, enabling a remotely hosted application to work only for certain employees if they are accessing from the office IP class.)
But one question on your initial post Ray? I noticed you compared "<cfif hash(form.captcha) neq form.captchaHash>"... by putting a cfimage within a cfform tag does that automagically become part of the form struct? I'm not following where you set the form.captcha?
cheers
nevermind...
/banging head...
sorry man. hah!
What release of cf8 are you running? I tried the captcha with the word hello. It told me I needed the dimmensions 130 width, and then a height of 35. I entered these dimensions, and it worked. Just want to make sure what I am working on will work on other servers.
Either way - it's a DARN nice helpful message to have.
<cfimage
action="captcha"
height="60"
width="200"
text="#session.whatWasIt#"
difficulty="low"
fonts="verdana,arial,times new roman,courier"
fontsize="15" />
to my <cfform> and the image did not show. I am using CF 8.0.1 too. Can you believe that. I wrote to Ben Forta too about this.
Code:
<cfformgroup type="vertical" label="Step 4 - Human or Robot check ">
<cfinput type="text" name="captcha_test" value="" size="7" />
<cfimage
action="captcha"
height="60"
width="200"
text="#session.whatWasIt#"
difficulty="low"
fonts="verdana,arial,times new roman,courier"
fontsize="15" />
</cfformgroup> thanks
I receive these values:
Form: 8B8C20ABED3E28B960ABD3FC127A83BF
Hash: 5E47596628F59DF0AB12A8C2B9A83278
But I entered in the text in the image correctly?
I guess I'm missing something
If you want to add another level of security, you could use an encoded hash and additionally include their IP address into it. This wouldn't stop a spammer from posting to the page multiple times in a short period of time from the same IP, but it would stop the profiling and returning from multiple spambots through proxies and compromised servers.
I've also been using Project HoneyPot to identify compromised IP addresses and post a special message for anyone trying to post from known IPs. Think of it as a DNS blacklist for abusive visitors.
http://en.wikipedia.org/wiki/DNSBL
I just noticed that when "asa" posted, that the "from" and "return-path" email address on the subscribed message was listed as his. This means that his personal email address has been exposed and that mine is also sent to anyone that subscribes to this post. This also means that bounces will go to him.
There is a problem with this... I have Sender Policy Framework (SPF) configured for third-party mailservers to not accept any email that is not sent from my mail server. To deal with this, please modify your CFMAIL tag as follows:
from="submitter@email.com" failto="blog@coldfusionjedi.com"
... or you could use your email address in both places (from/failto) to protect the privacy of users who post on your forums.
For more on SPF, go to:
http://www.openspf.org/
http://en.wikipedia.org/wiki/Sender_Policy_Framewo...
Also, for bypassing anti-spam filters, read this:
http://www.list-unsubscribe.com/
and add the following:
<CFMAILPARAM Name="List-Unsubscribe" Value="<mailto:unsubscribe@email.com>">
<cfif hash(form.captcha) neq form.captchaHash>
<cfset error = error & "You did not enter the right text. Are you a spammer?<br />">
</cfif>
I have the first part of the section as an include file so I could reuse. When I tried to add it the <cfif hash> statement on the actually page, it breaks. What am I doing wrong. I have done your code on other website and it works great. I did it as an include files. Trying to get it working on the forum is harder.


Spammers will manually build a profile based on your form. They will store the captchaHash and enter in the correct value in the "captcha" field. Since captchaHash will always equal the hashed captcha value, they can store the 2 parameters and then post as many bogus emails, names and comments as they desire while using a fake http_referer and user_agent from multiple compromised IP addresses.
In order to fix this, add a changing unpublished, non-displayable prefix/suffix to the generated hash to ensure that it can't be saved and used forever:
<cfset captchaHash = hash("#DateFormat(Now(),'YYYYMMDD')##captcha#")>