KP君之前买了一个拉杆箱,在初始设置密码时不熟悉步骤,一时手抖,密码已经设好,但不知道设置了什么密码,欲哭无泪。想要找回密码,只能一个个试验,拉杠箱的密码锁有3位,对应000~999,那么最多需要1000次就能打开密码,这就是简单的“暴力破解”。
暴力破解(Brute Force):核心就是“穷举法”,猜出用户的密码。看起来似乎工程量很大,但是通常用户设置密码都不太复杂,因此利用常用的密码字典,就能破获大部分的密码。理论上来说,只要给定足够的时间,暴力破解就一定能破译密码。
在实战前,先介绍一款渗透工具OWASP ZAP (Zed Attack Proxy),可点击下方链接了解和下载安装。
https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
这款工具功能强大,包含抓包,爬虫,端口扫描,主动扫描等等。我们在暴力破解这一章节中要利用的是它的抓包以及Fuzzy功能。
DVWA实战:
1. 打开phpStudy或xampp,运行Apach和MySQL;
2. 打开ZAP软件,默认代理端口是8080;因此,需要我们在Firefox设置同样的代理端口,可以使用之前提到的Proxy Switcher插件实现,也可直接配置浏览器的网络代理。
3. 浏览器进入DVWA主界面,在左侧栏选择DVWA Security安全等级为Low,然后进入Brute Force;
4. 随便输入一个用户名和密码:kplayer/password,收到报错提示:
通过ZAP抓包,我们看到登录时采用GET方法,带上username和password参数:
因此,我们采用暴力破解的一个思路就是构造多条URL请求,替换其中的username和password,不过要是人工构造,那跟在输入框里一个个输有什么两样?好在ZAP提供了Fuzz方法,让我们可以配置需要替换的域,一个个尝试,节省劳动力。
5. 选中kplayer,右键Fuzz,选中Payloads,点击Add,增加我们的用户名字典:admin,root,guest,Test;同样的方法,对password增加字典:admin,123456,111111,666666,root,password; 然后点击右下角Start Fuzzer,ZAP就会替我们自动组合发送请求。
6. 查看底部Fuzzer的任务,发现ZAP已经完成了所有的组合请求。那么问题来了,怎么知道哪一个是正确的用户名/密码响应?通常来说,登录成功和登录失败的响应报文大小会有差异,我们按响应报文大小排序,那个不一样的一般就是正确的用户名/密码,也就是我们的默认密码admin/password。
至此,我们完成了一个low等级的暴力破解,接下去我们看看medium等级的暴力破解。
7. 进入DVWA主界面,在左侧栏选择DVWA Security安全等级为medium,使用上述同样的方法,我们发现同样能够破解,只是需要花费的时间变长了,查看右下角View Source,我们发现代码中多了失败登录sleep两秒的控制:
else { // Login failed sleep( 2 ); echo "<pre><br />Username and/or password incorrect.</pre>"; }
8. 接下去我们看看high等级的破解,采用同样的方法,这次失败了,我们查看请求报文,发现在原来的参数username和password后面,多了一个token:
查看页面源码,发现多了一个隐藏的token框:
<form action="#" method="GET"> Username:<br /> <input type="text" name="username"><br /> Password:<br /> <input type="password" AUTOCOMPLETE="off" name="password"><br /> <br /> <input type="submit" value="Login" name="Login"> <input type=‘hidden‘ name=‘user_token‘ value=‘ac9dabf93f796a0b905e64e5326e579c‘ /> </form>
查看后台源码,发现登录中多了token的生成和校验。
<?php if( isset( $_GET[ ‘Login‘ ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ ‘user_token‘ ], $_SESSION[ ‘session_token‘ ], ‘index.php‘ ); ...... // Generate Anti-CSRF token generateSessionToken(); ?>
Token,也叫令牌,是随机生成的一组序列,一般用在两个地方: 1) 防止表单重复提交, 2) Anti-CSRF攻击。原理上都是通过session token来实现的:当客户端请求页面时,服务器会生成一个随机数Token,并且将Token放置到session当中,然后将Token发给客户端(一般通过构造hidden表单)。下次客户端提交请求时,Token会随着表单一起提交到服务器端。
在Anti-CSRF攻击中:服务器端会对Token值进行验证,判断是否和session中的Token值相等,若相等,则可以证明请求有效,不是伪造的。
在“防止表单重复提交",服务器端第一次验证相同过后,会将session中的Token值更新下,若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的Token没变,但服务器端session中Token已经改变。
那么是不是就无懈可击了呢?只要能拿到每次的token,就能像之前一样拼接请求,完成自动发送,所以一种方法就是用Python写个脚本,每次都取返回报文里的token value值再拼接。另外一个思路是,既然只有在第一次请求后,服务器会生成一个token返给客户端,之后才需要校验,那么如果我们每次的请求都伪装成第一次,就不用进行token检测了。
9. 最后我们来看看impossible等级,这里我们发现输错密码3次后,提示我们15分钟后才能输入,有效的增加了暴力破解的时间成本。
查看后台源码,看看是如何进行控制的:
<?php if( isset( $_POST[ ‘Login‘ ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ ‘user_token‘ ], $_SESSION[ ‘session_token‘ ], ‘index.php‘ ); // Sanitise username input $user = $_POST[ ‘username‘ ]; $user = stripslashes( $user ); $user = mysql_real_escape_string( $user ); // Sanitise password input $pass = $_POST[ ‘password‘ ]; $pass = stripslashes( $pass ); $pass = mysql_real_escape_string( $pass ); $pass = md5( $pass ); // Default values $total_failed_login = 3; $lockout_time = 15; $account_locked = false; // Check the database (Check user information) $data = $db->prepare( ‘SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;‘ ); $data->bindParam( ‘:user‘, $user, PDO::PARAM_STR ); $data->execute(); $row = $data->fetch(); // Check to see if the user has been locked out. if( ( $data->rowCount() == 1 ) && ( $row[ ‘failed_login‘ ] >= $total_failed_login ) ) { // User locked out. Note, using this method would allow for user enumeration! //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>"; // Calculate when the user would be allowed to login again $last_login = $row[ ‘last_login‘ ]; $last_login = strtotime( $last_login ); $timeout = strtotime( "{$last_login} +{$lockout_time} minutes" ); $timenow = strtotime( "now" ); // Check to see if enough time has passed, if it hasn‘t locked the account if( $timenow > $timeout ) $account_locked = true; } // Check the database (if username matches the password) $data = $db->prepare( ‘SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;‘ ); $data->bindParam( ‘:user‘, $user, PDO::PARAM_STR); $data->bindParam( ‘:password‘, $pass, PDO::PARAM_STR ); $data->execute(); $row = $data->fetch(); // If its a valid login... if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) { // Get users details $avatar = $row[ ‘avatar‘ ]; $failed_login = $row[ ‘failed_login‘ ]; $last_login = $row[ ‘last_login‘ ]; // Login successful echo "<p>Welcome to the password protected area <em>{$user}</em></p>"; echo "<img src=\"{$avatar}\" />"; // Had the account been locked out since last login? if( $failed_login >= $total_failed_login ) { echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>"; echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>"; } // Reset bad login count $data = $db->prepare( ‘UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;‘ ); $data->bindParam( ‘:user‘, $user, PDO::PARAM_STR ); $data->execute(); } else { // Login failed sleep( rand( 2, 4 ) ); // Give the user some feedback echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>"; // Update bad login count $data = $db->prepare( ‘UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;‘ ); $data->bindParam( ‘:user‘, $user, PDO::PARAM_STR ); $data->execute(); } // Set the last login time $data = $db->prepare( ‘UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;‘ ); $data->bindParam( ‘:user‘, $user, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>
实战心得:
暴力破解的核心是“穷举法”,因此,采用token校验,限制登录错误次数,验证码校验都是有效的防止暴力破解的手段。