DVWA靶場——Brute Force(暴力破解)
靶場搭建
關於靶場的搭建,網上很多教程,我就不講解了,這裡推薦幾個關於如何在kali系統搭建DVWA靶場教程供大家參考:
https://www.cnblogs.com/aeolian/p/11023238.html#autoid-1-0-0
http://kfbiji.com/article/bb63762f25b17b9c
順便講一下我搭建過程中遇到的錯誤解決經驗。如果出現和下圖一樣的情況,已經搭建成功,但不能登入DVWA靶場,極有可能是因為config.inc.php這個檔案中的配置錯誤,比如"db_password"或者"db_user"這兩個的配置和在MySQL中的配置不一樣,導致登入不了。解決方法就是在config.inc.php
如果出現以下圖片中這種情況,可以在config.inc.php檔案中,把"db_server"中的“127.0.0.1”修改為“localhost”。
Brute Force
概述
在DVWA靶場中,共設立了四個安全級別來供大家學習,這四個級別分為low、Medium、High、impossible,接下來將對此級別分別進行演示。
low級別
在頁面中隨便輸入使用者名稱密碼提交,然後開啟burp suite進行抓包
把抓取到的包傳送到 Intruder模組進行破解
新增使用者名稱和密碼兩個變數,攻擊型別選擇Cluster bomb
點選payloads,在payload type中選擇Runtime file型別,然後選擇已經寫好的字典txt檔案進行爆破(注:payload set 中的1是使用者名稱爆破,2是密碼爆破,2的操作和1一樣)
弄好payload後,點選右上角的start attack開始爆破。
點選length,可以看到有一條的長度和別的是不一樣的,這個就是我們爆破出來的正確的使用者名稱和密碼
拿爆破出來的使用者名稱和密碼去嘗試登陸,是可以登陸成功的
low級別原始碼:
<?phpif( 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); } ?>
low級別的程式碼直接獲取使用者輸入的使用者名稱和密碼,密碼再經過MD5進行加密,所以杜絕了在密碼處進行SQL注入的可能。這裡對輸入的使用者名稱和密碼沒經過任何的過濾和檢查等安全措施處理。
雖然在原始碼中,它對密碼進行MD5加密,杜絕了SQL注入的可能,但是它並沒有對使用者名稱進行任何的安全處理,所以我們可以在使用者名稱這一欄嘗試進行SQL注入。
在使用者名稱這一欄中分別嘗試輸入以下的payload,每個payload都可以注入成功,從而繞過使用者名稱和密碼,登入成功。
SQL注入payload
admin'or '1'='1 admin' -- - admin' #
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); } ?>
觀察Medium級別的原始碼,發現它做了很多限制。mysqli_real_escape_string()函式會對特殊符號(\x00,\n,\r,\,‘,“,\x1a)進行轉義,基本上抵禦了SQL注入(拿在low級別測試的SQL注入payload去嘗試,不能夠登入,說明SQL注入已經失敗)。
它還設定了一個sleep( 2 ),這個意思就是如果登入失敗的話,就會延遲兩秒後才能提交。
雖然在Medium級別中做了過濾、轉義安全措施,但利用在low級別中的爆破方法還是能爆破成功的,只不過時間久了點。
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(); ?>
檢視high級別的原始碼,可以發現它在Medium級別的安全措施基礎上增添了多個安全措施。
stripslashes()函式可以用來去掉反斜線字元("\");使用 Anti-CSRF token,用來隨機生成token值傳送到後臺驗證登入資訊是否正確。
在High級別的程式碼中使用了Anti-CSRF token來抵禦CSRF的攻擊,使用了stripslashes函式和mysqli_real_esacpe_string來抵禦SQL注入和XSS的攻擊,這些安全措施都加大了爆破的難度。
我們使用之前的方法來測試,發現不會成功,長度都是一樣的,狀態全部為302(status為200時則成功)。
以前的方法之所以會失敗,就是因為每次重新整理登入頁面的時候,他都會隨機生成一個token值去到後臺認證
所以我們可以去獲取這個token值,在爆破的同時一併提交該值,這樣就能爆破成功。
要獲取token值可以用python指令碼來獲取(但本人能力不行,這個方法就算了...),還好burpsuite這個神器是可以獲取token值的,只不過麻煩點。
在登入頁面中輸入正確的使用者名稱,密碼隨便輸入,然後bp抓包後,傳送到Intruder模組中,add新增password和user_token兩個變數(注:這裡的使用者名稱必須是正確的,我們只爆破password和user_token),攻擊型別選擇Pitchfork方式(注:Pitchfork攻擊型別是最多隻能選擇兩個變數的)
接下來我們進行關於token值的設定,點選Options,在Grep-Extract中點選add
找到token,點選token值後,它就會自動生成紅框中的值,然後點選ok
點選payloads,payload set中1的設定和之前一樣,只不過得選擇密碼字典
payload set:2中,payload type選擇Recursive grep型別,然後在紅框中輸入bp抓包時,抓取到的token值(也就是user_token的值)
點選開始爆破時會出現報錯:recursivegreppayloadscannotbeusedwithmultiplerequestthreads,這是因為遞迴不能使用多執行緒,所以我們要把多執行緒設定為單執行緒
在Options中,把多執行緒改為1也就是單執行緒,同時在Options 中找到 Redirections設定為Always
設定完後,點選開始爆破,可以看到它在爆破的時候,會自動生成token值一起提交,爆破成功。
Impossible級別
原始碼:
<?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(); ?>
通過原始碼可以看到,impossible級別在high級別的基礎上對使用者登入次數進行了限制,當用戶登入失敗3次後,後臺就會鎖住賬號,在15分鐘之內無法進行任何的操作,同時它也採用了更為安全的機制來抵禦SQL注入。(這個級別看看就行了,厲害的大佬可以去嘗試嘗試)
成功鎖住15分鐘。。。