用XBM图片做验证码

2007年3月27日 by comehope    Tags: XBM  验证码  ASP  

 (下载本文的相关代码

为什么需要验证码

  为防止某个黑客对某一个特定注册用户,用特定程序暴力破解方式进行不断的登陆尝试,即防止暴力注册/穷取密码/服务器拒绝等可能影响网站正常运行的行为。验证码的运行机制是在需要用户通过填写表单与服务器交互时,在随机产生验证码的同时生成了一Session,然后通过表单递交数据与Session中保存的验证码进行比较,正确即验证码通过,否则出错提示。为了保证以上效果,验证码应该是用户能读懂的信息,该信息不能通过非法计算机程序的分析进行解读。这样才可以保证每次表单的提交都是真实用户的操作,而不是计算机程序模拟的行为。


XBM介绍

  X-Bitmap(XBM)是一种古老但通用的图像文件格式,它与现在的许多Web浏览器都兼容。X-Windows图形界面(UNIX和Linux常用的GUI)的C代码库xlib中有一个组件专门描述了它的规范。XBM格式本来是为存储单色的系统位图而设计的,比如图标和鼠标指针。
  这里你也许会问:这种文件格式与Web浏览器有什么关系?在上世纪九十年代早期,美国超级计算应用中心(NCSA)在伊利诺斯大学开发第一个被广泛使用的Web浏览器,名为Mosaic。这个浏览器的图形支持来自很多开放源码代码库,其中就包括xlib。因此,导致今天的许多浏览器能够处理XBM图形。Mosaic项目后来成为了Netscape浏览器的开发基础。微软也借用了一部分Mosaic代码来创建Internet Explorer。微软继而在网络信息服务器(IIS)中将XBM作为一个MIME类型注册而提供本地支持,并且在现有所有版本的Internet Explorer中将其作为一种可支持的图像。
  X-Bitmap的实用程序包括动态生成的图、页面计数器、老式图形图标、以及统计图表。


XBM格式剖析

  从一个程序员的角度来看,JPEG或GIF与XBM有着极大的不同。前两种文件格式都可以支持很大的颜色深度范围;XBM图形被限制为两色(黑色和白色)。这主要是因为它们的文件结构不同,前两种文件是真正的纯二进制文件,需要通过服务器端脚本来创建;而XBM图形是ASCII码文件,换句话说,它是一个纯文本文件,所以通过客户端脚本也可以创建。

  下面是一个笑脸的点阵图:

  这个图形宽16个像素高7个像素。下面用二进制数值来描述该图像:

  0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0
  0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0
  0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0
  0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0

  图像按每八位一组进行十六进制编码,但是注意,它们是反向计算的(从左到右)而不是从右到左。把它颠倒是因为浏览器会从左到右来读图形,我们的代码必须与其一致。所以XBM二/十六进制转换表与标准的二/十六进制转换表不一样。

 

  根据这个转换表,可以得到这个图像的十六进制编码:

  18, 06,
  18, 06,
  00, 00,
  00, 00,
  04, 10,
  08, 08,
  f0, 07

  在源代码的头部定义该图形的宽/高像素值,再写入图形的编码,就是这个图像完整的源代码了:

  #define xbmsmile_width 16
  #define xbmsmile_height 7
  static unsigned char xbmsmile_bits[] = {18, 06, 18, 06, 00, 00, 00, 00, 04, 10, 08, 08, f0, 07};

  在每个十六进制值前加“0x”,这是标准C++表示十六进制的方法,那么上面的源代码也可以写成:

  #define xbmsmile_width 16
  #define xbmsmile_height 7
  static unsigned char xbmsmile_bits[] = { 0x18, 0x06, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x08, 0x08, 0xf0, 0x07 };

  将以上代码保存为名为smill.xbm的纯文本文件,通过使用IMG标签将XBM文件嵌入到一个Web页面中。其语法如下:
 <img src=”xbmsmill.xbm”>
  用浏览器打开包含上面IMG标签的页面,将会看到一个笑脸符号,如下图:


 

生成XBM验证码

  一共三个文件:
  numcode.asp – 验证码的XBM图片源代码

<%
Dim arrCode(10,10)

'数字0
arrCode(0,1) = "0x3c" : arrCode(0,2) = "0x66" :
arrCode(0,3) = "0xc3" : arrCode(0,4) = "0xc3"
arrCode(0,5) = "0xc3" : arrCode(0,6) = "0xc3" :
arrCode(0,7) = "0xc3" : arrCode(0,8) = "0xc3"
arrCode(0,9) = "0x66" : arrCode(0,10)= "0x3c"

'数字1
arrCode(1,1) = "0x18" : arrCode(1,2) = "0x1c" :
arrCode(1,3) = "0x18" : arrCode(1,4) = "0x18"
arrCode(1,5) = "0x18" : arrCode(1,6) = "0x18" :
arrCode(1,7) = "0x18" : arrCode(1,8) = "0x18"
arrCode(1,9) = "0x18" : arrCode(1,10)= "0x7e"

'数字2
arrCode(2,1) = "0x3c" : arrCode(2,2) = "0x66" :
arrCode(2,3) = "0x60" : arrCode(2,4) = "0x60"
arrCode(2,5) = "0x30" : arrCode(2,6) = "0x18" :
arrCode(2,7) = "0x0c" : arrCode(2,8) = "0x06"
arrCode(2,9) = "0x06" : arrCode(2,10)= "0x7e"

'数字3
arrCode(3,1) = "0x3c" : arrCode(3,2) = "0x66" :
arrCode(3,3) = "0xc0" : arrCode(3,4) = "0x60"
arrCode(3,5) = "0x1c" : arrCode(3,6) = "0x60" :
arrCode(3,7) = "0xc0" : arrCode(3,8) = "0xc0"
arrCode(3,9) = "0x66" : arrCode(3,10)= "0x38"

'数字4
arrCode(4,1) = "0x38" : arrCode(4,2) = "0x3c" :
arrCode(4,3) = "0x36" : arrCode(4,4) = "0x33"
arrCode(4,5) = "0x33" : arrCode(4,6) = "0x33" :
arrCode(4,7) = "0xff" : arrCode(4,8) = "0x30"
arrCode(4,9) = "0x30" : arrCode(4,10)= "0xfe"

'数字5
arrCode(5,1) = "0xfe" : arrCode(5,2) = "0xfe" :
arrCode(5,3) = "0x06" : arrCode(5,4) = "0x06"
arrCode(5,5) = "0x3e" : arrCode(5,6) = "0x60" :
arrCode(5,7) = "0xc0" : arrCode(5,8) = "0xc3"
arrCode(5,9) = "0x66" : arrCode(5,10)= "0x3c"

'数字6
arrCode(6,1) = "0x60" : arrCode(6,2) = "0x30" :
arrCode(6,3) = "0x18" : arrCode(6,4) = "0x0c"
arrCode(6,5) = "0x3e" : arrCode(6,6) = "0x63" :
arrCode(6,7) = "0xc3" : arrCode(6,8) = "0xc3"
arrCode(6,9) = "0x66" : arrCode(6,10) ="0x3c"

'数字7
arrCode(7,1) = "0xff" : arrCode(7,2) = "0xc0" :
arrCode(7,3) = "0x60" : arrCode(7,4) = "0x30"
arrCode(7,5) = "0x18" : arrCode(7,6) = "0x18" :
arrCode(7,7) = "0x18" : arrCode(7,8) = "0x18"
arrCode(7,9) = "0x18" : arrCode(7,10)= "0x18"

'数字8
arrCode(8,1) = "0x3c" : arrCode(8,2) = "0x66" :
arrCode(8,3) = "0xc3" : arrCode(8,4) = "0x66"
arrCode(8,5) = "0x3c" : arrCode(8,6) = "0x66" :
arrCode(8,7) = "0xc3" : arrCode(8,8) = "0xc3"
arrCode(8,9) = "0x66" : arrCode(8,10)= "0x3c"

'数字9
arrCode(9,1) = "0x3c" : arrCode(9,2) = "0x66" :
arrCode(9,3) = "0xc3" : arrCode(9,4) = "0xc3"
arrCode(9,5) = "0x66" : arrCode(9,6) = "0x3c" :
arrCode(9,7) = "0x18" : arrCode(9,8) = "0x0c"
arrCode(9,9) = "0x06" : arrCode(9,10)= "0x03"
%>

 

xbm.asp – 生成随机的XBM图片

<!--#include file="numcode.asp"-->
<%
Dim i
Dim intLength
Dim intLowerBound, intUpperBound
Dim strNum
Dim intWidth, intHeight
Dim strImage
Dim arrNum

'验证码长度
intLength = 6

Randomize
'验证码的第1个字符,不等于“0”
strNum = Chr(Int((57 - 49 + 1) * Rnd + 49))
'验证码的第2个字符~最后一个字符
for i = 1 to intLength - 1
  strNum = strNum & Chr(Int((57 - 48 + 1) * Rnd + 48))
next

Session("strValidateCode") = strNum

Redim arrNum(intLength)

For I = 1 To intLength
  arrNum(I) = Mid(strNum, I, 1)
Next

intWidth = 8 * intLength
intHeight = 10

Response.ContentType = "strImage/x-xbitmap"

strImage = "#define counter_width " & intWidth & vbCrLf
strImage = strImage & "#define counter_height " & intHeight & vbCrLf
strImage = strImage & "static unsigned char counter_bits[] = {" & vbCrLf

For I = 1 To intHeight
  For J = 1 To intLength
    strImage = strImage & arrCode(arrNum(J),I) & ","
  Next
Next

strImage = Left(strImage, Len(strImage) - 1)
strImage = strImage & "};" & vbCrLf

Response.Write strImage
%>

  form.asp – 输入验证码的表单

<%
Dim strValidate

strValidate = Trim(Request.Form("txtValidate"))
if strValidate<>"" then
  if Session("strValidateCode") = strValidate then
    Response.Write("true")
  else
    Response.Write("false")
  end if
else
  Response.Write("你没有输入验证码")
end if
%>

<form action="<%= Request.ServerVariables("Path_Info") %>" method="post">
  <img src="xbm.asp">
  <input name="txtValidate">
  <input type="submit" value="GOIT">
</form>

破解XBM验证码

  将上面10个数字的XBM源代码换一种方式排列,如下表。

  可以发现,以第3行和第4行为一组,则没有一组是重复的,也就是说,无需要比较全部的十行数据,只需要对这两行数据进行比较就可以识别出是哪一个数字。对于不同的网站,使用的XBM图形是不同的,但是,只要多观察多测试,都可以收集出相应的特征串。

WinXP SP2禁用XBM的原因及解决办法

  在WinXP SP2补丁包里,除了对bug的修复和功能的加强外,也对安全控制做了相应修改,指定了更严格的默认安全策略,以保证用户安全。在更改后的安全策略中,默认去掉了对XBM图片格式的支持的支持。

  这样一个被广泛采用的生成验证代码的技术,为什么微软在XP的SP2升级包中又要禁止掉它呢?这就需要从Xbm的漏洞谈起了。
  Microsoft Internet Explorer和Outlook Express在处理WEB页,HTML邮件,EMAIL附件中畸形的Xbm图象文件时会导致崩溃,问题存在于对Xbm文件中的内容缺少检查,MSIE按照图象规定的长度和宽度分配内存,攻击者可以提高超大的长度和宽度数值导致系统消耗内存或者访问冲突。换句话说,如果构造一个长宽的尺寸特别大的Xbm文件,很容易导致Windows的内存耗尽,导致程序无响应或者死机。本身来说,这不算一个特别严重的漏洞,因为根据安全公告,无法造成溢出,不会存在太大的权限漏洞。但是由于XP的SP2强调安全性,因此将Xbm功能禁用了。
  在安装完WinXP SP2后,因为不再支持XBM图片,当访问某些采用生成Xbm作为验证代码的站点的时候,就相当不方便了,如果有必要,可以通过简单的操作注册表恢复该功能:将注册表键值[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Security]blockXbm的值改为00000000(dword,双字节),没有的话新建立一个就可以了。之后重新启动机器,则Xbm格式的图片就可以看到了。

用其他方式实现验证码

  从SP2禁止Xbm的趋势看出,微软已经开始打算放弃对Xbm格式的支持了。那么,作为程序编写者,有必要未雨绸缪,寻找其他生成验证码的途径。在php中,可以通过调用gd库等方式生成jpg/gif等图形格式的注册验证码,那么在asp中有其他的办法么?可以采用以下2种方式实现支持SP2的图形验证码。
  如果是购买的虚拟主机,那么可以采用将jpg/gif图片放到数据库,然后用session传值的方式,最后利用asp直接从数据库中输出图片,这方法的好处是不需要特别设置服务器端,坏处则是每次生成验证图片时都会需要与数据库连接,增加了开销。
  如果是有管理员控制权限的用户,可以考虑采用第三方组件来实现。一些组件的免费版本对生成的图形有一定限制,不过已经足够用来制作验证码了。


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。