• 【笔记】网易微专业-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校验,限制登录错误次数,验证码校验都是有效的防止暴力破解的手段。

  • 相关阅读:
    angular js 删除及多条删除
    angular js 页面修改数据存入数据库
    angular js 页面添加数据保存数据库
    angular js 分页
    内置函数和匿名函数
    装饰器,迭代器,生成器
    函数的进阶
    函数
    文件操作
    列表,字典
  • 原文地址:https://www.cnblogs.com/kplayer/p/8467572.html
Copyright © 2020-2023  润新知