BotDetect CAPTCHA Randomization ASP Code Sample

The CAPTCHA randomization sample contains the code used to randomize various Captcha parameters. Randomly using different CAPTCHA algorithms and other available parameter values can significantly improve the CAPTCHA security, and is the best way to take full advantage of the 50 different algorithms shipped with BotDetect.

Sample Project Location

By default, this sample project is installed at
c:\Program Files\Lanapsoft\BotDetect\ASP\v2.0\Samples\CaptchaRandomization\.

You can also run it from the Start Menu:
Programs > Lanapsoft > BotDetect > ASP > v2.0 > Samples > CAPTCHA Randomization Sample.

BotDetectRandomDemo.asp

Full Source Code Listing

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head>
    <title>BotDetect CAPTCHA ASP Randomization Demo - Input 
      Page</title>
    <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
    <script type="text/javascript" src="BotDetectScript.js">
    </script>
  </head>
  <body>
    <form name="SampleForm" id="SampleForm" method="post" 
      action="ProcessFormRandom.asp">
    <fieldset id="SampleFields">
      <legend>Sample input form</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" value="<%=Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" type="text"  id="LastName" 
          class="textbox" value="<%= Request("LastName") %>" />
      </div>
    </fieldset>
    <fieldset id="CaptchaValidation">
      <legend>CAPTCHA Validation</legend>
      <div id="PromptDiv">Retype the code from the picture</div>
      <div id="CaptchaDiv">
        <div id="CaptchaImage">
          <img id="SampleForm_CaptchaImage" src="
            LanapBotDetectHandler.asp?Command=CreateImage" 
            alt='CAPTCHA Code Image' />
        </div>
        <div id="CaptchaIcons">
          <a href='LanapBotDetectHandler.asp?Command=CreateSound' 
            onclick='LBD_LoadSound("SampleForm_SoundPlaceholder", 
            "LanapBotDetectHandler.asp?Command=CreateSound");return 
            false;' title="Speak the code"><img src="speaker.gif" 
            alt="Speak the code" /></a>
          <a href='#' onclick='LBD_ReloadImage(
            "SampleForm_CaptchaImage"); return false;' title="
            Change the code"><img src="reload.gif" alt="
            Change the code" /></a>
          <div id='SampleForm_SoundPlaceholder' class="placeholder">
            &nbsp;</div>
        </div>
      </div>
      <div class="input">
        <label for="CaptchaCode">Code:</label>
        <input name="CaptchaCode" id="CaptchaCode" type="text" 
          class="textbox" onkeyup="this.value = 
            this.value.toLowerCase();" />
      </div>
      <%
      If (Request("WrongCode")<>"") Then
        Response.Write("<div><span id='CodeIncorrectLabel'>Incorrect _
          code</span></div>")
      End If
      %>
      </fieldset>
      <div id="ActionDiv">
        <input type="submit" name="ProcessForm" value="Process Form" 
          id="ProcessForm" />
      </div>
      <div id="Note">
        <span>NOTE: the Trial version will use "LANAP" instead of a 
          random code in 50% of CAPTCHA images.</span>
      </div>
    </form>
  </body>
</html>

Explanation

This form is esentially the same as in the non-randomized CAPTCHA sample projetct, with the same stylesheet, JavaScript, CAPTCHA image and sound inclusion code. The only difference is that the CAPTCHA image element doesn't set any parameters in the request querystring, since they are all set or randomized directly in the LanapBotDetectHandler.asp file.

ProcessFormRandom.asp

Full Source Code Listing

<%
  'Captcha validation
  Dim result, codeKey, inputCode
  result = False
  codeKey = "LanapBotDetectCode"
  inputCode = Request("CaptchaCode")

  If (Session(codeKey)<>"") Then
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'each Captcha code can only be validated once
    Session(codeKey) = ""
  End If

  If result = False Then
    first_name = Request("FirstName")
    last_name = Request("LastName")
    redirect_url = "BotDetectRandomDemo.asp?FirstName=" + _
      first_name + "&LastName=" + last_name + "&WrongCode=WrongCode"
    Response.Redirect redirect_url
  End If
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect CAPTCHA ASP Randomization Demo - Protected 
    Page</title>
  <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
</head>
<body>
  <form name="form1" method="post" id="form1" action="ProcessForm.asp">
    <fieldset id="Properties">
      <legend>BotDetect CAPTCHA validation passed!</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" readonly="readonly" 
          value="<% =Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" id="LastName" type="text" 
          class="textbox" readonly="readonly" 
          value="<% =Request("LastName") %>" />
      </div>
    </fieldset>
    <div id="ActionDiv">
      <a href="BotDetectRandomDemo.asp">Back to the sample form</a>
    </div>
  </form>
</body>
</html>

Explanation

CAPTCHA validation code doesn't change when randomizing CAPTCHA properties, so the same explanations and notes apply as in the regular CAPTCHA validation sample.

LanapBotDetectHandler.asp

Full Source Code Listing

<%
Dim code, codeKey, codeHash, codeHashKey, captchaId, comCaptcha

'the Captcha code is kept in Session state with this key
codeKey = "LanapBotDetectCode"
codeHashKey = "LanapBotDetectCodeHash"

'if there are multiple Captchas on tn the site, a Captcha id is 
'required to distinguish between them; otherwise, it can be ignored
captchaId = Request("CaptchaId")
If(captchaId<>"") Then
  codeKey = codeKey & "_" & captchaId
End If

If (Request("Command")="CreateImage") Then
'Captcha image generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'randomize the Captcha image drawing algorithm
  Dim algorithms(5)
  algorithms(0) = 28
  algorithms(1) = 36
  algorithms(2) = 44
  algorithms(3) = 25
  algorithms(4) = 39
  algorithms(5) = 48
  comCaptcha.TextStyle = RandomFromValues(algorithms)
  
  'randomize the Captcha code length
  comCaptcha.CodeLength = RandomFromRange(4, 6)
  
  'set other Captcha properties
  comCaptcha.ImageWidth = 238
  comCaptcha.ImageHeight = 50
  comCaptcha.CodeType = 0
  comCaptcha.Format = "JPEG"

  'set Captcha image Http response headers
  Response.Buffer = True
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  Response.ContentType = "image/jpeg"

  'generate the Captcha image binary data
  Dim varPicture
  varPicture = comCaptcha.CreateImage

  'save the Captcha code for sound generation and validation
  code = comCaptcha.GetValue
  Session(codeKey) = code
  'save the code hash for backward compatibility with 
	'older validation code
  codeHash = comCaptcha.GetHashValue
  Session(codeHashKey) = codeHash

  'send Captcha image binary data to the client
  Response.BinaryWrite varPicture
  Set comCaptcha = Nothing  'dispose of the Captcha component instance
  Response.End
'end Captcha image generation

ElseIf (Request("Command")="CreateSound") Then
'audio Captcha generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'set Http response headers
  If (Request.ServerVariables("HTTPS")="off") Then
    Response.CacheControl = "no-cache"
    Response.AddHeader "Pragma", "no-cache"
    Response.Expires = -1
  End If
  Response.Buffer = True
  Response.ContentType = "audio/x-wav"
  Response.AddHeader "content-disposition", _
    "attachment; filename=captcha.wav"
  Response.AddHeader "Content-Transfer-Encoding", "binary"
  Response.AddHeader "Connection", "Close"

  'generate the audio Captcha binary data from the saved code
  code = Session(codeKey)
  varSound = comCaptcha.CreateSoundFromCode(code)

  'send audio Captcha binary data to the client
  Response.BinaryWrite varSound
  Set comCaptcha = Nothing 'dispose of the Captcha component instance
  Response.End
'end audio Captcha generation

ElseIf (Request("Command")="Validate") Then
'Ajax Captcha validation

  Dim result
  result = False

  If (Session(codeKey)<>"") Then
    Dim inputCode
    inputCode = Request("Code")
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'Ajax validation shouldn't remove the code if successful, so 
    'both client- and server-side validation can be performed and pass
    If (Not result) Then
      Session(codeKey) = ""
    End If
  End If

  'Http response headers
  Response.Buffer = True
  Response.ContentType = "text/javascript"
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  Response.AddHeader "Connection", "Close"

  'send the JSON validation result to the client
  Response.Write "{ 'result': " & LCase(CStr(result)) & " }"
  Response.End

'end Ajax Captcha validation
End If

'If neither of the above conditions was met
Response.Status = "400 Bad Request"
Response.End

'helper randomization function
Function RandomFromRange(lowerLimit, upperLimit)
  Dim num
  Randomize
  num = CInt((upperlimit - lowerlimit)*Rnd() + lowerlimit) 
  RandomFromRange = num
End Function

Function RandomFromValues(values)
  Dim num
  Randomize
  num = RandomFromRange(0, UBound(values))
  RandomFromValues = values(num)
End Function
%>

Explanation

The CAPTCHA randomization code is located in the LanapBotDetectHandler.asp module and not in the ASP form code, because CAPTCHA images can be requested multiple times per page load. Images in general are loaded in separate Http requests from the main page, and there are many possible cases when the CAPTCHA images get accessed directly, without reloading the page: users clicking the Reload CAPTCHA button, various bots accessing the image, etc. Since we want the CAPTCHA randomization to apply for each generated CAPTCHA image, and not only once per page load, the randomization code must be included in the CAPTCHA generation code.

In this version of the LanapBotDetectHandler.asp file, we don't read various CAPTCHA properties from request querystring parameters, but we set them directly and randomize some of them. The CAPTCHA code length is randomized between a minimum and a maximum value, while the CAPTCHA image generation algorithm is randomly chosen from a set of given values. To make these randomizations as straightforward as possible, we define two utility functions handling exactly those cases. RandomFromRange(lowerLimit, upperLimit) returns a random value within the given bounds (inclusive), while RandomFromValues(values) takes an array of values and chooses one randomly.

You can also randomize other CAPTCHA properties in a similar manner, but the CAPTCHA drawing algorithm and the CAPTCHA code length are the ones that improve the CAPTCHA security the most when randomized. Every individual CAPTCHA algorithm can be theoretically and eventually be broken (given enough effort), but if the bot also has to recognize the algorithm used for every image, the task becomes an order of magnitudes harder. Also, several popular CAPTCHA algorithms have been broken because they used a fixed number of characters in their images – "find 5 characters in this image" is inherently a much easier task to automate than "find an unknown number of characters in this image".

BotDetectScript.js

Full Source Code Listing

function LBD_LoadSound(soundPlaceholderId, soundLink) {
  if(document.getElementById) {
    var i = soundLink.indexOf('&d=');
    if (-1 != i) {
      soundLink = soundLink.substring(0, i);
    }
    soundLink = soundLink + '&d=' + LBD_GetTimestamp();

    var placeholder = document.getElementById(soundPlaceholderId);
    var objectSrc = "<object id='captchaSound' 
      classid='clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95' 
      height='0' width='0' style='width:0; height:0;'><param 
      name='AutoStart' value='1' /><param name='Volume' value='0' 
      /><param name='PlayCount' value='1' /><param name='FileName' 
      value='" + soundLink + "' /><embed id='captchaSoundEmbed' 
      src='"+ soundLink + "' autoplay='true' hidden='true' 
      volume='100' type='"+ LBD_GetMimeType() +"' 
      style='display:inline;' /></object>";

    placeholder.innerHTML = "";
    placeholder.innerHTML = objectSrc;
  }
}

function LBD_GetTimestamp() {
  var d = new Date();
  var t = d.getTime() + (d.getTimezoneOffset() * 60000);
  return t;
}

function LBD_GetMimeType() {
  var mimeType = "audio/x-wav";
  return mimeType;
}

var LBD_ImgId = null;
var LBD_Img = null;
var LBD_NewImg = null;
var LBD_Parent = null;
var LBD_ImagePrompt = null;

function LBD_ReloadImage(imgId) {
  if(imgId) {
    LBD_ImgId = imgId;
    LBD_Img = document.getElementById(LBD_ImgId);
    var src = LBD_Img.src;

    var i = src.indexOf('&d=');
    if (-1 != i) {
      src = src.substring(0, i);
    }
    var newSrc = src + '&d=' + LBD_GetTimestamp();

    LBD_NewImg = document.createElement('img');
    LBD_NewImg.onload = LBD_ShowImage;
    LBD_NewImg.id = LBD_Img.id;
    LBD_NewImg.alt = LBD_Img.alt;
    LBD_NewImg.src = newSrc;

    LBD_ImagePrompt = document.createElement('span');
    LBD_ImagePrompt.appendChild(document.createTextNode('loading...'));

    LBD_Parent = LBD_Img.parentNode;
    LBD_Parent.removeChild(LBD_Img);
    LBD_Parent.appendChild(LBD_ImagePrompt);
  }
}

function LBD_ShowImage() {
  if(LBD_NewImg && LBD_Parent && LBD_ImagePrompt) {
    LBD_Parent.removeChild(LBD_ImagePrompt);
    LBD_Parent.appendChild(LBD_NewImg);
  }
}

Explanation

There are no changes in the clien-script code when randomizing CAPTCHA properties, since all randomization is performed on the server (otherwise it could be bypassed). The same explanations and notes apply as in the regular CAPTCHA validation sample.

FormStyle.css

Full Source Code Listing

body {
  background-color: #EEEEFF;
  font-family: Verdana, Arial;
  font-size: 0.9em;
}

fieldset {
  padding: 0 10px 10px 10px;
  margin: 11px;
  width: 300px;
  display: block;
}

div.input {
  margin: 7px 0;
}

legend {
  padding: 5px;
  color: #999999;
}

label {
  display: block;
  width: 85px;
  float: left;
  text-align: right;
  padding-right: 5px;
}

input.textbox {
  width: 170px;
}

input.textboxSmall {
  width: 40px;
}

#CodeIncorrectLabel {
  color: Red;
}

#CodeCorrectLabel {
  color: Green;
}

#Note {
  padding: 0;
  margin: 11px;
  margin-bottom: -7px;
  width: 320px;
  font-size: 0.8em;
  color: Red;
}

#PromptDiv {
  padding: 0;
  margin: 0;
  margin-bottom: 8px;
}

#ActionDiv {
  padding: 0 0 10px 10px;
  margin: 11px;
  margin-right: 0;
  width: 314px;
  text-align:right;
}

fieldset #ActionDiv{
  padding: 0;
  margin: 0;
  width: auto;
  text-align:right;
}

#CaptchaDiv {
  margin: 0;
  padding: 0;
  width:265px;
  height:50px;
  padding-bottom: 5px;
}

#CaptchaImage {
  float: left;
  margin: 0;
  padding: 0;
  width:240px;
  height:50px;
}

#CaptchaIcons {
  width: 22px;
  height: 50px;
  float: right;
  text-align: left;
  margin: 0;
  padding: 0;
}

td#CaptchaIcons {
  padding-left: 3px;
}

#CaptchaIcons img {
  border: 0;
  margin: 0;
  padding: 0;
  padding-bottom: 3px;
}

*html #CaptchaIcons img {
  margin-bottom: -2px;
}

.placeholder {
  visibility: hidden;
  width:0 !important;
  height:0 !important;
}

*html .placeholder {
  display: none !important;
}

#CaptchaPreviewDiv {
  margin: 0;
  padding: 0;
  padding-bottom: 5px;
}

div.FeaturesInput {
  margin: 7px 0;
}

div.FeaturesInput label {
  width: 110px;
}

Explanation

The style definitons used in this sample are the same as in the CAPTCHA validation sample.