A Better CAPTCHA Control (Look Ma - NO IMAGE!)

By Peter Bromberg

Peter takes a previous concept and applies it to the real world of "Really Useful" applications - A Better CAPTCHA!

We live in the Age of the Internet. It's a technologically - driven era in society where information flows more freely, is easier to access and find, and has more influence on the speed of development of the totality of the experience of being human and living human lives, than at any time in known history.

Along with that freedom and flow of information comes SPAM -- Blog SPAM, newsgroup SPAM, email SPAM. And so the CAPTCHA (an initialism for "Completely Automated Public Turing test to tell Computers and Humans Apart", trademarked by Carnegie Mellon University) came into play. CAPTCHA is a type of of challenge-response test used in computing to determine whether or not the user is human. The most common form of CAPTCHA requires that the user correctly type in the characters shown on an image, usually an obscured sequence of letters or digits that appears on the screen.

Unfortunately, this image-based CAPTCHA technique, which has become almost ubiquitous on blogs, newsgroups, forums and any other form of Internet community publication, is severely flawed. In order to prevent BOTS (automated spam mechanisms) from performing OCR on the characters in the displayed image, the CAPTCHA characters are often so deliberately obscured by lines, text warp effects and other visual "noise" that even we, the real humans, cannot read them correctly. I have good vision, and six times out of ten, I get the required input for a CAPTCHA challenge wrong on the first try. Not only is this inefficient, it is damned frustrating and it really puts a dent in the user experience. I doubt that many will disagree with this statement.  Can you imagine the difficulty someone with impaired vision will have? Yes, I know, you will tell me that there are CAPTCHAs that use audio, and so on. That isn't the point.

It is not necessary to put up with this. While there have been numerous "experiments" in offering "different kinds" of CAPTCHA Tests - e.g., selecting the "prettiest girl" from a group of images, using "smart questions", etc. they haven't really taken hold, and we, the poor Internet Users, must suffer the slings and arrows of CAPTCHA Bipolar Disorder just to leave a comment on our buddy's Blog. Yecch! We can do better.

I have an idea that offers an interesting alternative. If images are so susceptible to being Optical Character Recognized by bots, why display an image at all? That's right, FORGET ABOUT IMAGES.

In an earlier article, I featured a class that would turn an image into carefully CSS styled photorealistic HTML. Why not just apply this same technique to a CAPTCHA image with random text, and convert it to HTML, and display that instead? We can dispense with the visual noise, since there is no longer any image to OCR! That means it will be very easy to read, and still accomplish the objective of easily proving the user is human. Bye bye, Mr. BOT -- you now have NOTHING to defeat; we've just chopped your legs off! Sure, you can grab the HTML, but the only way you could possibly find out what the characters are is to have a human render it and read it!  (Of course, some enterprising soul may come up with a scheme to "recognize" the HTML instead, but we will be one step ahead of them with unannounced innovations, so fear not!)

In actual fact, you "could" still OCR this. You would have to grab the HTML, render it, take a screen cap of it into an image, and then finally you could perform OCR on the image.  But, that's highly unlikely to happen anytime soon, if you just think about it.

So, here's  what I did in order to create this, which  is very simple:

1) I took the best of some other people's "pretty good" ASP.NET CAPTCHA image controls that were open source or freeware, and
2) I made a new control that still creates the image, but it runs it through my Image2Html class and returns the string of styled HTML that "looks just like" the original image, and displays that instead. Everything else works exactly the same.

The difference is that now that we aren't using images any longer, we don't need to obfuscate them to the point where the poor user has to go blind figuring out what the damned text is that they need to enter! In fact, we don't need to obfuscate them AT ALL. 

The typical "size" of an HTML version of an image is somewhere in the range of 67K - quite a manageable amount of HMTL and not much bigger than a lot of images that people put in web pages anyway. In addition, since it's inline HTML right in the page, it does not require a separate HTTP request from the browser to get the image.

Here is  my Image2Html class, in case you missed the original article:

using System;
using System.Text;
using System.IO;
using System.Web;
using System.Net;
using System.Drawing ;
namespace PAB.Web.Utils
{    
    public class Image2Html
    {
        private Image2Html()
        {            
        }

        public static string ConvertImage(Bitmap img, int scale)
        {
             
    
            Bitmap b = img;
            MemoryStream ms = new MemoryStream();
            StreamWriter SW = new StreamWriter(ms);
        
            SW.WriteLine("<style>pre{letter-spacing:-4px;word-spacing:-4px;line-height:2px}</style>");
            SW.WriteLine("<pre><b><font size='1pt'>");
            string s2 = "";
            for (int y = 0; y < b.Height; y += scale)
            {
                for (int x = 0; x < b.Width; x += scale)
                {
                    Color c = b.GetPixel(x, y);
                     s2 = c.Name.Substring(2);
                    SW.Write("<font color='#" + s2 + "'>");
                    SW.Write(((byte)c.ToArgb()) >> 7);
                    SW.Write("</font>");
                }
                SW.WriteLine();
            }

            SW.WriteLine("</font></b></pre>");
           
            SW.Close();
            SW = null;
            byte[] b2 = ms.ToArray();
            string s = System.Text.Encoding.ASCII.GetString(b2);
            return s;
        }
    }
}


Here is a simple screen cap illustrating one layout of the control:



The CAPTCHA numbers and letters you see, which are quite easy to read, are NOT an image- they are HTML.

And here is the full Visual Studio 2005 solution with the control and a sample web page, which I am putting in the public domain. The control also features a settable timeout to help defeat those "human bot shops". Zero for the timeout means "no timeout". Please feel free to modify it, change it, make it "your own" and help me get rid of those AWFUL Image - based CAPTCHAS! Thank you!

NOTE: Look at Christopher Lewis' comment below for a nice enhancement that cuts way down on the amount of generated HTML by only changing the pixel color if it is different from the previous one.

Popularity  (8535 Views)
Picture
Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Follow Microsoft MVP
Create New Account
Article Discussion: A Better CAPTCHA Control (Look Ma - NO IMAGE!)
Peter Bromberg posted at Thursday, January 18, 2007 9:38 PM
reply
Nice solution
Wim Okiedokie replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
Hi Peter,

Thanks for sharing this solution, I totally agree with you that current CAPTCHA controls are poorly readable and therefore creates frustrations on places where you are trying to give a good service to people without a risk of spam.

However I questioning the safety of your control,  is it not so that I can automated copy the HTML code and still get the correct code to validate a request?

Regards Wim

reply
I think you are missing the point--
Peter Bromberg replied to Wim Okiedokie at Thursday, January 18, 2007 9:38 PM

Any HUMAN can copy the HTML code out of the page and look at it, just as they can look at the image of a CAPTCHA (without being converted to an HTML representation).

The whole point of a CAPTCHA is to defeat NON-HUMANS. If you were a BOT, how would you propose to "recognize" the characters when there is no image to perform Optical Character Recognition on?

reply
Scrambling
Nicholas Bott replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
Peter,

It seems to me that if this were a popular technique it would become easy for a bot to recognize the "image" representation of the html dump - I can think of several ways to do this.

I think this is an innovative solution; but, it might be better to scramble it a bit more, by using CSS that has a randomly generated class name, as opposed to placing the color representation inline.  It might also be good to generate it by having several "pieces" of the HTML used in the dump that are rendered in a final layer via javascript - making locating signatures less feasible.

2 cents :)
reply
Have you thought about using ASCII graphics?
adrian c replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM

Have you thought about using 'ASCII graphics' i.e. generate big letters made of small ASCII characters, like this (copy and paste into a fixed font editor to see it properly):

!||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||
||||||||||||||||oO|||||||||||||||
|||||||||||||||oOoO||||||||||||||
||||||||||||||oOooOo|||||||||||||
|||||||||||||oOo|oOo|||||||||||||
||||||||||||oOo|||oOo||||||||||||
|||||||||||oOo|||||oOo|||||||||||
||||||||||oOo|||||||oOo||||||||||
|||||||||oOo|||||||||oOo|||||||||
||||||||oOo|||||||||||oOo||||||||
|||||||oOoOoOoOoOoOoOoOoOo|||||||
||||||oOoOoOoOoOoOoOoOoOoOo||||||
|||||oOo|||||||||||||||||oOo|||||
||||oOo|||||||||||||||||||oOo||||
|||oOo|||||||||||||||||||||oOo|||
|||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||

Adrian

reply
RE: Have you thought about...
Peter Bromberg replied to adrian c at Thursday, January 18, 2007 9:38 PM
No.
reply
Slight change to ConvertImage to greatly reduce size of page
Christopher Lewis replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM

Hi -

Just looking at the source of this HTML made me cringe.

Here's a slight change to the ConvertImage method of Image2Html.CS that greatly reduces the size of the HTML:

public static string ConvertImage(Bitmap img, int scale)
{
  Bitmap b = img;
  MemoryStream ms = new MemoryStream();
  StreamWriter SW = new StreamWriter(ms);

  SW.WriteLine("<style>pre{letter-spacing:-4px;word-spacing:-4px;line-height:2px}</style>");
  SW.WriteLine("<pre><b><font size='1pt'>");
  string s2 = "";
  for (int y = 0 ; y < b.Height ; y += scale) {
    string sLast2 = "";
    for (int x = 0 ; x < b.Width ; x += scale) {
      Color c = b.GetPixel(x, y);
      s2 = c.Name.Substring(2);
      //Write a new font tag if we're a new color
      if (string.Compare(s2, sLast2) != 0) {
        //Don't write closing Font tag for first item
        if (sLast2.Length > 0) {
          SW.Write("</font>");
        }
        SW.Write("<font color='#" + s2 + "'>");
        //Remember the current color
        sLast2 = s2;
      } 
      SW.Write(((byte) c.ToArgb()) >> 7);
    }
    //Always end with </font>
    SW.Write("</font>");
    SW.WriteLine();
  }

  SW.WriteLine("</font></b></pre>");

  SW.Close();
  SW = null;
  byte[] b2 = ms.ToArray();
  string s = System.Text.Encoding.ASCII.GetString(b2);
  return s;
}

Basically, the inner loop only writes out a new <Font Color> tag if the color changes.  Spot checking, it reduced the HTML to less then a third of the original code.

 

reply
Very Nice!
Peter Bromberg replied to Christopher Lewis at Thursday, January 18, 2007 9:38 PM
Certainly is refreshing to see people with above-room temperature IQ's. Thanks for this.
reply
Accessibility an issue?
Grady Werner replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM

Very nice indeed.  My only concern is what may happen with screen readers.  I can just imagine the poor soul that is listening to the page and starts hearing: oneoneoneoneoneoneoneonezerozerooneoneoneone...

A nice extension of this may be to add a CSS definition to the element telling screen readers to not bother reading it out loud, and maybe an alternate rendering for them (audio, for example).

reply
An image by any other encoding is still an image
Brian Katzung replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
If this encoding became prevalent and I were a bot designed to defeat it, I would scan my target pages for long sequences of <font color='#rrggbb'>...</font>. Then I would assemble my own image with the specified pixel colors and then OCR it.

You have not eliminated the image, you have just replaced an external binary encoding with a much less efficient in-line HTML encoding.
reply
you are absolutely right.
Peter Bromberg replied to Brian Katzung at Thursday, January 18, 2007 9:38 PM

And how many bot writers do YOU know who would go through all that pain just to spam somebody's blog?

 

reply
bot writers and pain
Brian Katzung replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
From what I've seen, there seem to be way too many people with nothing more constructive or beneficial to do. As far as I know, I don't know any of them.

As for pain, it only took me about 30 non-comment lines of perl to download a URL and convert the first HTML-ized image to a PNG. The parsing and conversion makes up about half of that. A bunch of the rest is closing braces.

The most difficult part was tracking trashed images to an inverted conditional. The second most difficult was learning the GD::Image module which I had not used before. Neither one was that difficult.

There is some promise here, and possible alternative applications as well (e.g. image in-lining), but I agree with Nicholas.

For a perl spin on the topic, see also: http://search.cpan.org/~lgoddard/Image-ThousandWords-0.08/ThousandWords.pm
reply
What about...
Tim Dietz replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM

Pardon my ignorance, for I am new to this subject, but if you're only interested in foiling bots, why don't you just display a series of characters or numbers and ask a question about it?  Like:

2T47H5Z - How many even numbers are there?

or, using the same sequence:  What is the sum of the numbers?

Would something like that work?

 

reply
brilliant
dan matthews replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
What a fantastically smart idea - I certainly do find conventional captcha controls nigh-on impossible to validate! bravo to you Peter.
reply
xhtml compat
Nenad Golubovic replied to dan matthews at Thursday, January 18, 2007 9:38 PM
I've made some change to make this xhtml compatible.
code goes here :

image2html.cs

public static string ConvertImage(Bitmap img, int scale)
        {
            Bitmap b = img;
            MemoryStream ms = new MemoryStream();
            StreamWriter SW = new StreamWriter(ms);

            SW.WriteLine("<span id=\"pre\" style=\"letter-spacing:1px; word-spacing:1px; line-height:2px;\" >");
            SW.WriteLine("<b><span style=\"font-size: 4px; \">");
            string s2 = "";
            for (int y = 0; y < b.Height; y += scale)
            {
                string sLast2 = "";
                for (int x = 0; x < b.Width; x += scale)
                {
                    Color c = b.GetPixel(x, y);
                    s2 = c.Name.Substring(2);
                    //Write a new font tag if we're a new color
                    if (string.Compare(s2, sLast2) != 0)
                    {
                        //Don't write closing Font tag for first item
                        if (sLast2.Length > 0)
                        {
                            SW.Write("</span>");
                        }
                        SW.Write("<span style=\"font-color: #" + s2 + " ;\">");
                        //Remember the current color
                        sLast2 = s2;
                    }
                    SW.Write(((byte)c.ToArgb()) >> 7);
                }
                //Always end with </font>
                SW.Write("</span>");
                SW.WriteLine();
            }

            SW.WriteLine("</span></b></span>");

            SW.Close();
            SW = null;
            byte[] b2 = ms.ToArray();
            string s = System.Text.Encoding.ASCII.GetString(b2);
            return s;
        }
---
and HtmlCaptchaControl.cs like here :

private string CssStyle()
        {
            StringBuilder builder1 = new StringBuilder();
            string text1 = null;
            builder1.Append(" style=\"");
            if (this.BorderWidth.ToString().Length > 0)
            {
                builder1.Append("border-width:");
                builder1.Append(this.BorderWidth.ToString());
                builder1.Append(";");
            }
            if (this.BorderStyle != BorderStyle.NotSet)
            {
                builder1.Append("border-style:");
                builder1.Append(this.BorderStyle.ToString());
                builder1.Append(";");
            }
            text1 = this.HtmlColor(this.BorderColor);
            if (text1.Length > 0)
            {
                builder1.Append("border-color:");
                builder1.Append(text1);
                builder1.Append(";");
            }
            text1 = this.HtmlColor(this.BackColor);
            if (text1.Length > 0)
            {
                builder1.Append("background-color:" + text1 + ";");
            }
            text1 = this.HtmlColor(this.ForeColor);
            if (text1.Length > 0)
            {
                builder1.Append("color:" + text1 + ";");
            }
            if (this.Font.Bold)
            {
                builder1.Append("font-weight:bold;");
            }
            if (this.Font.Italic)
            {
                builder1.Append("font-style:italic;");
            }
            if (this.Font.Underline)
            {
                builder1.Append("text-decoration:underline;");
            }
            if (this.Font.Strikeout)
            {
                builder1.Append("text-decoration:line-through;");
            }
            if (this.Font.Overline)
            {
                builder1.Append("text-decoration:overline;");
            }
            if (this.Font.Size.ToString().Length > 0)
            {
                builder1.Append("font-size:" + this.Font.Size.ToString() + ";");
            }
            if (this.Font.Names.Length > 0)
            {
                builder1.Append("font-family:");
                foreach (string text2 in this.Font.Names)
                {
                    builder1.Append(text2);
                    builder1.Append(",");
                }
                builder1.Length--;
                builder1.Append(";");
            }
            if (this.Height.ToString() != "")
            {
                builder1.Append("height:" + this.Height.ToString() + ";");
            }
            if (this.Width.ToString() != "")
            {
                builder1.Append("width:" + this.Width.ToString() + ";");
            }
            builder1.Append("\"");
            if (builder1.ToString() == " style=\"\"")
            {
                return "";
            }
            return builder1.ToString();
        }

        private void GenerateNewCaptcha()
        {
            this.LocalGuid = Guid.NewGuid().ToString();
         
            if (!this.DesignMode)
            {
                HttpContext.Current.Cache.Add(this.LocalGuid, this._captcha, null, DateTime.Now.AddMinutes((double) HttpContext.Current.Session.Timeout), TimeSpan.Zero, CacheItemPriority.Normal, null);
                HttpContext.Current.Cache["guid"] = this.LocalGuid;
            }
            this.CaptchaText = this._captcha.Text;
            this.GeneratedAt = DateTime.Now;
        }

        private string HtmlColor(Color color)
        {
            if (color.IsEmpty)
            {
                return "";
            }
            if (color.IsNamedColor)
            {
                return color.ToKnownColor().ToString();
            }
            if (color.IsSystemColor)
            {
                return color.ToString();
            }
            int num1 = color.ToArgb();
            return ("#" + num1.ToString("x").Substring(2));
        }

        public bool LoadPostData(string PostDataKey, NameValueCollection Values)
        {
            // we have a postback - this is where we validate user entry
            this.ValidateCaptcha(Convert.ToString(Values[this.UniqueID]));
            // generate a new one and store the value for a subsequent try
            this.GenerateNewCaptcha();
            return false;
        }

        protected override void OnPreRender(EventArgs E)
        {
            if (this.LocalGuid == "")
            {
                this.GenerateNewCaptcha();
            }
        }

        public void RaisePostDataChangedEvent()
        {
        }

        protected override void Render(HtmlTextWriter Output)
        {
            Output.Write("<div");
            if (this.CssClass != "")
            {
                Output.Write(" class=\"" + this.CssClass + "\"");
            }
            Output.Write(this.CssStyle());
            Output.Write(">");
            if (this.LayoutStyle == HtmlCaptcha.CaptchaControl.Layout.Vertical)
            {
                Output.Write("<div style=\"text-align:center;margin:5px;\">");
            }
            else
            {
                Output.Write("<span style=\"margin:5px;float:left;\">");
            }

            CaptchaImage img = new CaptchaImage();
            img.GenerateImage();
            // convert what would normally be a CAPTCHA image to pure HTML...
            string s = PAB.Web.Utils.Image2Html.ConvertImage(this._captcha.Image,2);
            Output.Write(s);       
         
          
            
            if (this.LayoutStyle == HtmlCaptcha.CaptchaControl.Layout.Vertical)
            {
                Output.Write("</div>");
            }
            else
            {
                Output.Write("</span>");
            }
            if (this.LayoutStyle == HtmlCaptcha.CaptchaControl.Layout.Vertical)
            {
                Output.Write("<div style=\"text-align:center;margin:5px;\">");
            }
            else
            {
                Output.Write("<span style=\"margin:5px;float:left;\">");
            }
            if (this._strText.Length > 0)
            {
                Output.Write(this._strText);
                Output.Write("<br />&nbsp;");
            }
            Output.Write("<input name=\"" + this.UniqueID + "\"" + " type=\"text\" size=\"");
            Output.Write(this._captcha.TextLength.ToString() + "\"");
            Output.Write(" maxlength=\"");
            Output.Write(this._captcha.TextLength.ToString() +"\"");
            if (this.AccessKey.Length > 0)
            {
                Output.Write(" accesskey=\"" + this.AccessKey + "\"");
            }
            if (!this.Enabled)
            {
                Output.Write(" disabled=\"disabled\"");
            }
            if (this.TabIndex > 0)
            {
                Output.Write(" tabindex=" + this.TabIndex.ToString() + "\"");
            }
            Output.Write(" value=\"\" />");
            if (this._blnShowSubmitButton)
            {
                Output.Write("&nbsp;");
                Output.Write("<input type=\"submit\" value=\"submit\"");
                if (!this.Enabled)
                {
                    Output.Write(" disabled=\"disabled\"");
                }
                if (this.TabIndex > 0)
                {
                    Output.Write(" tabindex=\"" + this.TabIndex.ToString() + "\"");
                }
                Output.Write("/>");
            }
            if (this.LayoutStyle == HtmlCaptcha.CaptchaControl.Layout.Vertical)
            {
                Output.Write("</div>");
            }
            else
            {
                Output.Write("</span>");
                Output.Write("<br clear=\"all\" />");
            }
            Output.Write("</div>");
        }

...

however, now captcha looks a bit different :

http://www.vado.ba/upload/captcha.jpg

...
it is functional, but i'm concerned about captcha preview....
greeting

great job Peter!
reply
Quick fix that worked
Tom Regan replied to Nenad Golubovic at Thursday, January 18, 2007 9:38 PM

Three web sites I managed suddenly started getting bombarded by bots, and I had no time that day to add a proper captcha control, so as a quick fix I added the question "What is 1 + 1" with a text box for the user to enter "2".  I slapped in a .Net required field validator with the message, "you need to enter '2' here".

It has worked flawlessly.  In 6 months I've not had a single bot post on all 3 sites.

reply
xhtml transitional valid
Peter Strömblad replied to Nenad Golubovic at Thursday, January 18, 2007 9:38 PM

Thanks for the code Nenad - and Peter for this excellent Captcha control.

I modified ConvertImage so the rendered text-image is more similar to the original, yet fully valid and similar in all browsers (tested at browsershots.org).

Here is the code:

public static string ConvertImage(Bitmap img, int scale)
    {
      Bitmap b = img;
      MemoryStream ms = new MemoryStream();
      StreamWriter SW = new StreamWriter(ms);

      SW.WriteLine("<pre>");
      SW.WriteLine("<span style=\"letter-spacing:1px;word-spacing:1px;line-height:2px; font-weight:bold; font-size:2px; \">");
      string s2 = "";
      for (int y = 0; y < b.Height; y += scale)
      {
        string sLast2 = "";
        for (int x = 0; x < b.Width; x += scale)
        {
          Color c = b.GetPixel(x, y);
          s2 = c.Name.Substring(2);
          //Write a new font tag if we're a new color
          if (string.Compare(s2, sLast2) != 0)
          {
            //Don't write closing Font tag for first item
            if (sLast2.Length > 0)
            {
              SW.Write("</span>");
            }
            SW.Write("<span style=\"color: #" + s2 + ";\">");
            //Remember the current color
            sLast2 = s2;
          }
          SW.Write(((byte)c.ToArgb()) >> 7);
        }
        //Always end with </font>
        SW.Write("</span>");
        SW.WriteLine();
      }
      SW.WriteLine("</span>");
      SW.WriteLine("</pre>");

      SW.Close();
      SW = null;
      byte[] b2 = ms.ToArray();
      string s = System.Text.Encoding.ASCII.GetString(b2);
      return s;
    }
reply
great work
Nenad Golubovic replied to Peter Strömblad at Thursday, January 18, 2007 9:38 PM
this is just great :) ... thanks
reply
Why use letters at all?
Richard Hamilton replied to Nenad Golubovic at Thursday, January 18, 2007 9:38 PM

I think we should move away from the concept of 'Letters' than machines can understand to something that only a child could understand.

we could have little pics of animals!

 

reply
null reference exception in ValidateCaptcha fixed
Peter Strömblad replied to Nenad Golubovic at Thursday, January 18, 2007 9:38 PM

When a form takes really long to fill out the cache can be cleared. Then the captcha validation fails with a null reference exception. This alteration takes care of a refresh.

 

private bool ValidateCaptcha(string strUserEntry)
    {
      string g = (string)HttpContext.Current.Cache["guid"];
      if (!String.IsNullOrEmpty(g)) {
        CaptchaImage ci = (CaptchaImage)HttpContext.Current.Cache[g.ToString()]; // B: 20090119, null reference exception when cache has been cleared
        string strCaptchaText = ci.Text;
        if (string.Compare(strUserEntry, strCaptchaText, false) == 0)
        {
          if (this.CaptchaTimeout == 0)
          {
            this._blnUserValidated = true;
          }
          else
          {
            this._blnUserValidated = this.GeneratedAt.AddSeconds((double)this.CaptchaTimeout) > DateTime.Now;
          }
        }
        else
        {
          this._blnUserValidated = false;
        }
        if (this.UserValidationEvent != null)
        {
          this.UserValidationEvent(this._blnUserValidated);
        }
        }
      // return this._blnUserValidated;
      return false; // always return false, uservalidated is a public property
    }
reply
Copyright
marko losh replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
Great idea,
Can I use this in commercial application?
reply
Yup that works
Robert Daniels replied to Tim Dietz at Thursday, January 18, 2007 9:38 PM

I've already run into sites that are not using the lame captcha method with things like:

What sounds does a cow make? :
What kind of bird flies at night? :
If you add 2 to the number 4, the answer is :
What is Barney Rubble's first name? :



reply
Very Nice.
Derrick Simpson replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
I am aware that it has been some time since anyone replied here, but I really like this. 

I think we can make the generated html even smaller by using smaller tags, such as "p", "i", or "b".  Also, we can generating a corresponding css document, so that colors are represented by classes which are based on the tags... 

We really only need two colors, white / black, so we can do it something like this.

<style>
.cp > b{
display:block;
width:1px;
height:1px;
background-color:#000;
}

.cp > i {
display:block;
width:1px;
height:1px;
background-color:#FFF;
}
</style>

<div class="cp">
 <i></i><i></i><b></b><i></i><i></i>
</div>


This would generate a 5 pixel wide "image", with 2 white pixels on each side of a black one.

I could be wrong, but we may be able to write the tags such as <i/><i/><b/><i/><i/> as well.
reply
Anthony Grace replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
In the sample project, I set ShowSubmitButton to false and incorporated it into my project to use with my contact form. I wanted my own contact submit button to cause the validation check but it allows the form to be submitted without and CAPTCHA input. I got the sample project working fine but do not want two subit buttons on a form. What am I doing wrong?

reply
Anthony Grace replied to Anthony Grace at Thursday, January 18, 2007 9:38 PM
Never mind... tweaked my code-behind which was toggling panel visibilities and added the CAPTCHA check in the apropriate place and it works fine! From a usability point of view I'm thinking of setting the timeout to zero...
reply
Mike replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
> In actual fact, you "could" still OCR this. You would have to grab the HTML, render it, take a screen cap of it into an image, and then finally you could perform OCR on the image.  But, that's highly unlikely to happen anytime soon, if you just think about it.

Um. wkhtmltopdf uses webkit to create a PDF from a web page. It even handles css, images, javascript, and flash. You can then use ImageMagick to convert it from a pdf to a png or whatever you need. It's utterly trivial. I can generate an image of any webpage simply like this:

wkhtmltopdf http://example.com/ output.pdf
convert output.pdf output.png

Or I can just pull out the snippet of html containing the captcha and do the same thing. If the text in the image isn't appropriately obfuscated I can then just run gocr or similar against it. Or send the image off to a captcha farm...
reply
Peter Bromberg replied to Mike at Thursday, January 18, 2007 9:38 PM
Any captcha can be "cracked". It all depends on how sophisticated you want to get. What we're trying to do here is create a captcha that is user friendly and still functional, no?
reply
Mike replied to Peter Bromberg at Thursday, January 18, 2007 9:38 PM
My point was, it's probably a lot easier to crack than your average obfuscated image. It's only a tiny bit more difficult to crack than a non-obfuscated image, which is in it's self trivial.

If you just want to stop random bots that are scanning for forms to fill in, there are various solutions that require zero user interaction. If you want to stop a targetted attack, you need something more powerful than the technique you've described.
reply