再论验证码安全:请及时销毁你的验证码

我在 上一篇文章中讲到了如何使用C#模拟用户登陆具备验证码网站。今天我就换位思考一下,站在网站开发人员的角度讲一讲验证码的的一个安全问题:及时销毁网站中的验证码。
为了方便你们理解,这里我就以一个投票的应用网站为例进行说明。投票网站首先要防止的就是用户不断点击投票按钮来重复投票;固然,避免重复投票的解决办法有不少,好比记录IP、写入Session、Cookie甚至还有要求用户输入×××号码等。可是你记录IP,那我就写一个程序来模拟发包,每投1票后自动换代理,而后继续投票,若是是写入到Session中那么我写个投票程序,每投1票就从新开启一个新的会话就是。若是是记入Cookie,那我该表Cookie的值再模拟投票发包,要输入×××号进行验证?写个××××××程序也十分容易……
在投票机器人面前,记录IP、记录×××号码、写入Session和Cookie等防做弊技术形同虚设。因此在投票网站中验证码功能必不可少。那么咱们将验证码功能加入到网站投票中:
1.生成验证码图片的页面CreateImg.aspx,其后台代码为:
protected void Page_Load( object sender, EventArgs e)
     {
string checkCode = CreateCode(4); //生成随机是4位验证码
             Session[ "CheckCode"] = checkCode; //将验证码保存到Session中
             CreateImage(checkCode); //将验证码以图片的方式输出
     }    
2.在用户单击投票按钮后触发的事件:
protected void btnVote_Click( object sender, ImageClickEventArgs e)
{
         if (!ValidateUserInfo()) //验证IP在数据库中的状况,一个IP一天只能投5张票,从而防止重复投票
        {
                UIHelper.Alert(Page, "一个IP一天只能投5张票,请勿重复投票");
                 return;
        }
         if (Session[ "CheckCode"] == null)
        {
                UIHelper.Alert(Page, "验证码已过时,请从新输入");
                 return;
        }
         if (Session[ "CheckCode"].ToString().ToLower() != txbCode.Text.ToLower()) //验证码忽略大小写
        {
                UIHelper.Alert(Page, "验证码错误");
                 return;
        }
         else //验证码正确
        {
                UpdateVote(); //修改数据库,将投票数加1
        }
}
OK,大功告成!这个程序逻辑上有问题吗?没有吧,验证码是生成的图片,图片是有干扰因素的,不会被程序识别,并且验证码的内容是保存到服务器的,逻辑处理也是错。彷佛一切都那么完美,可是事实并非这样,对于这样的投票网站,个人投票机器人仍然肆无忌惮的不断切换IP,不断刷票。(要作投票机器人的同志们注意啦,不要看到投票的地方是有验证码的就束手无策了哦,也许他的网站就存在如下描述的漏洞哦!)
漏洞就出在投票时对验证码进行验证后没有对服务器上Session中的验证码内容进行销毁。在平时使用IE浏览时,每投票一次后刷新页面,验证码生成页面被从新请求,因此Session值在请求验证码生成页时被替换,因此不会有什么问题。可是如今面对的是投票机器人,个人机器人在第一次请求时得到验证码的图片并展现给用户,用户肉眼识别验证码,而后输入程序的文本框中,因为服务器上验证码的内容并无被销毁,并且投票程序也不会再请求验证码生成图片的URL,因此接下来每次使用相同的SessionID和用户输入的验证码值,服务器验证投票时 
if (Session["CheckCode"].ToString().ToLower() != txbCode.Text.ToLower())
都会返回false,验证码都是经过的,因此投票天然成功。终究是百密一疏啊!费尽心思防止投票做弊,最终却由于这一个地方的疏忽而前功尽弃,投票做弊成功,投票结果仍是被投票机器人所左右。
也许有人想到了,那能够在Session中放置一个标记,若是投票成功了就将标记置“1”,下次请求时判断Session中标记为“1”就拒绝投票就是了。可是投票只是我这里举的一个例子,像论坛这种用验证码防止用户恶意灌水的总不可能限制用户只发一帖吧。论坛发帖时的验证码若是没有被及时销毁,那么个人灌水机器人就仍然能够处处肆意发帖了,哈哈哈哈。
要避免这个漏洞被利用仍是很简单,只须要将上面的代码中投票完成后当即将验证码从服务器上销毁便可:
protected void btnVote_Click( object sender, ImageClickEventArgs e)
{
         if (!ValidateUserInfo()) //验证IP在数据库中的状况,一个IP一天只能投5张票,从而防止重复投票
        {
                UIHelper.Alert(Page, "一个IP一天只能投5张票,请勿重复投票");
                 return;
        }
         if (Session[ "CheckCode"] == null)
        {
                UIHelper.Alert(Page, "验证码已过时,请从新输入");
                 return;
        }
         if (Session[ "CheckCode"].ToString().ToLower() != txbCode.Text.ToLower()) //验证码忽略大小写
        {
                UIHelper.Alert(Page, "验证码错误");
                 return;
        }
         else //验证码正确
        {
                UpdateVote(); //修改数据库,将投票数加1
        }

Session[ "CheckCode"] = null; //验证码使用后立刻从服务器销毁
}
这样若是仍然使用相同的SessionID和验证码值,那么将会在 if (Session["CheckCode"] == null)这里判断出验证码已通过期,想成功投票?从新请求验证码页面得到验证码图片,而后从新输入验证码吧!
这个问题虽然看起来不觉得然,可是正所谓“千里之堤毁于蚁穴”,只要验证码没有从服务器上销毁,那么页面上的验证码仍是形同虚设,和验证码的图片地址为<img src="CreateCheckCode.aspx?code=af5d" alt="验证码">
这样把验证码直接暴露在HTML中或者直接使用文本而不是图片来表示验证码有什么区别呢?
另外有人提到,这里是Session保存验证码才会有这个问题,那彻底基于Cookie加密的呢?在前面的文章中我也提到过Cookie加密的方式保存验证码的内容,可是今天我又仔细想了一下,得出结论: 验证码内容不能保存到客户端,也就是说根本就不该该使用Cookie加密的方式,Cookie加密保存验证码明文是没有什么意义的,必需要在服务器端保存与验证码相关的信息(好比验证码明文或者验证码加密解密密钥)。为何不能使用Cookie加密保存验证码?我举个简单的例子吧:
好比如今页面上显示的验证码是1234,同时抓包发现提交的时候Cookie中有值:“EncryptCode=asdf”这是验证码的明文通过加密后的密文,我不知道加密算法是什么,可是我每次程序提交时就将1234做为验证码的值同时将“EncryptCode=asdf”做为Cookie的一部分发送到服务器,那么服务器将1234加密后与发送过来的Cookie值“asdf”一比较,两者相同,验证经过!!!
因此我认为验证码的明文是不可能彻底基于客户端的,必需要在服务器上保存与验证码相关的信息(验证码明文或密钥)。既然要在服务器上保存相关信息,那么就可能出现这个漏洞。固然这是我想遍了全部验证码明文保存的方法后得出的结论,我还不敢拍胸脯说这是100%正确的,若是你们认为这个结论不正确,那但愿可以提出具体的状况。
但愿你们若作过验证码的都再回头看看本身的验证码内容在服务器上及时销毁没有。这个错误很容易犯,我在某大公司的网站上都发现了这个漏洞,可见犯此错的网站绝对不在少数。
最后但愿你们的网站更加安全,更加健壮。
【出自 博客园深蓝居

0html

收藏数据库

fangdaren

15篇文章,3W+人气,0粉丝

相关文章
相关标签/搜索