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

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介:

我在上一篇文章中讲到了如何使用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%正确的,如果大家认为这个结论不正确,那希望能够提出具体的情况。

希望大家若做过验证码的都再回头看看自己的验证码内容在服务器上及时销毁没有。这个错误很容易犯,我在某大公司的网站上都发现了这个漏洞,可见犯此错的网站绝对不在少数。

最后希望大家的网站更加安全,更加健壮。

目录
相关文章
|
安全 Java API
阿里云——Java实现手机短信验证码功能
通过手机短信发送验证码,是最普遍、最安全验证用户真实身份的方式。目前,短信验证码广泛应用于用户注册、密码找回、登录保护、身份认证、随机密码、交易确认等应用场景。本文通过调用API开发一个短信验证码为例,带您了解如何实现短信验证码功能。
7220 6
阿里云——Java实现手机短信验证码功能
|
4月前
|
NoSQL Java Redis
认证服务---整合短信验证码,验证码倒计时,验证码防刷校验 【一】
这篇文章介绍了如何在分布式微服务项目中整合短信验证码服务,包括使用阿里云短信验证接口、将短信验证功能集成到第三方服务中、其他服务的远程调用,以及通过Redis实现验证码防刷机制的代码实现和遇到的问题解决方案。
|
存储 前端开发 NoSQL
TienChin 验证码响应结果分析&验证码生成接口分析
首先从前端开始进行分析,进入到登录页面,打开开发者工具(f12),找到 network,f5 刷新一下页面,然后,筛选一下,筛选内容为 Fetch/XHR:
92 0
TienChin 验证码响应结果分析&验证码生成接口分析
|
安全 开发工具
【干货】验证码的常见类型总结
验证码是一种区分用户是计算机和人的公共全自动程序。简单来说,验证码就是验证操作是人还是机器。下面我就总结一下常见的验证码类型都有哪些?
【干货】验证码的常见类型总结
|
运维 机器人 API
细数验证码的N种生成方式
验证码(CAPTCHA)是一种用于确定网站或应用程序使用者是否为人类的技术。它通常由一组图像或数字组成,用户需要输入正确的内容才能通过验证。验证码被广泛用于防止自动化脚本或机器人攻击,以确保用户是真正的人类。
451 0
细数验证码的N种生成方式
|
安全 前端开发
KgCaptcha 行为验证码自定义类型设置
KgCaptcha 行为验证码自定义类型设置
KgCaptcha 行为验证码自定义类型设置
|
安全 前端开发 定位技术
推荐一个非常好的行为验证码项目!
KgCaptcha 结合了设备指纹、行为特征、访问频率、地理位置等多项技术,有效的拦截恶意登录、批量注册,阻断机器操作,拦截非正常用户。较传统验证码相比,用户无需再经过思考或输入操作,只需轻轻一滑即可进行验证。
推荐一个非常好的行为验证码项目!
|
Java 数据安全/隐私保护 Windows
【Java实验五】字符串加密、模拟用户登录、生成验证码、春节倒计时等
1、实验题目:字符串加密 键盘输入一个原始字符串作为明文,然后使用加密方法加密,再对加密字符串进行解密。样例如下图,加密方法自定,完成其功能并测试。
159 0
|
NoSQL Redis
注册+发送验证码思路
注册+发送验证码思路
149 0
注册+发送验证码思路
|
Java Spring
网站验证码的设计与实现
网站验证码的设计与实现