【笔记】网易微专业-Web安全工程师-04.WEB安全实战-2.暴力破解

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校验,限制登录错误次数,验证码校验都是有效的防止暴力破解的手段。

原文地址:https://www.cnblogs.com/kplayer/p/8467572.html