DVWA 黑客攻防演練(二)暴力破解 Brute Froce


暴力破解,簡稱”爆破“。不要以為沒人會對一些小站爆破。實現上我以前用 wordpress 搭建一個博客開始就有人對我的站點進行爆破。這是裝了 WordfenceWAF 插件后的統計的情況。

裝了 WordfenceWAF 看到報告就深刻感受到國際友人對我這破站的安全性的深刻關懷了。你不封他們的 ip ,他們的程序就會像中了 “奇淫合歡散” 那些對你的網站鍥而不舍地爆破。而下面會從 dvma 中學習如何爆破和如何防爆破。

初級

頁面是這樣的。

很簡單的登錄,代碼可以點擊 view source 可以看到

<?php

if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];

    // Get password
    $pass = $_GET[ 'password' ];
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        echo "<pre><br />Username and/or password incorrect.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

這提交太簡單,簡直引人犯罪,來看看人類滿滿的惡意吧。下面介紹在 Kali Linux 中比較常用的兩個工具 burp suite 和 hydra

Burp Suite

Burp Suite 是 Kali Linux 中預裝的非常強大的滲透工具。在這個部分主要用來抓包和進行吧爆破。(可以在這里下載,win,mac都可以用的

將安全等級設置為 low 后,打開 burp suite,設置火狐瀏覽器的網絡走 burp suite 的代理(即localost:8080,如圖所示),讓 burp suite 攔截火狐的請求

如果你要修改 burp suite 端口的話,你可以在 proxy-> options 中修改

之后你可以在 DVWA 的 Brute Force 的頁面上輸入帳號 admin 和 密碼,密碼隨便都可以,提交后,發現頁面動不了了,因為 burp suite 攔截了請求了。 點擊右鍵,將求的信息發送到入侵器(intruder)(你點擊 Forward 會將請求跑完,如果錯過了,可以在 Proxy->Http History 和 target 那里都能找到之前的記錄 )

然后在 intruder->position 那里點擊 clear ,清理掉所有的變量,然后再添加 password 部分。也就是爆破點只有 password 部分。

然后點擊 payloads,添加一份常用密碼(Kali Linux 在 /usr/share/john/password.lst 或者從 github 上搜一份),並移除注釋。

然后再設置爆破的標准,因為密碼錯誤的時候會提示 Username and/or password incorrect

所以報文如果不能匹配到 incorrect,就說明密碼爆破成功。所以在 Options 那里設置成這樣就行了

然后再點擊上面,右上角的 start attack 就可以了

結果如下

明顯 password 就是密碼了

Hydra

爆破的原理是一樣的,不過 hydra 用的是命令行。而根據上面抓包得出的信息。(文檔可以看這里) 可以馬上用這條命令進行爆破。

hydra 192.168.0.110 -s 5678 -l admin -IVv -P /usr/share/john/password.lst http-get-form "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:incorrect.:H=Cookie: security=low;PHPSESSID=isk2inn2psu1sh56slicq6oim7"

常用參數

  • s :端口
  • l:用戶名
  • L:用戶列表文件
  • p:密碼
  • P:密碼文件
  • I:忽略現有的恢復文件,強制退出就有恢復文件了(不需要等10秒)
  • v:輸出詳細信息
  • V:輸出每次嘗試的用戶和密碼

而 http-get-form 的東西和上面說的都差不多只是一個是圖形化界面一個是命令行參數罷了。應該很容易理解。得到的結果如下。

SQL 注入

如果你看上面的源代碼的話,你會發現會有 SQL 注入的問題的。密碼因為有 md5 一下所有不存在注入的問題,但是 $user 沒有。。。所以往 $user 參數的方向去想。 比如 user 是 admin'# 時

    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    //結果會是這樣
    $query = "SELECT * FROM `users` WHERE user = 'admin'#' AND password = '$pass';"
    //mysql 語句中 # 后面都是注釋,就變成
    $query = "SELECT * FROM `users` WHERE user = 'admin'

從而登錄到了 admin 帳號。。。

中級

中級的界面是這樣的


而代碼與前面相比只是多了要用mysqli_real_escape_string函數進行驗證,以及登錄失敗會 sleep(2)

將 用戶名和密碼轉義,比如說 \n 被轉義成 \\n,' 轉義成 \',這可以抵御一些 SQL 注入攻擊,但是不能抵御爆破。代碼如下

<?php

if( isset( $_GET[ 'Login' ] ) ) {
    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        sleep( 2 );
        echo "<pre><br />Username and/or password incorrect.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

用 hydra 試試 hydra 192.168.0.110 -s 5678 -l admin -IVv -P /usr/share/john/password.lst http-get-form "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:incorrect.:H=Cookie: PHPSESSID=pfoju9qirkvqmcnpgb49a2bop4; security=medium" 毫無疑問可以爆破的

高級

高級的頁面代碼

<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="b258081b421c1ee77b3b1d5a53be58ca">
</form>

服務端的代碼

<?php

if( isset( $_GET[ 'Login' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = stripslashes( $user );
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );

    // Check database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        sleep( rand( 0, 3 ) );
        echo "<pre><br />Username and/or password incorrect.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

高級點的代碼的話,會檢查 user_token

意思是,用戶訪問 login.php 的時候就生成一個 token 保存在 session,並讓它登錄的時候發送給服務器,如果服務器有這個 token 就證明這個請求是確實打開了 login.php 才再提交了。

登錄失敗或者根本就沒 token 就將這個 token 從 session 中移除,生成新的 token 再執行之前的操作。

就如注釋所言但這是用來防止 CSRF 攻擊的,所謂的 CSRF 就是比如打開惡意網站,里面有張圖片,或者偽造一個輸入框利用網站 cookies 就可以直接“幫你”做刪除數據之類的操作的。

這段代碼防不了爆破,每次爆破之前獲取頁面的 token 不就可以了嗎。 而 stripslashes 是用來還原 html 詞匯,比如 a\tsay\t\'world\' 之類,就會還原成 a say 'world' 對防御爆破沒什么幫助的。

一些文章會用 python 去寫,我覺得要寫代碼去做功能考慮去做,如果能工具去做的事絕不寫代碼。
(有點奉太郎的味道

所以下面主要是用 brup 實現,而基本操作 在 Kali Linux 中要用 brup v1.3.6 才行。v1.3.5 有錄制宏的bug 。 burp suite 也不復雜。

爆破可以配置成這樣,在作用域(scope)中設置匹配的 Url 及相關的宏,爆破前就能就獲取頁面的 token,並將之放到 url 參數中

下面是設置過程

錄制請求

之后你可以在 DVWA 的 Brute Force 的頁面上輸入帳號 admin 和 密碼,密碼隨便都可以,提交后,發現頁面動不了了,因為 burp suite 攔截了請求了。

此時,如果你點擊 Forward 會將請求會繼續跑

配置作用域

在 target-> sitemap 那里配置作用域

設置匹配條件

在 project options -> session 的 Session Handle Rules 上,配置匹配的項目以及匹配后要做的事。 點擊 Add 按鈕創建匹配 Url 后要做的東西。

這里配置意思是說,這是只有爆破的時候才會啟動(intruder),其他功能不會用到這個規則。

錄制獲取 token 的行為的宏

行為是,在爆破前獲取 token ,並將之放到 url 中。


然后點擊 Add 按鈕,添加宏

選擇需要那個需要獲取 token 的頁面,點擊最下面的 ok 按鈕

配置選項

創建要添加到 url 的參數

填寫要放到參數名,雙擊 b59619b0e13a9016a524e686f47720e2 后幫你自動填好的。然后點擊 ok

配置后如下

然后一直點擊 Ok 就行了。

爆破設置

步驟和之前的一樣。在 target->site map 中找到要爆破的請求

到 Intruder 頁,設置要爆破的參數

配置常用密碼字典

匹配出錯情況

開始爆破

爆破結果如下

不可能級別

到這個級別,利用工具基本無法爆破。來看看代碼吧

<?php

if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Sanitise username input
    $user = $_POST[ 'username' ];
    $user = stripslashes( $user );
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Sanitise password input
    $pass = $_POST[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $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 = strtotime( $row[ 'last_login' ] );
        $timeout    = $last_login + ($lockout_time * 60);
        $timenow    = time();

        /*
        print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
        print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
        print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
        */

        // Check to see if enough time has passed, if it hasn't locked the account
        if( $timenow < $timeout ) {
            $account_locked = true;
            // print "The account is locked<br />";
        }
    }

    // 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();

?> 

這代碼就多了很多鎖定用戶的邏輯了。而且 sql 查詢也不用stripslashes( $pass )mysql_real_escape_string($pass )了,更加簡潔和安全了。覺得最好還是加個驗證碼,比如是錯了三次之后就要填寫驗證碼之類的邏輯。

最后

  1. 任何手段都無法保護弱密碼,如果密碼就是 123456,password之類的弱密碼就不需要爆破也能解決,當你的密碼是有大小寫的英文,有數字,有特殊符號(@#.)之類的8位數以上就非常難被爆破獲得了
  2. 鎖定的用戶的邏輯其實是有漏洞的,假如我討厭一個人,那么我寫個程序每隔一段登錄失敗,他就永遠登錄不了了。。。
  3. 登錄簡單嗎?一個簡單的功能可以很簡單,但也可以很不靠譜,老鳥與菜鳥實現同一種功能可能會考慮很多種情況,為何他會知道這種情況,可能是看書的,可能是看別人寫的代碼,但更有可能是坑過或者曾被坑過。。。這就是老鳥的價值了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM