A few months ago I posted a quick guide to walk folks through adding CAPTCHA's to forms:
This guide made use of the excellent Lyla CAPTCHA component. One of the new features of ColdFusion 8 is a built in CAPTCHA generator. So let's take a look at how we can do it the CF8 way...
First off, let's start with a simple contact us style form. I won't go into details about this form. It's a basic self-posting form with validation for a name and comment box.
<cfset showForm = true>
<cfparam name="form.name" default="">
<cfparam name="form.comments" default="">
<cfif isDefined("form.send")>
<cfset errors = "">
<cfif not len(trim(form.name))>
<cfset errors = errors & "You must include your name.<br />">
</cfif>
<cfif not len(trim(form.comments))>
<cfset errors = errors & "You must include your comments.<br />">
</cfif>
<cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif>
</cfif>
<cfif showForm>
<cfoutput>
<p>
Please fill the form below.
</p>
<cfif isDefined("errors")>
<p>
<b>Correct these errors:<br />#errors#</b>
</p>
</cfif>
<form action="#cgi.script_name#" method="post" >
<table>
<tr>
<td>Name:</td>
<td><input name="name" type="text" value="#form.name#"></td>
</tr>
<tr>
<td>Comments:</td>
<td><textarea name="comments">#form.comments#</textarea></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="send" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput>
<cfelse>
<cfoutput>
<p>
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
</p>
</cfoutput>
</cfif>
Hopefully nothing above is new to you. So lets start updating this with some CAPTCHA love. First off, creating a CAPTCHA in ColdFusion 8 is incredibly easy. It takes all of one tag:
<cfimage action="captcha" width="300" height="75" text="paris">
The width and height determine the size of the image. The text determines what text will be displayed on the CAPTCHA. You can also determine what fonts to use - as well as the difficulty level.
So that part is easy. Everything after that takes a little bit of work. The first thing you need to figure out is what text to use. In the example above I used a hard coded value, paris, but in the real world you wouldn't do that. If you do, spammers would get past your CAPTCHA rather quickly.
You can create a list of random words - but unless your list is pretty big, you will again have the issue of spammers being able to guess the word. Instead, I recommend a random set of letters. I've built a UDF just for this purpose. Let's take a look:
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,7)>
<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>
This UDF simply creates a random string from 4 to 7 characters long. You can tweak that size all you want, but any more than 7 will probably tick off your visitors. Also note the range of characters. I removed things like 1 (number one), l (lower case 'el'), and I (upper case "eye') since they can be confusing. Thanks to the NYCFUG members for feedback on this.
So once we have the UDF, we can now generate random text. But now we have another problem. When we submit the form, we are going to need to validate that the text you entered is the same as the text in the image. To do that, we need to store the text. Imagine if we did this:
<cfset captcha = makeRandomString()>
<input type="hidden" name="captchatext" value="#captcha#">
As you can imagine, this is not very secure. A spammer would simply look for the hidden form field. So we need to encrypt the string somehow. ColdFusion offers multiple ways of doing this. For example though I'll just hash it:
<cfset captcha = makeRandomString()>
<cfset captchaHash = hash(captcha)>
Then I can add the CAPTCHA to my form like so:
<tr>
<td>Enter Text Below:</td>
<td><input type="text" name="captcha"></td>
</tr>
<tr>
<td colspan="2">
<cfimage action="captcha" width="300" height="75" text="#captcha#">
<input type="hidden" name="captchaHash" value="#captchaHash#">
</td>
</tr>
Now the form has both the captcha and the text in hashed form. The last step is to just add the new validation. I do this by hashing the user's text against the hidden form field:
<cfif hash(ucase(form.captcha)) neq form.captchaHash>
<cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
</cfif>
And that's it. I'm done. The complete template is below. Enjoy.
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,7)>
<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>
<cfset showForm = true>
<cfparam name="form.name" default="">
<cfparam name="form.comments" default="">
<cfparam name="form.captcha" default="">
<cfparam name="form.captchaHash" default="">
<cfif isDefined("form.send")>
<cfset errors = "">
<cfif not len(trim(form.name))>
<cfset errors = errors & "You must include your name.<br />">
</cfif>
<cfif not len(trim(form.comments))>
<cfset errors = errors & "You must include your comments.<br />">
</cfif>
<cfif hash(ucase(form.captcha)) neq form.captchaHash>
<cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
</cfif>
<cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif>
</cfif>
<cfif showForm>
<cfset captcha = makeRandomString()>
<cfset captchaHash = hash(captcha)>
<cfoutput>
<p>
Please fill the form below.
</p>
<cfif isDefined("errors")>
<p>
<b>Correct these errors:<br />#errors#</b>
</p>
</cfif>
<form action="#cgi.script_name#" method="post" >
<table>
<tr>
<td>Name:</td>
<td><input name="name" type="text" value="#form.name#"></td>
</tr>
<tr>
<td>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>
<tr>
<td colspan="2">
<cfimage action="captcha" width="300" height="75" text="#captcha#">
<input type="hidden" name="captchaHash" value="#captchaHash#">
</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="send" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput>
<cfelse>
<cfoutput>
<p>
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
</p>
</cfoutput>
</cfif>
Archived Comments
Hey Ray, we are trying to use a CAPTCHA in our application but for various reason we are writing the file to a location before using it on screen. Most CF tags that give an option to display something or write to a destination, do one OR the other. It seems with cfimage and CAPTCHA, even if you specify a destination attribute it ALSO tries to write it to the browser. For example, if I put destination="c:\image.jpg", it writes the file to the c drive then renders <img src="c:\image.jpg"> in the final HTML. Not at all what I would expect from the documentation or past experience with other CF tags. Would you consider that normal behavior or a bug?
Confirmed. You can fix this by wrapping the tag in cfsavecontent. That will suppress it. I'm filing a bug report now.
Thanks, Ray - for both the fix and filing the bug. You're a life saver!
Very helpful.
May I ask why you use the ucase function when validating in this line:
<cfif hash(ucase(form.captcha)) neq form.captchaHash>
Thanks.
I've had issues with case and hashes before, so this just removes it.
Occasionally the captcha image has an icon character in it, like a skull and crossbones or the international "rewind" symbol (like <<). Is there a way to restrict it to just numbers and letters? I don't expect my site users to know the hotkeys for dingbat symbols, etc. :-)
I put an example up at http://www.centralscene.com... (keyboard icon).
Thanks for the great example, and the help!
@Nate:
You can control the text since - well - you have to. But what you are seeing is the font being one of the symbol fonts. CF's captcha support lets you specify fonts. I didn't do that in my example as I wanted the code to work on both Macs and Windows machines. In a real production environment you would want to specify a few fonts that you know folks can read.
Working on adding this to my website, but I get an error on the random character generator script.
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript>
Eric - are you sure you are using CF8? What error do you get?
Hi I tried this and it works great, the problem what I have is I have CF 7 and want to make it work but of course it is throwing an error about the cfscript, is there a way or something to make it workunder CF 7?
Change the <= to lte.
Thanks a lot Raymond,
Your solution is very user friend and cool,
Good luck in your life,
Ray,
Excellent post. I used a lot of your code as the basis for my captcha functionality.
One comment, though. What about the problem of a user who submits 1 for your form.captcha field, and then the hash of 1, (which is c4ca4238a0b923820dcc509a6f75849b) for the form.captchaHash field? Way to easy to break that IMO.
In order to fix this, one thing we've done is concatenated a secret suffix to the captcha text before hashing it. For example:
Let the secretSuffix = "iHateRobots".
Let the randomly generated string = "ABCDE";
We would now hash ("ABCDEiHateRobots") and store that in the form.captchaHash field. When checking for validity simply:
Let the user input = "ABCDE";
Hash the userInput & secretSuffix, check against your form.captchaHash field, and this will protect you against that basic CAPTCHA bypass.
With all of that having been said, this post is excellent and superbly captures the basics of creating a captcha. Nice job, and thanks again for the post!
- Mike
Mike, nope, that is precisely the biggest issue with my code here (and I think someone may have raised it already). What you are doing is referred to as adding a salt (I believe) and would be a smart idea. You could store that salt in the session which is another way to help block a robot as they will not (normally) have a session. You would want the salt to be random as well. Ie, onSessionStart, session.salt = random.
Even better! Random session salt versus one globally concatenated suffix would work perfectly for us. So simple, yet so very powerful. Thanks again, and keep up the great posts!
captcha doesn't display in Palm Blazer v4.5 (Treo 700p). Anybody have any experience with this issue?
I found out the problem with Captcha and Blazer. CFimage generates a PNG file that has 'layers'. If I open the generated captcha image in photoshop and the 'flaten' the image and get rid of the layer and then re-save, then I can view the image in Blazer.
Anyone know how to make CF generate a captcha png without layers? Or some workaround to this?
Thanks.
I found a workaround...
I save the captcha to a destination (use cfsavecontent as mentioned above), then I convert the image format to jpg, then I display the image with IMG tag.
Converting to JPG removes the layers.
This was an excellent tutorial! I did have one problem with implementation. The captcha cfimage displays fine for about 5 minutes after restarting the app server, then intermittantly will not display unless the page is refreshed a bunch of times. Here's my example:
http://v3.lightspeedvt.net/...
If you refresh the page a few time, the captcha image will by displayed and then broken seemingly at random.
I wonder if it is a bad font? If you did not specify the fonts attribute, the font is going to be random. So maybe specify a font that works, and see if it works well 100% of the time.
Seems like 'levensok' gave up, example url does not have captcha on it any more.
In addition to the server side checks on the captcha you can use javascript on the client side to check it before allowing submission of the form. There are two hashing libraries for JS available, at least. I'm using SHA-256 in CF and the JS library that offers the same.
This has the advantage of alerting the user before submission that they've got it wrong, which is less stressful than waiting for a response to find out and having the characters change all over again.
It's not going to do much in the way of preventing brute force attacks etc as they'll be posting direct, but it does mitigate some of the negative effects on the user.
I guess this is not a good method to block flooders. Indeed, it could block a spammer bot if the process doesn't start by a human. For example, if a man read a captcha, he (actually his robot) will be able to post as many things as he want (including in other resources that requires the same captcha), since the code has the Hash in a input hidden, so he could use the same hash (which he knows the captcha) for every post.
A good solution would be save the hash in Session scope, but it has its limitations. It impedes the user to access two pages that requires captcha simultaneously, once the first captcha would be "nullified" by the second.
In this case, I would suggest a mix between both solutions, creating an associative array (map, or a structure in the case of CF) of captchas generated for that user and store it in Session scope with the hash and the value of captchas. Also, put the hash in a hidden field as in this example and verify that hash in the "table" of captchas when user submits. If captcha matches the hash, delete its array register.
Has anybody thought about taking this a step further with regards to accessibility?
Standard captcha will cause problems for the blind and partially sighted. There is some good information about this on the W3C website;
http://www.w3.org/TR/2005/N...
Although these users make up a very small percentage of our visits, some websites need to incorporate alternatives to meet guidelines and to ensure nobody is excluded.
I have not seen an example yet of anybody using text to speech with coldfusion.
Your thoughts and ideas?
There is a Java project, FreeTTS, that could work. I'm looking at this today.
Why dont we use encrypt and decrypt ?
I am randomly getting an error when the page first loads.
"Cannot find the config file. configFile=..."
If you refresh the page, then it's fine, but its the initial load of the page.
Other than that, great script!
Um, what configFile? My demo here doesn't make use of it. Are you using my code here?
Oh wow! Thanks. That made me go back and compare code. It's a leftover from the previous captcha script I was using before I found yours.
Thanks for straightening me out.
Ray,
Did you have any success with the FreeTTS?
Yep: http://www.coldfusionjedi.c...
Hey Ray,
Thanks for the post. Works great!
I find the captcha to always be legible but, is there a simple solution to provide a refresh link that doesn't clear the filled form fields?
Hi Jeff,
I use a JavaScript method;
<a href="javascript:location.reload(false)">Refresh</a>
You can see this in action here;
http://www.stratford.gov.uk...
Hope this helps!
Sam.
I've realised the method I described above will not work in IE... the location.reload acts just like a browser refresh and IE will clear out the form fields annoyingly!
Thinking caps back on!
Thanks Sam! I have another question to all... Is there a simple way to add email validation to this form? I like the idea of not using JavaScript to handle error messages but and pretty new to CF. I am using the code pretty much as is above and noticed that as long as the field isn't empty any value can be passed. Any help is appreciated.
Add this to the <cfif isDefined("form.send")>
<cfif not len(trim(form.email)) or not isValid("email", form.email)>
<cfset error = error & "Please include a valid email address!<br>">
</cfif>
nobody answered me about encrypt and decrypt why don't we use them to pass the captcha ? it looks better than hash
Thanks for that solution Ron! Worked like a charm!
Thanks for the example!
I need some help! I had this working fine and now it's just not working. After filling in the form, the form is still visible and the text "Correct these errors" is still showing with no error messages. I am using the code exactly as is. Does anyone have any ideas? Here is the site:
http://www.overdriveevent.c...
Thanks!
I don't believe you are using the code exactly as is. I bet you are missing:
<cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif>
or you are missing the <cfif showForm> aroudn the form.
I think levensok's issue might have been with load balancing. cfimage writes the image to disk before displaying it. This means the image could be written to one server but the second server is trying to display it which obviously won't work. It seems the only way to solve this is to use sticky sessions, which is not an option for me. I guess I could use a folder that all the servers have access to but then I need to deal with the cleanup. Not an option I'd like to use. Has anyone else figured out a way around this?
Unfortunately there is no easy way. The captcha form of cfimage handles both randomly placing the text, saving the file, and generating HTML. You can't make it not do the HTML.
You can, of course, make your own image, but then you need to be responsible for adding the text yourself (which means picking the random font, position, etc).
I'm trying to use the captcha with CF8, but the hashed value for the form.captcha is different from the from to the submit side of the script, thus, the submit fails every time. What gives? Does nobody else have this problem?
I'm trying to implement this but am having problems.. my image is not showing up. I stripped it from my code and posted your code from the example above verbatim and the image shows broken... when I look at the properties... i see the image as
mydomain.com/CFFileServlet/...
Is this a server setting that is incorrect? While I'm pretty good at coding CF.. I'm a bit clueless when it comes to server settings like this. I am using CF8 as well.
Thanks and great tutorial... Nice an easy... I just need to get mine to work!
Capcha's make use of a dynamic image hosted directly by the CF server, so yes, that image does make sense. Can you share your code, and the URL if it is on a public server?
Ray,
My code is the EXACT code, with nothing else on it as your page above...
I just turned on robust reporting and see it's messing with one of my components it seems. I'm even more lost now that I see the error.. it's usually opposite!
The sample form is located at:
http://www.hermanscentral.c...
I GREATLY appreciate the help
Looks like something not configured right. Do you have a 404 handler for the server? Disable it - just temporarily - and see if that corrects it.
I'm sorry I wasn't clear.. I had said I already cleared out my custom 404.. so in cf admin on Missing Template Handler and Site-Wide Error Handler I have nothing entered. I did have a custom 404 that cleans misc strings and such.. and then attempts to find products or departments related and serve up a related result for 404s....
Turns out this was triggering the custom 404.cfm BUT on removing that 404.. (just removing all fancy code and putting 404 on that 404.cfm) the image still doesn't pop up.
Any other ideas?
Ray - Thanks again for your help.. for those that come across the same issue.. it was a matter of mapping a virtual directory..
so create a virtual directory called CFFileServlet
Map it to (your coldfusion install) / tmpCache/CFFileServlet and there you have it.
You rule an thanks for the tutorial!
Ray,
instead of putting the captcha string (plain or hash'ed) in a form field, i use to put it in the session scope, so its value is not in any way transferred to browser: at submit i get my captcha from session and compare ot with the submited value. What do you think about?
That is better. I avoided the session scope because I wanted this demo to be as simple as possible. :)
Hi Ray,
I get a java.lang.reflect.InvocationTargetException exception for the following line in the cfc file:
var fileOutputStream = createObject("java", "java.io.FileOutputStream").init(arguments.filePath);
Please can you help me out?
Thanks,
Reshma
Hi Ray,
I just wanted to mention that the version of CF that we are using is CF 6.
Thanks,
Reshma
I assume you are talking about Lyla, not the built in captcha in CF8? If so - it may simple be that Lyla doesn't support CF6. You should contact them to see. I checked their FAQ and saw mention of CF7 functionality, so that is probably it.
Thank you Ray.
Hi Ray,
I was wondering if you have had to integrate this into an application already using jQuery for form validation. I did further research and it seems that doing an ajax post to validate would not be a secure option. Any thoughts on this? Thanks.
Sincerely,
Aaron H.
I've not done this with jQuery validation yet. If you stored the hash in a session scope then it should be ok to do.
I'll put it on my list. I've done a few blog entries already on the main jQuery validation library, and this would be a good followup. (Warning, I'll probably forget to post back here, so check the blog daily. Or every hour. ;)
Is there a way to refresh the cfcaptcha image without refreshing the whole page, in case people can't identify the text and want to try a new image?
It is possible. You would need to use Ajax to reload the image and ensure you use the right validation on saving. If you remind me, I can try to whip up a demo of this next week.
I use another system. I check if the field is ok
if the field is ok, it is in hidden and the guy does not have to refill it. If there is 0 error, the form is processed.
@Jeremy's Dad: http://www.coldfusionjedi.c...
Why not skip the hashing altogether, and just write the string to a session var before it renders, then check it on the far side?
Sean - for this demo I wanted something as simple as possible. Therefore, I did not assume the Session scope was enabled. If you view the new version I linked to above, one of the changes I made was to use the Session scope.
I've had issues with spam bots finding the word "captcha," and doing a really good job at being able to read these captcha images. To partly combat it, I suggest not using the word "captcha."
I also pass an encrypted URL param based on a session variable to the captcha.cfm code. If the decryption fails, the captcha.cfm code has detected a spam bot. For example: (in the form code)
<cfset sessionID = Session.sessionID>
<cfset appID = getAppID ()> <!--- you would write getAppID () --->
<cfset enc = Encrypt (sessionID, appID, "CFMX_COMPAT", "hex");
<img src="skinnypig.cfm?sid=#somevalue#">
NOTE: skinnypig.cfm replaces captcha.cfm, and you would write getAppID (), specific to your application framework.
Then in your captcha.cfm code:
<cfparam name="URL.sid" default="">
<cfset sessionID = Session.sessionID>
<cfset appID = getAppID ()>
<cfset encSessionID = Decrypt (URL.sid, appID, "CFMX_COMPAT", "hex")>
<cfset flValid = true>
<cfif sessionID NEQ encSessionID>
<cfset flValid = true>
</cfif>
if the decryption fails, you would store some constant value, such as "SPAMBOT-DETECTED" in your session variable. When your form is submitted, you can check to see if the captcha code detected a spambot.
I can't get the image to load, under CF 9.0.1. The source shows the path to the image as "/CFFileServlet/_cf_captcha/_captcha_img-6722633845177586041.png" but all I see is a red x.
I found discussion of the same problem at http://forums.crystaltech.c... but the answer seems to be that in CF 9 you have to save the image and then have the img tag refer to the file saved.
Is that the case?
Nope, it is not the case. Most likely something was misconfigured on the web server. I assume you are using Crystaltech, right? If so - that's a bug in their implementation, and if they are requiring this, it is their fault.
Thanks, Ray. I am not using CrystalTech, never even heard of it. I am the Cold Fusion server admin, I'm working on an internal DEV server, Windows 2003 VM, that our central IT folks built but which I administer.
Hmm. Any particular errors in IIS logs? Perhaps do a quick reinstall to ensure web mappings didn't get borked?
Ray, the problem seems to be the server configuration. I put a simple test file on my internal DEV, QA, and PROD servers, and it does not work, but the same file works fine on my server in our external DMZ. Now I just have to figure out what is causing the problem. Thanks!
Ray, for the record, Crystaltech no longer has the CF 9 update on any of their shared servers... much to my chagrin.
new developer here, how would set this up to send the comments to my email?
Just use the cfmail tag. Check the docs. It's pretty easy to use.
Is it better to use this than to cfformprotect? We've used that on our forms and been pretty happy with it. I really don't want to do captcha if I can help it.
I'd say no. cfformprotect is like the Galactus of spam protection. I use it here. It's a bit more involved setup wise, and some of the services it use has costs for commercial usage (I believe), so CAPTCHA is still something you will need from time to time.
Ray,
Is there a way to validate the hash via javascript? If so, can you give me an example?
If you use the modified version that stores the CAPTCHA in the session (http://www.raymondcamden.co..., then you could use AJAX to hit the server and confirm if the values match.
I'm in the process of implementing your Captcha code, not sure whether to use the session value code or the original, with the hash function. To get the image to redisplay without loosing the form data or displaying the error message, I just added a button (name=newimage) and used a cfif block around the null errors section, as follows:
<cfif NOT isDefined("form.NewImage")>
<cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif>
</cfif>
I also specified the font type as serif to help differentiate letters and numbers in the image. Before I post my page, I'm wondering, since these posts are so old, is this approach still regarded as effective, or is Google's reCaptcha the standard now?
I'm not quite sure I get what you are asking?
Hackers and spammers have gotten more sophisticated in their ability to circumvent antispam measure such as this method based on CFIMAGE, and the code uses the word "captcha" in several instances. Some bots search for this word in order to get around the measures implemented to stop them The images used by your method may be more easily deciphered than, say, those used by reCaptcha, which I find difficult to use, myself (the images presented in the form, not the code). I was just wondering whether you still regard your method still effective given today's environment.
Well, you may notice I don't use it here. :) Here I use cfformprotect which uses Akismet and... something else (bit busy). I do use captcha in other sites. I know it isn't perfect, but it doesn't seem to hurt either.
Thanks, Ray. I am looking at cfformprotect as well as cfcaptcha, but I wanted a simpler approach to get something out the door more quickly. I'll see what happens .....
Thanks for this Ray. Works like a charm.
You are most welcome. I fixed the formatting issues.