0x01 前言
验证码的定义:验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上用验证码是现在很多网站通行的方式,我们利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。验证码的作用:
简而言之、防止机器操作。验证码分为图形验证码、短信验证码、语音验证码等等。本文仅分析图形验证码这一种。
0x02 基本流程
笔者将生成验证码的流程总结如下:
- 验证码使用后未销毁。
- 生成验证码的字符集可控。
- 验证码存放位置暴露。
- 验证码对比失败后仍进行其他对比。
- 其他对比后验证码未销毁。
- 验证码尺寸可控
- 验证码过于简单,可轻易机器识别。
0x03 代码还原&实例分享
两段简单的PHP代码实现生成数字验证码并验证,以下代码复现均基于这两文件实现: captcha.php<?php
session_start();
$image=imagecreatetruecolor(100,30);
$bgcolor=imagecolorallocate($image,255,255,255);
imagefill($image, 0, 0, $bgcolor);
$captch_code='';
//画出4个随机的数字
for($i=0;$i<4;$i++){
$fontsize=10;
$data='0123456789';//字符集
$fontcontent=substr($data,rand(0,strlen($data)),1);
$captch_code.=$fontcontent;
$x=($i*100/4)+rand(5,10);
$y=rand(5,10);
imagestring($image,$fontsize,$x,$y,$fontcontent,$fontcolor);
}
$_SESSION['captcha']=$captch_code;
header('content-type:image/png');
imagepng($image);
imagedestroy($image);
index.php
<?php
header("Content-type: text/html; charset=utf-8");
if(isset($_REQUEST['captcha'])){
session_start();
if(strtolower($_REQUEST['captcha'])==$_SESSION['captcha']){
echo '验证码正确';
$_SESSION['captcha']=null;
}else{
echo '验证码错误';
$_SESSION['captcha']=null;
}
exit();
}
?>
<html>
<head>
<meta charset="UTF-8">
<title>浅析图形验证码安全</title>
</head>
<body>
<form method="post" action='./index.php'>
<p>验证码图片:
<img id="code_img" border="1" src="./captcha.php?r=<?php echo rand();?>" >
<a href="javascript:void(0)" onclick="document.getElementById('code_img').src='./captcha.php?r='+Math.random()">刷新一下</a>
</p>
<p>请输入验证码:<input type="text" name="captcha" value=""></p>
<p><input type="submit" value="提交" style="padding:6px 20px;"></p>
</form>
</body>
</html>
Round 1:验证码使用后未销毁
这是最常见的一种验证码缺陷,通常我们也叫验证码复用,代码如下:

Round 2:生成验证码的字符集可控
生成验证码的字符集可控这种情况并不常见,目前笔者接触过的案例出现在ThinkCMF 1.X-2.X
,少见,但是脚本化容易,对比上一中优势在于验证码可控,不用人工输入第一个有效 Payload ,代码层面如下:
案例:

Round 3:验证码存放位置暴露
此类问题出现比例低于第一种高于第二种,出现问题的缓解主要在于开发没把验证码的明文写进session而是选择了写到其他地方,比如写到Cookie,藏到编码后的某些值里面,藏到图片的字节里面等等。代码层面如下:

Round 4:验证码对比失败后仍进行其他对比
这类问题出现的频率和上一中相差无几,问题出现在开发的登录逻辑上,验证码失败后本该退出页面,开发只是提醒了一下,并未阻断后续代码的运行,代码实现如下:

Round 5:其他对比后验证码未销毁
这类不常见,但是利用起来很有意思,常出现在有其他校验的地方。代码实现如下:


Round 6:验证码尺寸可控
该问题被人们广为关注源于PHPcms早期版本的后台登录验证码尺寸可控,可用于DDoS。而笔者亲自挖掘到这样的问题源于ThinkCMF 1.X-2.X
,详情可参考验证码外部控制实例中的尺寸可控部分。代码实现如下:


Round 7:验证码过于简单,可轻易机器识别。
写到这里细心的读者会发现,到目前为止给出的代码运行后的真面目都还见过,真面目是这样的:

0x04 总结
修复套话:- 验证码设置为6位并设置超时(一分钟)失效。
- 建议修改应用程序源代码,在登录模块中增加对验证码正确性的验证,并且要保证在验证用户名、密码或其他信息之前首先验证验证码的正确性。