Home|Sitemap|Contact

How can you use images instead of characters for CAPTCHA?

My friend Oli suggests for a simpler method of preventing SPAM bots from posting to your Blog or forum, a simple version of the image CAPTCHA test may suffice.  He has a nice write up here.  However he incorrectly suggested classic ASP was not capable of streaming images without a third party image component.

Streaming images allows us to hide the true name and source of the images from any robot attempting to automatically pass our CAPTCHA test. I'm using files, you could use a database or another method.

This example simply demonstrates that classic ASP is quite capable of streaming images. I have also improved on his version by increasing the number of available image choices, providing easier to recognize images, and ensuring no image is used twice on a page.  For a character based CAPTCHA example in classic ASP (not using streaming) see this example.  For an example of reading files as an include with pure JavaScript without using an <IFRAME> or a .js extension, (another thing you supposedly can not do, see this article.)

Click the three pictures of "cats" to verify.

  • verification image
  • verification image
  • verification image
  • verification image
  • verification image
  • verification image
  • verification image
  • verification image
  • verification image

NOTE: All images are freely available content available from Wikimedia Commons.



<%
Option Explicit
Session.CodePage=65001
Response.Charset="UTF-8"

' no browser caching of this page
Response.Expires=-1
Response.ExpiresAbsolute = Now() - 1

' do not allow proxy servers to cache this page !! to be used on all pages
Response.CacheControl="private"
Response.CacheControl="no-cache"
Response.CacheControl="no-store"


' A temporary array used to get unique random values.
' i.e. we pick a random value, remove it from the array, and on
' subsequent random picks, we reject the value if already removed
' from the array.
Dim Unique(9)
Unique(1)=1
Unique(2)=2
Unique(3)=3
Unique(4)=4
Unique(5)=5
Unique(6)=6
Unique(7)=7
Unique(8)=8
Unique(9)=9

function getRndUnique
   Dim idx
   idx=0
   While (idx=0)
      idx = Int(8 * Rnd)+1
      if Unique(idx)>0 then
         idx = Unique(idx)
         Unique(idx)=0
      else
         idx=0
      end if
   Wend
   getRndUnique = idx
end function

function getCorrect
   ' choose three unique values for the image CAPTCHA
   ' the values will be comma separated in ascending order.
   Dim tmp, idx, out
   tmp = getRndUnique & "," & getRndUnique & "," & getRndUnique

   for idx=1 to 9
      if Unique(idx)=0 then
         out = out & idx & ","
      end if
   next
   getCorrect = Left(out, Len(out)-1)
end function

' another temporary array for getting unique images on each page
' with no repeated images
Dim UniqueImg(18)
UniqueImg(1)=1
UniqueImg(2)=2
UniqueImg(3)=3
UniqueImg(4)=4
UniqueImg(5)=5
UniqueImg(6)=6
UniqueImg(7)=7
UniqueImg(8)=8
UniqueImg(9)=9
UniqueImg(10)=10
UniqueImg(11)=11
UniqueImg(12)=12
UniqueImg(13)=13
UniqueImg(14)=14
UniqueImg(15)=15
UniqueImg(16)=16
UniqueImg(17)=17
UniqueImg(18)=18


function notUsed
   Dim idx
   for idx=1 to 18
      if UniqueImg(idx)>0 then
         notUsed = idx
         UniqueImg(idx)=0
         Exit For
      end if
   next
end function

function getRndUniqueImg
   Dim idx
   idx=0
   While (idx=0)
      idx = Int(18 * Rnd)+1
      if UniqueImg(idx)>0 then
         idx = UniqueImg(idx)
         UniqueImg(idx)=0
      else
         idx=notUsed
      end if
   Wend
   getRndUniqueImg = idx
end function

Randomize

' we use session variables so the image streaming page
' will know what image to return
Session("img1") = getRndUniqueImg
Session("img2") = getRndUniqueImg
Session("img3") = getRndUniqueImg
Session("img4") = getRndUniqueImg
Session("img5") = getRndUniqueImg
Session("img6") = getRndUniqueImg
Session("img7") = getRndUniqueImg
Session("img8") = getRndUniqueImg
Session("img9") = getRndUniqueImg

' The correct image choices will be the three numbers we put in correct.
Session("correct") = getCorrect
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="author" content="Roderick Divilbiss">
<meta name="copyright" content="© 2005, 2006 Roderick Divilbiss">
<meta name="MSSmartTagsPreventParsing" content="TRUE">
<title>Image Captcha :: Classic ASP Image Streaming</title>
<script type="text/javascript">
<!--
// to keep track of the image clicks
var clicked=new Array();
clicked[1]=false;
clicked[2]=false;
clicked[3]=false;
clicked[4]=false;
clicked[5]=false;
clicked[6]=false;
clicked[7]=false;
clicked[8]=false;
clicked[9]=false;

function updateClick() {
   // put the values which are clicked into our form
   var tmp='';
   for (var idx=1;idx<=9;idx++) {
     if (clicked[idx]) {
        tmp+=idx+',';
     }
   }
   if (tmp.substr(tmp.length-1,1)==',') {
      tmp = tmp.substr(0,tmp.length-1);
   }
   document.getElementById('value1').value=tmp;
}

function cMe(objI) {
   var idx=parseInt(objI.id.substr(3,1));
   if (objI.style.borderColor=='') {
      // never clicked
      objI.style.borderColor='red';
      clicked[idx]=true;
      updateClick();
   }else if (objI.style.borderColor=='black') {
      // clicked after being unclicked
      objI.style.borderColor='red';
      clicked[idx]=true;
      updateClick();
   } else {
      // unclicked
      objI.style.borderColor='black';
      clicked[idx]=false;
      updateClick();
   }
   // auto submit on three clicks
   // you could remove this and add a submit button
   if (document.getElementById('value1').value.length==5) {
      var myForm = document.getElementById('theForm');
      myForm.submit();
   }
}
//-->
</script>
<style type="text/css">
<!--
#container {
   width: 350px;
   height: 300px;
}

#iarray li {
   float:left;
   margin-right:10px;
   margin-bottom: 10px;
   list-style-type:none
}
.dynamicImg {
   border:solid 2px;
   border-color:black;
   border-style:solid;
}
-->
</style>
</head>

<body>
<!-- the container division allows us to set a width-->
<div id="container">
  <form id="theForm" name="theForm" method="post" action="dynamicImageVerify.asp">
    <ul id="iarray">
      <li><img class="dynamicImg" id="img1"
           src="streamImage.asp?c=1"
           alt="verification image"
           onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img2"
           src="streamImage.asp?c=2"
           alt="verification image"
           onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img3"
           src="streamImage.asp?c=3"
           alt="verification image"
           onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img4"
           src="streamImage.asp?c=4"
           alt="verification image"
           onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img5"
          src="streamImage.asp?c=5"
          alt="verification image"
          onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img6"
          src="streamImage.asp?c=6"
          alt="verification image"
          onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img7"
          src="streamImage.asp?c=7"
          alt="verification image"
          onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img8"
          src="streamImage.asp?c=8"
          alt="verification image"
          onclick="cMe(this);" width="75" height="75"></li>
      <li><img class="dynamicImg" id="img9"
           src="streamImage.asp?c=9"
           alt="verification image"
           onclick="cMe(this);" width="75" height="75"></li>
    </ul>
    <p><input type="hidden" id="value1" name="value1" size="20"></p>
  </form>
</div>
</body>

</html>


IMAGE STREAMER (streamImage.asp)
<%
Option Explicit
Dim img, idx, param, correct
correct = Session("correct")
param = Request.QueryString("c")

' note the image URL on the form has a parameter
' which has no association with which image is streamed
' and the parameter is only valid for a particular session.

' I am choosing three of eighteen possible cats. You could
' make this exceedingly large if you were worried about
' harvesting...which I am not. A Blog is not Fort Knox.
Dim validImage(18)
validImage(0) = "NotAvailable.jpg"
validImage(1) = "cat1.jpg"
validImage(2) = "cat2.jpg"
validImage(3) = "cat3.jpg"
validImage(4) = "cat4.jpg"
validImage(5) = "cat5.jpg"
validImage(6) = "cat6.jpg"
validImage(7) = "cat7.jpg"
validImage(8) = "cat8.jpg"
validImage(9) = "cat9.jpg"
validImage(10) = "cat10.jpg"
validImage(11) = "cat11.jpg"
validImage(12) = "cat12.jpg"
validImage(13) = "cat13.jpg"
validImage(14) = "cat14.jpg"
validImage(15) = "cat15.jpg"
validImage(16) = "cat16.jpg"
validImage(17) = "cat17.jpg"
validImage(18) = "cat18.jpg"


Dim otherImage(18)
otherImage(0) = "NotAvailable.jpg"
otherImage(1) = "not18.jpg"
otherImage(2) = "not2.jpg"
otherImage(3) = "not16.jpg"
otherImage(4) = "not4.jpg"
otherImage(5) = "not14.jpg"
otherImage(6) = "not6.jpg"
otherImage(7) = "not7.jpg"
otherImage(8) = "not8.jpg"
otherImage(9) = "not9.jpg"
otherImage(10) = "not10.jpg"
otherImage(11) = "not11.jpg"
otherImage(12) = "not12.jpg"
otherImage(13) = "not13.jpg"
otherImage(14) = "not5.jpg"
otherImage(15) = "not15.jpg"
otherImage(16) = "not3.jpg"
otherImage(17) = "not17.jpg"
otherImage(18) = "not1.jpg"

function inCorrect(num)
   Dim tmp, out
   out = false
   tmp = Split(Session("correct"),",")
   for idx = 0 to UBound(tmp)
      if CInt(tmp(idx))=CInt(num) then
         out = true
      end if
   next
   inCorrect = out
end function

' A little protection against calling the page without
' coming from the correct image CAPTCHA page.
' e.g. attempt to stop harvesting of images or
' associating any image with a URL.
if param & "x" = "x" then
   img = "NotAvailable.jpg"
else
   idx = Session("img" & param)
   if (idx < 1) or (idx="") or (idx & "x"="x") then
      img = "NotAvailable.jpg"
   else
      if InStr(correct, param)>0 then
         img = validImage(idx)
         validImage(idx)="NotAvailable.jpg"
         Session("img" & param)=0
      else
         img = otherImage(idx)
      end if
   end if
end if

Dim objStream
Set objStream = Server.CreateObject("ADODB.Stream")

'Open a image file
objStream.Type = 1 ' adTypeBinary
objStream.Open
objStream.LoadFromFile Server.MapPath(img)

'Output the contents of the stream object
'Response.ContentType = "image/gif"
Response.ContentType = "image/jpeg"
Response.BinaryWrite objStream.Read

objStream.close
Set objStream=nothing
%>