BotDetect ASP.NET 2.0 CAPTCHA Randomization VB.NET Code Sample
The CAPTCHA randomization sample shows how you can easily randomize various Captcha control parameters, which can significantly improve the CAPTCHA protection security and is the best way to take full advantage of the 50 different CAPTCHA algorithms shipped with BotDetect.
Sample Project Location
By default, this sample project is installed at
C:\Program Files\Lanapsoft\BotDetect\ASP.NET 2.0\v2.0\Samples\VBNetBotDetect2RandomDemo\.
You can also run it from the Start Menu:
Programs > Lanapsoft > BotDetect > ASP.NET 2.0 > v2.0 > Samples > VB.NET BotDetect Random Demo Preview.
Default.aspx
Full Source Code Listing
<%@ Page Language="VB" AutoEventWireup="false"
CodeFile="Default.aspx.vb" Inherits="_Default" %>
<%@ Register Assembly="Lanap.BotDetect" Namespace="Lanap.BotDetect"
TagPrefix="BotDetect" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>BotDetect Random Demo</title>
<link type='text/css' rel='Stylesheet' href='StyleSheet.css' />
</head>
<body>
<form id="form1" runat="server">
<fieldset id="Preview">
<legend>
<span id="PreviewLegend">CAPTCHA Preview</span>
</legend>
<div id="PromptDiv">
<span id="Prompt">Type the characters you see in
the picture</span>
</div>
<div id="CaptchaDiv">
<BotDetect:Captcha ID="SampleCaptcha" runat="server" />
</div>
<div id="ValidationDiv">
<asp:TextBox ID="CodeTextBox" runat="server">
</asp:TextBox>
<asp:Button ID="ValidateButton" runat="server" />
<asp:Label ID="MessageCorrectLabel" runat="server">
</asp:Label>
<asp:Label ID="MessageIncorrectLabel" runat="server">
</asp:Label>
</div>
</fieldset>
<div id="Note">
<span>NOTE: the Trial version will use "LANAP" instead of a
random code in 50% of renderings.</span>
</div>
</form>
</body>
</html>
Explanation
Lines required to add the BotDetect CAPTCHA control to the ASP.NET form are bolded. To use the <BotDetect:Captcha> control, we must first register the Lanap.BotDetect.dll assembly using the <%@Register %> directive.
The form also contains an <asp:TextBox> for the user input, an <asp:Button> to submit the page, and a pair of <asp:Label> controls which are used to show the CAPTCHA validation result. The rest of the file is either generated by Visual Studio 2005 by default, or just defines the layout and visual presentation of the page.
There is no difference between the .aspx file of this sample and the basic one, since all CAPTCHA randomization is performed in codebehind.
Default.aspx.vb
Full Source Code Listing
Imports System.Drawing
Imports Lanap.BotDetect
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Init(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Init
'register CAPTCHA-specific event handler
AddHandler SampleCaptcha.PreDrawCaptchaImage, _
AddressOf SampleCaptcha_PreDrawCaptchaImage
End Sub
Protected Sub Page_PreRender(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.PreRender
' initial page setup
If (Not IsPostBack) Then
'set control text
ValidateButton.Text = "Validate"
MessageCorrectLabel.Text = "Correct!"
MessageIncorrectLabel.Text = "Incorrect!"
'these messages are shown only after validation
MessageCorrectLabel.Visible = False
MessageIncorrectLabel.Visible = False
End If
CodeTextBox.Attributes.Add("onkeyup", _
"this.value = this.value.toLowerCase();")
If (IsPostBack) Then
'validate the input code, and show
'the appropriate message
Dim code As String = CodeTextBox.Text.Trim().ToUpper()
If (SampleCaptcha.Validate(code)) Then
MessageCorrectLabel.Visible = True
MessageIncorrectLabel.Visible = False
Else
MessageCorrectLabel.Visible = False
MessageIncorrectLabel.Visible = True
End If
'clear previous user code input
CodeTextBox.Text = ""
End If
End Sub
' all CAPTCHA randomization should be performed in this event
' handler instead of Page_Load or Page_PreRender, because this
' event is also fired for direct CAPTCHA image requests (which
' skip the page events since the page is never loaded), for
' example when clicking the Reload CAPTCHA button repeatedly
Protected Sub SampleCaptcha_PreDrawCaptchaImage( _
ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim captcha As ICaptcha = sender
' randomize code generation properties
captcha.CodeType = RandomizationHelper.GetRandomCodeType()
captcha.CodeLength = _
RandomizationHelper.GetRandomCodeLength(4, 6)
' randomize text style
Dim styles As TextStyleEnum() = { _
TextStyleEnum.Lego, TextStyleEnum.MeltingHeat, _
TextStyleEnum.Ghostly, TextStyleEnum.FingerPrints, _
TextStyleEnum.Graffiti2, TextStyleEnum.Bullets2, _
TextStyleEnum.CaughtInTheNet2, TextStyleEnum.Collage, _
TextStyleEnum.Chalkboard
}
captcha.TextStyle = _
RandomizationHelper.GetRandomTextStyle(styles)
End Sub
End Class
Explanation
In the Page_Init phase of the ASP.NET page lifecycle, we register a special event handler to be executed before every CAPTCHA image is generated, in which we perform the CAPTCHA randomization. Since CAPTCHA images are generated and sent to the client in a Http request separate from the one loading the ASP.NET page (when this code gets executed), we add this event handler to ensure the CAPTCHA is randomized every time it is drawn, and not only once per page load.
This is important because the CAPTCHA image requests don't have to be tied to the number of page loads - most notably, when using the Reload CAPTCHA button, and when bots are accessing the CAPTCHA image directly, it's possible that many CAPTCHA images will be generated after only a single page load (and the related Page_PreRender execution).
In the Captcha_PreDrawCaptchaImage handler, the Captcha control instance is available as the sender parameter. To make the randomization as simple as possible, we use the RandomizationHelper class, which allows us to get a random value of a given parameter from all available values (as the CAPTCHA CodeType), or from a given range of values (as the CAPTCHA CodeLength), or from the given set of values (as the CAPTCHA TextStyle).
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".
App_Code\RandomizationHelper.vb
Full Source Code Listing
Imports Microsoft.VisualBasic
Imports System.Drawing
Imports Lanap.BotDetect
Public NotInheritable Class RandomizationHelper
Private Sub New()
'constructor omitted, static methods only
End Sub
' a single global generator is used for all random numbers
Private Shared ReadOnly _rand As Random = New Random()
Public Const DefaultCodeType As CodeTypeEnum = _
CodeTypeEnum.AlphaNumeric
Public Shared Function GetRandomCodeType( _
ByVal ParamArray usedValues As CodeTypeEnum()) As CodeTypeEnum
Dim codeType As CodeTypeEnum = DefaultCodeType
If (0 = usedValues.Length) Then
Dim values As Array = _
System.Enum.GetValues(GetType(CodeTypeEnum))
Dim max As Integer = values.Length
codeType = CType(_rand.Next(max), CodeTypeEnum)
ElseIf (1 = usedValues.Length) Then
codeType = usedValues(0)
Else
Dim max As Integer = usedValues.Length
Dim index As Integer = _rand.Next(max)
codeType = usedValues(index)
End If
Return codeType
End Function
Public Const DefaultCodeLength As Integer = 5
Public Const MinCodeLength As Integer = 1
Public Const MaxCodeLength As Integer = 15
Public Shared Function GetRandomCodeLength( _
Optional ByVal min As Integer = 0, Optional ByVal max As _
Integer = 0) Integer
If ((max > MaxCodeLength) OrElse (max < MinCodeLength)) Then
max = MaxCodeLength
End If
If ((min < MinCodeLength) OrElse (min > max)) Then
min = MinCodeLength
End If
Return _rand.Next(min, max + 1)
End Function
Public Shared ReadOnly DefaultImageFormat As ImageFormatEnum = _
ImageFormatEnum.Jpeg
Public Shared Function GetRandomImageFormat( _
ByVal ParamArray usedValues As ImageFormatEnum()) As _
ImageFormatEnum
Dim imageFormat As ImageFormatEnum = DefaultImageFormat
If (0 = usedValues.Length) Then
Dim max As Integer = System.Enum.GetValues( _
GetType(ImageFormatEnum)).Length
imageFormat = CType(_rand.Next(max), ImageFormatEnum)
ElseIf (1 = usedValues.Length) Then
imageFormat = usedValues(0)
Else
Dim max As Integer = usedValues.Length
Dim index As Integer = _rand.Next(max)
imageFormat = usedValues(index)
End If
Return imageFormat
End Function
Public Shared ReadOnly DefaultImageSize As Size = New Size(250, 50)
Public Shared ReadOnly MinImageSize As Size = New Size(50, 40)
Public Shared ReadOnly MaxImageSize As Size = New Size(500, 200)
Public Shared Function GetRandomImageSize() As Size
Return GetRandomImageSize(New Size(0, 0), New Size(0, 0))
End Function
Public Shared Function GetRandomImageSize(ByVal maxSize As Size) _
As Size
Return GetRandomImageSize(New Size(0, 0), maxSize)
End Function
Public Shared Function GetRandomImageSize(ByVal minSize As Size, _
ByVal maxSize As Size) As Size
'determine width
If ((maxSize.Width > MaxImageSize.Width) OrElse _
(maxSize.Width < MinImageSize.Width)) Then
maxSize.Width = MaxImageSize.Width
End If
If ((minSize.Width < MinImageSize.Width) OrElse _
(minSize.Width > maxSize.Width)) Then
minSize.Width = MinImageSize.Width
End If
Dim width As Integer = _
_rand.Next(minSize.Width, maxSize.Width + 1)
'determine height
If ((maxSize.Height > MaxImageSize.Height) OrElse _
(maxSize.Height < MinImageSize.Height)) Then
maxSize.Height = MaxImageSize.Height
End If
If ((minSize.Height < MinImageSize.Height) OrElse _
(minSize.Height > maxSize.Height)) Then
minSize.Height = MinImageSize.Height
End If
Dim height As Integer = _
_rand.Next(minSize.Height, maxSize.Height + 1)
'the result
Return New Size(width, height)
End Function
Public Const DefaultTextStyle As TextStyleEnum = _
TextStyleEnum.Chalkboard
Public Shared Function GetRandomTextStyle( _
ByVal ParamArray usedValues As TextStyleEnum()) As _
TextStyleEnum
Dim textStyle As TextStyleEnum = DefaultTextStyle
If (0 = usedValues.Length) Then
Dim max As Integer = _
System.Enum.GetValues(GetType(TextStyleEnum)).Length
textStyle = CType(_rand.Next(max), TextStyleEnum)
ElseIf (1 = usedValues.Length) Then
textStyle = usedValues(0)
Else
Dim max As Integer = usedValues.Length
Dim index As Integer = _rand.Next(max)
textStyle = usedValues(index)
End If
Return textStyle
End Function
End Class
Explanation
RandomizationHelper is a small utility class which you can use in your projects to simplify various CAPTCHA parameter randomization.
For each CAPTCHA parameter which can take a numeric or enumerated value, this class provides a static method returning a random value. Each parameter has a default value, and numeric parameters also have predefined minimum and maximum values.
All randomization methods take a variable number of parameters, either via multiple method overloads or via the params keyword.
Methods returning numeric values (for example, CodeLength) behave in the following manner:
- If no parameters are given, the random value is selected between the predefined minimum and maximum values.
- If a single value is given as a parameter, the random value is selected between the predefined minimum and the given value as the maximum. Note that it is not possible to use a maximum greater than the predefined one, only a lesser one.
- If two values are given as parameters, the random value is selected between the first value as the minimum and the second value as the maximum. Note that it is not possible to use a maximum greater than the predefined one, or a minimum lesser than the predefined one.
Methods returning enumerated values (for example, CodeType) behave in the following manner:
- If no parameters are given, the random value is selected from all possible enumeration values.
- If a single value is given as the parameter, it is returned as the result - be careful not to pass a single value to these methods, since there will be no randomization. And if you want to use a single value, just assign it to the appropriate parameter directly.
- If a set (i.e. an array) of values is given as the parameter, the random value is selected from within that set.
Of course, you could also use such randomization code directly in your projects, but encapsulating it in the RandomizationHelper class is made available for convenience and better code readability.
Web.config
Full Source Code Listing
<?xml version="1.0"?>
<!--
Note: As an alternative to hand editing this file you can use the
web admin tool to configure settings for your application. Use
the Website->Asp.Net Configuration option in Visual Studio.
A full list of settings and comments can be found in
machine.config.comments usually located in
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
<configuration>
<connectionStrings/>
<system.web>
<httpHandlers>
<add verb="*" path="LanapCaptcha.aspx"
type="Lanap.BotDetect.CaptchaHandler, Lanap.BotDetect"/>
</httpHandlers>
<sessionState mode="InProc" cookieless="AutoDetect" timeout="20"
sessionIDManagerType="Lanap.BotDetect.Persistence.
CustomSessionIDManager, Lanap.BotDetect" />
<!--
Set compilation debug="true" to insert debugging
symbols into the compiled page. Because this
affects performance, set this value to true only
during development.
-->
<compilation debug="false">
<assemblies>
<add assembly="System.Design, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
<pages>
<namespaces>
<clear/>
<add namespace="System"/>
<add namespace="System.Collections"/>
<add namespace="System.Collections.Specialized"/>
<add namespace="System.Configuration"/>
<add namespace="System.Text"/>
<add namespace="System.Text.RegularExpressions"/>
<add namespace="System.Web"/>
<add namespace="System.Web.Caching"/>
<add namespace="System.Web.SessionState"/>
<add namespace="System.Web.Security"/>
<add namespace="System.Web.Profile"/>
<add namespace="System.Web.UI"/>
<add namespace="System.Web.UI.WebControls"/>
<add namespace="System.Web.UI.WebControls.WebParts"/>
<add namespace="System.Web.UI.HtmlControls"/>
</namespaces>
</pages>
<!--
The <authentication> section enables configuration
of the security authentication mode used by
ASP.NET to identify an incoming user.
-->
<authentication mode="None"/>
<!--
The <customErrors> section enables configuration
of what to do if/when an unhandled error occurs
during the execution of a request. Specifically,
it enables developers to configure html error pages
to be displayed in place of a error stack trace.
<customErrors mode="RemoteOnly"
defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
-->
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="LanapCaptchaHandler" />
<add name="LanapCaptchaHandler" preCondition="integratedMode"
verb="*" path="LanapCaptcha.aspx"
type="Lanap.BotDetect.CaptchaHandler, Lanap.BotDetect" />
</handlers>
</system.webServer>
</configuration>
Explanation
Lines neccesary for BotDetect CAPTCHA to function properly have been bolded, other lines are all standard values generated by Visual Studio 2005 by default. There are no special settings related to CAPTCHA randomization in the web.config file.
The <httpHandlers> element registers the path used for CAPTCHA image and sound requests for processing by Lanap.BotDetect.dll code. The <system.webServer> element serves the same purpose, but is used only by IIS 7.0 machines configured to integrate the ASP.NET pipeline in the IIS process.
The validateIntegratedModeConfiguration="false" declaration ensures that the web.config file can be processed by by older versions of IIS (5.1, 6.0) as well as the 7.0 version. Since the HttpHandler registration syntax differs between IIS versions and ASP.NET integration modes, having both elements makes the web.config file compatible with all supported IIS versions.
The <sessionState> element declares the persistence mechanism used by BotDetect to keep the CAPTCHA codes and settings for each user. Different Session State modes, providers, timeouts and cookieless attribute settings can be used, but the sessionIDManagerType element is required to fix a bug caused by Windows Media Player 11 when requesting audio CAPTCHAs (as explained in this FAQ item).


