本篇继续对于安全性测试话题,结合DVWA进行研习。

Insecure Captcha不安全验证码

1. 验证码到底是怎么一回事

这个Captcha狭义而言就是谷歌提供的一种用户验证服务,全称为:Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)。

很巧妙的是,Captcha单独成词的意思就是,抓到你了哟_

Captcha在各种海外网站被广泛用于用户验证。而在国内,由于众所周知的原因,我们不用谷歌的服务,很多接口平台都可以提供类似服务。

比如apishop的这个四位验证码服务接口:

那么验证码到底在用户验证的过程中起到什么样的作用呢?

验证码最大的作用就是防止攻击者使用工具或者软件自动调用系统功能

就如Captcha的全称所示,他就是用来区分人类和计算机的一种图灵测试,这种做法可以很有效的防止恶意软件、机器人大量调用系统功能:比如注册、登录功能。

我们前面讲到的Brute Force字典式暴力破解,就必须要使用工具大量尝试登录。如果这个时候系统有个严密的验证码机制,此类攻击就无计可施了。

其工作流程如下所示:

2. 验证码绕过

为什么前文要在验证码机制前面黑体强调他要是严密的,那当然是如果验证码机制设计不得当,绕过它也只是分分钟的事情。。。

DVWA提供的试验模块长这个样子:

我们将其安全级别调到最低,使用ZAP做为代理进行抓包,填入任意密码触发请求,看到请求内容如下:

这是个啥子意思呢,我们参考一下DVWA的后台逻辑:

<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
); // Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
$html .= "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
} if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ]; // Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new ); // Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for the end user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
} ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
} ?>

代码有点长,但是可以明显看出来,这个机制是很简单的。整个验证逻辑将验证分为两步:Step1,Step2,而对于captcha验证的逻辑只存在于Step1中的小小一段:

既然所有验证逻辑都只存在于Step1,那么如果我直接绕过它,有可能吗?

其实非常简单,这里我们要用到抓包-改包-重发的方法,ZAP已经给我们提供了“请求断点”功能。

点击上图中绿色断点按钮,ZAP就进入请求断点状态,在此状态下ZAP不再简单的将客户端和服务器之间的请求交互转发,而是像其他编程工具的断点功能一样,让请求反馈变为单步执行。那么我们就可以在请求发出,尚未传递至服务器之前,对请求内容进行篡改:

改包重发的结果:

密码修改成功,而这整个过程中我们完全没有去处理captcha的验证码,也就是说这个验证码被完全绕过了!

3. DVWA的验证码机制完善防御

既然验证码逻辑是有可能被绕过,接下来我们来研究一下,如何建立更完善的机制呢。

Medium级别

<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
); // Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
$html .= "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
} if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ]; // Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
} // Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new ); // Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for the end user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
} ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
} ?>

主要的机制在这里:

加入了验证第一步是否通过的判断。

唔。。。其实没什么变化,同样改包重发一键搞定,无非是多加了一个参数:

High级别

<?php

if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
); if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new ); // Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for user
$html .= "<pre>Password Changed.</pre>"; } else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
} } else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
} ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
} // Generate Anti-CSRF token
generateSessionToken(); ?>

High级别的变动还比较多:

  • 验证改成了单步

  • 加入了另一个参数'g-recaptcha-response'

  • 加入验证user-agent

  • 加入Anti-CSRF-Token(本文虽未提及,但其实前面两个级别通过CSRF攻击也可以实现攻击,可以参考上一篇中的方法)

通过前两两个级别的攻破,我们应该知道,增加的这个参数根本没啥用;而user-agent也是完全可以改包的。

改包如下即可绕过:

Impossible

<?php

if( isset( $_POST[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Hide the CAPTCHA form
$hide_form = true; // Get input
$pass_new = $_POST[ 'password_new' ];
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new ); $pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_conf = md5( $pass_conf ); $pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr ); // Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
); // Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute(); // Do both new password match and was the current password correct?
if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) {
// Update the database
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute(); // Feedback for the end user - success!
$html .= "<pre>Password Changed.</pre>";
}
else {
// Feedback for the end user - failed!
$html .= "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";
$hide_form = false;
}
}
} // Generate Anti-CSRF token
generateSessionToken(); ?>

Impossible级别主要的改动在于,移除了High级别中多余的g-recaptcha-response参数判断,而只采用CAPTCHA本身的验证结果进行判断,并且要求输入原始密码。这样要绕过验证码就基本不可能了。

3. 验证码机制测试

话题再回到测试,如本文开头所说,验证码的主要作用就是防止所谓的"机器人" - 即计算机自动程序。

在验证码机制推行起来之前,许多知名网站都经受过“机器人”注册的攻击。由于“机器人”可能在短时间内大量调用系统功能,因此经常导致服务器宕机以及垃圾数据。

我们之前提到过的字典式破解等攻击方式,也可以通过验证码进行防御。

不过现在随着人工智能的发展,验证码的破解,图形解析的技术门槛越来越低,图形类验证码的破解已经不是很难的事情了。

而且通过此文,我们也应该知道,现在各大系统的验证码一般通过接口调用实现,而一般来说验证码的处理逻辑则是独立的。而这个处理逻辑则是安全验证和测试的主要要点,如果逻辑设计不合理,验证码就会变成徒劳。如本文所示,其实我们完全没有去处理验证码破解的问题。

题外话

对于UI自动化而言,我们也会遇到验证码的问题。那么UI自动化中应该如何处理验证码呢。

要知道做为一种图灵测试机制,验证码防御的就是类似selenium这样的计算机自动化程序,即“机器人”。我们做UI自动化有没有必要去引入验证码破解机制予以破解?

个人认为,没有这个必要。

    1. 如果你使用自动化的手段破解了验证码,那么只能说明你们系统的验证码是废物!要更新升级!直到你破不掉为止。
    1. 破解验证码需要额外的代码量和技术手段,费力不讨好
    1. 图形验证码也许现在的技术手段可以破解,但是类似谷歌的第三代captcha验证,现在还没有完美的破解手段。

所以,如果UI自动化中遇到验证码怎么办?

其实很简单,与开发协商将测试环境调为测试模式,即关闭验证码功能即可。验证码功能可以单独予以测试。

安全性测试入门 (五):Insecure CAPTCHA 验证码绕过的更多相关文章

  1. 安全性测试入门:DVWA系列研究(一):Brute Force暴力破解攻击和防御

    写在篇头: 随着国内的互联网产业日臻成熟,软件质量的要求越来越高,对测试团队和测试工程师提出了种种新的挑战. 传统的行业现象是90%的测试工程师被堆积在基本的功能.系统.黑盒测试,但是随着软件测试整体 ...

  2. 安全性测试入门:DVWA系列研究(二):Command Injection命令行注入攻击和防御

    本篇继续对于安全性测试话题,结合DVWA进行研习. Command Injection:命令注入攻击. 1. Command Injection命令注入 命令注入是通过在应用中执行宿主操作系统的命令, ...

  3. 安全性测试入门 (四):Session Hijacking 用户会话劫持的攻击和防御

    本篇继续对于安全性测试话题,结合DVWA进行研习. Session Hijacking用户会话劫持 1. Session和Cookies 这篇严格来说是用户会话劫持诸多情况中的一种,通过会话标识规则来 ...

  4. 安全性测试入门 (三):CSRF 跨站请求伪造攻击和防御

    本篇继续对于安全性测试话题,结合DVWA进行研习. CSRF(Cross-site request forgery):跨站请求伪造 1. 跨站请求伪造攻击 CSRF则通过伪装成受信任用户的请求来利用受 ...

  5. DVWA-全等级验证码Insecure CAPTCHA

    DVWA简介 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法 ...

  6. DVWA 黑客攻防演练(六)不安全的验证码 Insecure CAPTCHA

    之前在 CSRF 攻击 的那篇文章的最后,我觉得可以用验证码提高攻击的难度. 若有验证码的话,就比较难被攻击者利用 XSS 漏洞进行的 CSRF 攻击了,因为要识别验证码起码要调用api,跨域会被浏览 ...

  7. DVWA全级别之Insecure CAPTCHA(不安全的验证码)

    Insecure CAPTCHA Insecure CAPTCHA,意思是不安全的验证码,CAPTCHA是Completely Automated Public Turing Test to Tell ...

  8. Insecure CAPTCHA (不安全的验证码)

    dvwa不能正常显示,需要在配置文件中加入谷歌的密钥: $_DVWA[ 'recaptcha_public_key' ] = '6LfX8tQUAAAAAOqhpvS7-b4RQ_9GVQIh48dR ...

  9. 安全性测试:OWASP ZAP使用入门指南

    免责声明: 本文意在讨论使用工具来应对软件研发领域中,日益增长的安全性质量测试需求.本文涉及到的工具不可被用于攻击目的. 1. 安全性测试 前些天,一则12306用户账号泄露的新闻迅速发酵,引起了购票 ...

随机推荐

  1. 【转】 Pro Android学习笔记(四八):ActionBar(1):Home图标区

    目录(?)[-] Home Icon 源代码 TextView的滚动 返回主activity或指定activity     ActionBar在Android 3.0 SDK中为平板引入,在4.0中也 ...

  2. mina中的发送延时

    由于项目需要,用到了 mina 框架进行 tcp 通讯.我是初次接触 mina,于是从 Hello world 开始学习了 mina .期间遇到了一个奇怪的发送数据的延迟问题,解决的过程是曲折的,但找 ...

  3. 人物-IT-任正非:任正非

    ylbtech-人物-IT-任正非:任正非 任正非,祖籍浙江省浦江县,1944年10月25日出生于贵州省安顺市镇宁县.华为技术有限公司主要创始人兼总裁. 1963年就读于重庆建筑工程学院(现已并入重庆 ...

  4. 滑动swipe的妙用

    转自:http://www.cnblogs.com/NEOCSL/archive/2013/03/04/2942861.html iterface ITouchable; function OnPic ...

  5. sql查询将列里面的值替换为别的值但是实际值不变

    数据库有一张表BUG(缺陷记录表) 里面有字段severity(严重程度): severity的值实际为1,2,3,4,但希望在查询结果中将severity的1,2,3,4值显示为其他的值,但seve ...

  6. 阿里巴巴Druid数据库连接池的使用

    准备: 创建一个基于SpringBoot的web项目 1 引入相关依赖 jpa.mysql.druid <?xml version="1.0" encoding=" ...

  7. Springboot ResponseEntity IE无法正常下载文件

    项目在google浏览器下都很nice了,但当测试到IE的时候开始出现各种问题. 项目是前端js通过URL传参fileName到后台解析返回ResponseEntity 前端代码如下: window. ...

  8. AngularJS(Part 10)--页面导航

    页面导航     过去,一个URL代表一个页面.但是随着Ajax的兴起,情况发生的很大的变化.不同的内容可以使用同一个URL.这让浏览器中的回退.前进甚至收藏按钮都失去了作用.而AngularJS提供 ...

  9. 在Eclipse里面配置Struts2

    下面介绍在Eclipse里面配置Struts2 下载Struts2的压缩包 我下载的是2.3.32版本 解压之后如图所示 apps目录:Struts2的范例 docs目录:Struts2的文档 lib ...

  10. C#回调函数的简单讲解与应用例子

    using System; namespace CallBackTest{ class Program //用户层,执行输入等操作 { static void Main(string[] args) ...