Probing CAPTCHASP

An Examination of the Source Code of a CAPTCHA Web Control

ControlFreak

LANGUAGES: VB.NET | C#

ASP.NET VERSIONS: 2.x

 

Probing CAPTCHASP

An Examination of the Source Code of a CAPTCHA Web Control

 

By Steve C. Orr

 

In the September 2007 issue of asp.netPRO we examined the rise of bots and the solution of CAPTCHA technology (see CAPTCHASP) . We also examined how to use the free CAPTCHASP control. In this installment, we ll probe the source code of the CAPTCHASP control and examine its overall architecture, unique design traits, and major functions.

 

The CAPTCHASP control (shown in Figure 1) is a standard Web custom control compiled into a standalone DLL that can be added to the Visual Studio toolbox and dropped onto any ASP.NET Web form.

 


Figure 1: Let users in, keep bots out! The CAPTCHASP control provides instant, highly customizable CAPTCHA verification.

 

Securing the Page

The CAPTCHASP control s base class (WebControl) provides an OnInit event, which has been overridden with the code listed in Figure 2.

 

Protected Overrides Sub OnInit(ByVal e As System.EventArgs)

 If Me.DesignMode = False AndAlso Not _

 Page.Request.QueryString("CAPTCHASP_Img" Is Nothing) Then

   'A request for the CAPTCHA image has been intercepted

   '(Detected by querystring parameter)

   'Replace normal page/control output with CAPTCHA image

   OutputImage()

   Exit Sub

 End If

 InitControls() 'Initialize constituent controls

 If Me.DesignMode=False AndAlso Page.IsPostBack=False Then

   'Secure viewstate to prevent tampering

   Page.RegisterRequiresViewStateEncryption()

   If Page.Session.Mode <> _

   SessionState.SessionStateMode.Off Then

       Page.ViewStateUserKey = Page.Session.SessionID

   End If

 End If

End Sub

Figure 2: CAPTCHASP overrides its base class OnInit event with this code.

 

If the page s QueryString contains a parameter named CAPTCHASP_Img , then the OutputImage subroutine (more on this later) is called; otherwise, the InitControls subroutine (detailed in the next section) is invoked.

 

Finally, the page s security settings are increased because this control (and, thus, its page) is a likely target of hackers and bots. The page s RegisterRequiresViewStateEncryption method is called to inform the page that it should use the CPU cycles required to encrypt ViewState to discourage tampering. Similarly, the page s ViewStateUserKey property is set to the SessionID of the current user s session (if any) to help prevent cross-site scripting and session hijacking.

 

Parts & Pieces

The standard run-time layout of the CAPTCHASP instance shown in Figure 1 consists of four main elements: The CAPTCHA image, the instruction label underneath, the entry textbox, and the Submit button. (There are other optional elements, as well, but they are fairly redundant from a coding standpoint, so I won t cover them here.)

 

A slightly simplified version of CAPTCHASP s InitControls subroutine is listed in Figure 3. This subroutine is called from the base control class overridden OnInit event shown in Figure 2. InitControls creates each sub element of the CAPTCHASP user interface and initializes them with their default values.

 

'Initialize constituent controls with default values

Protected Sub InitControls()

 Me.Controls.Clear()

 'Create the Image tag

 _imgMain = New System.Web.UI.WebControls.Image

 _imgMain.AlternateText = "CAPTCHA"

 _imgMain.Width = New Unit(100, UnitType.Percentage)

 If Me.DesignMode Then

   _imgMain.ImageUrl = _DefaultImgUrl

 Else

   With Page.Request.FilePath

     'Create a quasi-callback URL

      Dim SlashInd As Integer = .LastIndexOf("/")

     Dim BaseUrl As String = .Substring(SlashInd + 1)

     _imgMain.ImageUrl = BaseUrl & "?CAPTCHASP_Img=true"

   End With

 End If

 'Create the instruction label

 _lblInstruct = New Label()

 InstructionStyle.Font.CopyFrom(Me.Font)

 InstructionStyle.Width = _DefaultInstructionWidth

 'Create the entry text box

 _txtEntry = New TextBox

 _txtEntry.ID = "EntryTextBox"

 _txtEntry.Attributes.Add("AUTOCOMPLETE", "OFF")

 'Create the submit button

 _btnSubmit = New Button

 _btnSubmit.ID = "CAPTCHASP_SubmitButton"

 ButtonStyle.Width = New Unit(20, UnitType.Percentage)

 _btnSubmit.CommandName = "CAPTCHASP_SubmitButton"

 _btnSubmit.CommandArgument = "CAPTCHASP_SubmitButton"

 'Create the Error label (only visible upon entry error)

 _lblErr = New Label()

 FailMessageStyle.Font.CopyFrom(Me.Font)

 FailMessageStyle.ForeColor = Color.Red

 'Add each control to the control collection with spacing

 Controls.Add(_lblTitle)

 Controls.Add(_imgMain)

 Controls.Add(New LiteralControl("
"))

 Controls.Add(_lblInstruct)

 Controls.Add(New LiteralControl("
"))

 Controls.Add(_txtEntry)

 Controls.Add(New LiteralControl(" "))

 Controls.Add(_btnSubmit)

 Controls.Add(_lblErr)

End Sub

Figure 3: This subroutine is called from the OnInit event to initialize CAPTCHASP s constituent controls with their default values.

 

The first code block initializes an image tag that will be output in the page. This img tag will reference the CAPTCHA image generated by the control. This will be discussed more in the next section.

 

The rest of the code blocks are similar, initializing a label to display the instruction text, a textbox to hold the user s input, an error label that gets displayed only when the user enters an invalid code, and, finally, a submit button. The last code block of Figure 3 simply adds each control to the control collection of the base WebControl class. Appropriate spacing is inserted.

 

While the Init event triggers controls being created with their default values, the Render event is overridden to ensure customized (non-default) property values get rendered correctly, as shown in Figure 4.

 

Protected Overrides Sub Render(ByVal writer _

 As System.Web.UI.HtmlTextWriter)

   'Choose a codeword (if not already chosen)

   If Me.CodeWord.Length = 0 Then

     GenerateNewCodeWord()

   End If

   If Me.Controls.Count > 2 Then

     _imgMain.ToolTip = Me.ImageToolTip

     _lblInstruct.Text = Me.InstructionText

     _lblInstruct.ApplyStyle(Me.InstructionStyle)

     Me.TextEntryStyle.Width = _

         New Unit(75, UnitType.Percentage)

     _txtEntry.ApplyStyle(Me.TextEntryStyle)

     _btnSubmit.Text = Me.SubmitButtonText

     _btnSubmit.ApplyStyle(Me.ButtonStyle)

     _btnSubmit.Visible = Me.ShowSubmitButton

     _lblErr.Text = Me.ErrorMessage

     _lblErr.ApplyStyle(Me.FailMessageStyle)

     _lblErr.Visible = Me.ErrorMessage.Length > 3

   End If

   MyBase.Render(writer)

End Sub

Figure 4: The Render event is overridden to ensure non-default control settings are rendered appropriately.

 

The first code block of Figure 4 calls a function that randomly generates a new CAPTCHA CodeWord if one has not already been generated. Then customizable text, style, and visibility properties are applied to the constituent controls. Finally, the last line calls the base class Render method to ensure its functionality is executed normally.

 

Innovative Imaging

Custom Web controls that wish to render dynamic images have always been troublesome to create. This is because a control can typically output only HTML. So it s no problem to output an image tag in the HTML, but to what URL should it refer? Typically, such image-generating controls refer to a separate page or HTTP handler that generates the image, but this creates deployment problems because the user must remember to put that page in the right place or register that HTTP handler in their web.config file. If the user forgets or does it wrong, then things won t work and there is no easy way to help the user discover the problem. Sure, it can be documented that the user must take these extra steps, but not many people bother to read documentation. Most controls don t have requirements like this; instead, they work instantly as soon as the control is dropped on the page. Many users tend to move on if a control doesn t work right away, because they perceive the control to be broken or difficult to use.

 

Fellow MVP Oleg Tkachenko has documented an interesting technique for adding an HTTP handler reference to the web.config automatically at design time (http://www.tkachenko.com/blog/archives/000686.html), but this is complex code that only works if the control is viewed in the Visual Studio designer.

 

So I took another approach. Instead of referring to a separate page or HTTP handler, the image tag refers back to the page on which the CAPTCHASP component is hosted. The QueryString is replaced with a custom value, CAPTCHASP_Img=true, in this case. Figure 3 shows this URL alteration being done in the code block commented Create a quasi-callback URL . The first code block of the control s OnInit event (shown in Figure 2) reveals this request being intercepted. The result is that the OutputImage subroutine is called instead of the control s normal HTML output functionality. The OutputImage subroutine shown in Figure 5 clears the control s usual HTML output and replaces it with a custom-drawn CAPTCHA image.

 

'Write the CAPTCHA image to the output stream

'instead of the normal page/control contents

Protected Sub OutputImage()

 Page.Response.Clear()

 Page.Response.ContentType = "image/jpg"

 Using Memstr As MemoryStream = New MemoryStream()

   Using bmp As Bitmap = GenImage()

     bmp.Save(Memstr, Imaging.ImageFormat.Jpeg)

     Page.Response.BinaryWrite(Memstr.ToArray())

   End Using

 End Using

 Page.Response.End()

End Sub

Figure 5: The OutputImage subroutine clears the CAPTCHASP control s normal HTML output and instead outputs an image.

 

This fairly unorthodox approach for having a control create its own image avoids dependencies on any external pages or HTTP handlers, and allows it to work the instant it is first dropped on a Web form without any additional configuration being required. There is no way the control s configuration or deployment can be botched so badly that the control will fail to work.

 

This technique may not work so well for a more general image-generation control that is meant to be used on a variety of pages, because some pages may implement some strange URL functionality of their own that could potentially conflict. However, a control such as CAPTCHASP is likely to be used on a small number of relatively simple pages that generally don t do much else besides CAPTCHA verification so it should work well in virtually all realistic situations. Additionally, the fact that the page s rendering process is intercepted very early (in the OnInit event) helps ensure that any custom page code will not significantly interfere.

 

Any existing HTML that may have been written to the page s output is cleared by the first line of code in the OutputImage subroutine. Then the content type of the new output is specified to be a JPEG image. A memory stream is then created to hold the bitmap generated by the GenImage function listed in Figure 6. Finally, the image is rendered into the page s Response object using the BinaryWrite method and the response is then explicitly ended to ensure no other page content gets written out. No further page code is executed.

 

'Generate the CAPTCHA image

Protected Function GenImage() As Bitmap

 Dim Code As String = Me.CodeWord

 'Calculate character spacing

 Dim Rect As Rectangle = _

   New Rectangle(0, 0, _ImageWidth, _ImageHeight)

 _CharSpacing = CInt(((_ImageWidth - 20) / Code.Length))

 'Generate the image

 Using bmp As Bitmap = New Bitmap(Rect.Width, Rect.Height)

   Using g As Graphics = Graphics.FromImage(bmp)

     'Fill the image background with random coloring

     Dim IsDarkClr As Boolean = _

       Convert.ToBoolean(Int((2 * Rnd())))

     Dim BackClr As Brush = _

       GetRandBackgroundBrush(Rect, IsDarkClr)

     g.FillRectangle(BackClr, Rect)

     'Loop through and draw each character

     _CharCount = 0

     For Each ch As Char In Code

       'Render the character into the image

       DrawChar(g, Not IsDarkClr, BackClr, ch)

     Next

   End Using

   Return CType(bmp.Clone(), Bitmap)

 End Using

End Function

Figure 6: The GenImage function creates the actual CAPTCHA image that ultimately gets displayed.

 

The GenImage function first calculates the spacing that will be required for each character based on the length of the CodeWord and the size of the bitmap. A random brush is created to paint the background of the image. A gradient or patterned brush is randomly chosen with randomly selected colors to ensure a fairly unique image background is created each time. Then each character of the CodeWord is drawn on top of the background one by one. This character drawing is delegated to the DrawChar subroutine listed in Figure 7.

 

'Draw a character in the image

Protected Sub DrawChar(ByVal g As Graphics, _

ByVal UseDarkClr As Boolean, _

ByVal BackClr As Brush, _

ByVal Character As Char)

 'random font

 Dim fon As Font = GetRandFont()

 'random size

 Dim sz As SizeF = _

   g.MeasureString(Character.ToString(), fon)

 'random rotation

 Dim ypos As Integer = _

   CInt((_ImageHeight / 2) - (sz.Height / 2) - _

    (RandRotate(g, _CharCount) * 2))

 'Calculate character location

 Dim xpos As Integer = CInt(_CharSpacing * _

    (_CharCount) + (_CharSpacing / 3))

 Dim RectCh As Rectangle = _

   New Rectangle(xpos, ypos, _

   CInt(sz.Width), CInt(sz.Height))

 'Random text color

 Dim BrushClr As Brush = GetRandSolidBrush(UseDarkClr)

 'Draw the character

 g.DrawString(Character.ToString(), _

   fon, BrushClr, RectCh)

 'clean up

 UnRotate(g, BackClr)

 _CharCount += Convert.ToByte(1)

End Sub

Figure 7: The DrawChar subroutine draws each character of the CAPTCHA CodeWord with random fonts, sizes, colors, and rotations.

 

The DrawChar subroutine first chooses a random font with which to draw the character. The font is picked from a carefully crafted list of standard fonts to ensure compatibility. A random size is then chosen for the font and it is rotated to a random degree. The character s location is then calculated semi-randomly in relation to the other characters. Then a random text color is chosen before the character is finally drawn onto the image using the DrawString method of the Graphics object. The imported System.Drawing namespace is used extensively for all of this image-creation code.

 

Conclusion

With more than 1,200 lines of source code in the CAPTCHASP control, and only a limited amount of space here to examine it, there is no way to cover every interesting function contained within the control. However, we have examined the most innovative and unique elements of the control; enough, certainly, to get you started on a similar CAPTCHA control of your own if you should choose to embark on such a journey.

 

If you re an asp.netPRO subscriber, you can download the complete source code and probe every nook and cranny of CAPTCHASP. Even if you re not an asp.netPRO subscriber, you can still download the compiled control and use it for free. You can download CAPTCHASP or try it out live right now from the demo pages I ve assembled at http://SteveOrr.net/demo/CAPTCHASP. Now your Web site can be immune from bot infestations.

 

Sample code accompanying this article is available for download.

 

Steve C. Orr is an ASPInsider, MCSD, Certified ScrumMaster, Microsoft MVP in ASP.NET, and author of the book Beginning ASP.NET 2.0 AJAX by Wrox. He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he can often be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish