1. 程式人生 > >DVWA 黑客攻防實戰(三)暴力破解 Brute Froce

DVWA 黑客攻防實戰(三)暴力破解 Brute Froce

暴力破解,簡稱”爆破“。不要以為沒人會對一些小站爆破。實現上我以前用 wordpress 搭建一個部落格開始就有人對我的站點進行爆破。這是裝了 WordfenceWAF 外掛後的統計的情況。

裝了 WordfenceWAF 看到報告就深刻感受到國際友人對我這破站的安全性的深刻關懷了。你不封他們的 ip ,他們的程式就會像中了 “奇淫合歡散” 那些對你的網站鍥而不捨地爆破。而下面會從 dvma 中學習如何爆破和如何防爆破。

初級

初級在 初步認識網路漏洞 一文中有介紹了,如果你是從那篇文章過來的,請跳過。

頁面是這樣的。

很簡單的登入,程式碼可以點選 view source 可以看到

<?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 = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

    if( $result && mysql_num_rows( $result ) == 1 ) {
        // Get users details
        $avatar = mysql_result( $result, 0, "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>";
    }

    mysql_close();
}

?>

這提交太簡單,簡直引人犯罪,來看看人類滿滿的惡意吧。下面介紹在 Kali Linux 中比較常用的兩個工具 burp suite 和 hydra

Burp Suite

Burp Suite 是 Kali Linux 中預裝的非常強大的滲透工具。在這個部分主要用來抓包和進行吧爆破。(可以在這裡下載,win,mac都可以用的

將安全等級設定為 low 後,開啟 burp suite,設定火狐瀏覽器的網路走 burp suite 的代理(即localost:8080,如圖所示),讓 burp suite 攔截火狐的請求

如果你要修改 burp suite 埠的話,你可以在 proxy-> options 中修改

之後你可以在 DVWA 的 Brute Force 的頁面上輸入帳號 admin 和 密碼,密碼隨便都可以,提交後,發現頁面動不了了,因為 burp suite 攔截了請求了。

此時,如果你點選 Forward 會將請求跑完。如果錯過了,可以在 Proxy->Http History 和 target 那裡都能找到之前的記錄
現在我的操作是將請求的資訊傳送到入侵器(intruder)。

然後在入侵器那裡點選 clear ,清理掉所有的變數,然後再新增 password 部分。也就是爆破點只有 password 部分。

然後點選 payloads,新增一份常用密碼(Kali Linux 在 /usr/share/john/password.lst),並移除註釋。

然後再設定爆破的標準,因為密碼錯誤的時候會提示 Username and/or password incorrect 。所以報文如果不能匹配到 incorrect,就說明密碼爆破成功。而這在 Options 那裡設定成這樣就行了

然後再點選右上角的 start attack 就可以了。結果如下

明顯 password 就是密碼了

Hydra

爆破的原理是一樣的,不過 hydra 用的是命令列。而根據上面抓包得出的資訊。(文件可以看這裡) 可以馬上用這條命令進行爆破。

hydra 192.168.31.166 -s 5678 -l admin -IVv -P /usr/share/john/password.lst http-get-form "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:incorrect.:H=Cookie: security=low;PHPSESSID=isk2inn2psu1sh56slicq6oim7"

常用引數

  • s :埠
  • l:使用者名稱
  • L:使用者列表檔案
  • p:密碼
  • P:密碼檔案
  • I:忽略現有的恢復檔案,強制退出就有恢復檔案了(不需要等10秒)
  • v:輸出詳細資訊
  • V:輸出每次嘗試的使用者和密碼

而 http-get-form 的東西和上面說的都差不多隻是一個是圖形化介面一個是命令列引數罷了。應該很容易理解。得到的結果如下。

SQL 注入

如果你看上面的原始碼的話,你會發現會有 SQL 注入的問題的。密碼因為有 md5 一下所有不存在注入的問題,但是 $user 沒有。。。所以往 $user 引數的方向去想。 比如 user 是 admin'# 時

    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    //結果會是這樣
    $query = "SELECT * FROM `users` WHERE user = 'admin'#' AND password = '$pass';"
    //mysql 語句中 # 後面都是註釋,就變成
    $query = "SELECT * FROM `users` WHERE user = 'admin'

從而登入到了 admin 帳號。。。

中級

中級的介面是這樣的


而程式碼與前面相比只是把用mysql_real_escape_string函式

將 使用者名稱和密碼轉義,比如說 \n 被轉義成 \\n,' 轉義成 \',這可以抵禦一些 SQL 注入攻擊,但是不能抵禦爆破。程式碼如下

<?php 

if( isset( $_GET[ 'Login' ] ) ) { 
    // Sanitise username input 
    $user = $_GET[ 'username' ]; 
    $user = mysql_real_escape_string( $user ); 

    // Sanitise password input 
    $pass = $_GET[ 'password' ]; 
    $pass = mysql_real_escape_string( $pass ); 
    $pass = md5( $pass ); 

    // Check the database 
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
    $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

    if( $result && mysql_num_rows( $result ) == 1 ) { 
        // Get users details 
        $avatar = mysql_result( $result, 0, "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>"; 
    } 

    mysql_close(); 
} 

?>

用 hydra 試試 hydra 192.168.31.166 -s 5678 -l admin -IVv -P /usr/share/john/password.lst http-get-form "/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:incorrect.:H=Cookie: security=medium;PHPSESSID=v8n8jjdg6d8fjat7b0cnroof11" 毫無疑問可以爆破的

高階

高階的頁面程式碼

<form action="#" method="GET">
    Username:<br>
    <input type="text" name="username"><br>
    Password:<br>
    <input type="password" autocomplete="off" name="password"><br>
    <br>
    <input type="submit" value="Login" name="Login">
    <input type="hidden" name="user_token" value="b258081b421c1ee77b3b1d5a53be58ca">
</form>

服務端的程式碼

<?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 = mysql_real_escape_string( $user ); 

    // Sanitise password input 
    $pass = $_GET[ 'password' ]; 
    $pass = stripslashes( $pass ); 
    $pass = mysql_real_escape_string( $pass ); 
    $pass = md5( $pass ); 

    // Check database 
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
    $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

    if( $result && mysql_num_rows( $result ) == 1 ) { 
        // Get users details 
        $avatar = mysql_result( $result, 0, "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>"; 
    } 

    mysql_close(); 
} 

// Generate Anti-CSRF token 
generateSessionToken(); 

?>

高階點的程式碼的話,會檢查 user_token

意思是,使用者訪問 login.php 的時候就生成一個 token 儲存在 session,並讓它登入的時候傳送給伺服器,如果伺服器有這個 token 就證明這個請求是確實打開了 login.php 才再提交了。

登入失敗或者根本就沒 token 就將這個 token 從 session 中移除,生成新的 token 再執行之前的操作。

就如註釋所言但這是用來防止 CSRF 攻擊的,所謂的 CSRF 就是比如開啟惡意網站,裡面有張圖片,或者偽造一個輸入框利用網站 cookies 就可以直接“幫你”做刪除資料之類的操作的。

這段程式碼防不了爆破,每次爆破之前獲取頁面的 token 不就可以了嗎。 而 stripslashes 是用來還原 html 詞彙,比如 a\tsay\t\'world\' 之類,就會還原成 a say 'world' 對防禦爆破沒什麼幫助的。

一些文章會用 python 去寫,我覺得要寫程式碼去做功能考慮去做,如果能工具去做的事絕不寫程式碼。
(有點奉太郎的味道

所以下面主要是用 brup 實現,而基本操作 在 Kali Linux 中要用 brup v1.3.6 才行。v1.3.5 有錄製巨集的bug 。 burp suite 也不復雜。

爆破可以配置成這樣,在作用域(scope)中設定匹配的 Url 及相關的巨集,爆破前就能就獲取頁面的 token,並將之放到 url 引數中

下面是設定過程

設定火狐瀏覽器

開啟 burp suite,設定火狐瀏覽器的網路走 burp suite 的代理(即localost:8080,如圖所示),讓 burp suite 攔截火狐的請求

之後你可以在 DVWA 的 Brute Force 的頁面上輸入帳號 admin 和 密碼,密碼隨便都可以,提交後,發現頁面動不了了,因為 burp suite 攔截了請求了。

此時,如果你點選 Forward 會將請求會繼續跑。如果不想再攔截,想請求直接通過,可以點選 intercept 按鈕。然後跑 Brute Force 頁面,並點選登入按鈕傳送一次請求。

配置作用域


設定匹配條件

在 project options -> session 的 Session Handle Rules 上,配置匹配的專案以及匹配後要做的事。 點選 Add 按鈕建立匹配 Url 後要做的東西。

這裡配置意思是說,這是隻有爆破的時候才會啟動(intruder),其他功能不會用到這個規則。

錄製獲取 token 的行為的巨集

行為是,在爆破前獲取 token ,並將之放到 url 中。


然後點選 Add 按鈕,新增巨集

選擇需要那個需要獲取 token 的頁面,點選最下面的 ok 按鈕

配置選項

建立要新增到 url 的引數

填寫要放到引數名,雙擊 b59619b0e13a9016a524e686f47720e2 後幫你自動填好的。然後點選 ok

配置後如下

然後一直點選 Ok 就行了。

 

爆破設定

步驟和之前的一樣。

到 Intruder 頁,設定要爆破的引數

配置常用密碼字典

匹配出錯情況

開始爆破

爆破結果如下

不可能級別

到這個級別,利用工具基本無法爆破。來看看程式碼吧

<?php 

if( isset( $_POST[ 'Login' ] ) ) { 
    // Check Anti-CSRF token 
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 

    // Sanitise username input 
    $user = $_POST[ 'username' ]; 
    $user = stripslashes( $user ); 
    $user = mysql_real_escape_string( $user ); 

    // Sanitise password input 
    $pass = $_POST[ 'password' ]; 
    $pass = stripslashes( $pass ); 
    $pass = mysql_real_escape_string( $pass ); 
    $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. 
    // 是否應該鎖定使用者,如果登入失敗的次數多於 3次(total_failed_login),就鎖定使用者,在鎖定時間內不能再登入。
    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 = $row[ 'last_login' ]; 
        $last_login = strtotime( $last_login ); 
        $timeout    = strtotime( "{$last_login} +{$lockout_time} minutes" ); 
        $timenow    = strtotime( "now" ); 

        // Check to see if enough time has passed, if it hasn't locked the account 
        if( $timenow > $timeout ) 
            $account_locked = true; 
    } 

    // 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 
        // 登入失敗的次數+1s
        $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(); 

?>

這程式碼就多了很多鎖定使用者的邏輯了。而且 sql 查詢也不用stripslashes( $pass )mysql_real_escape_string($pass )了,更加簡潔和安全了。覺得最好還是加個驗證碼,比如是錯了三次之後就要填寫驗證碼之類的邏輯。

最後

  1. 任何手段都無法保護弱密碼,如果密碼就是 123456,password之類的弱密碼就不需要爆破也能解決,當你的密碼是有大小寫的英文,有數字,有特殊符號(@#.)之類的8位數以上就非常難被爆破獲得了
  2. 鎖定的使用者的邏輯其實是有漏洞的,假如我討厭一個人,那麼我寫個程式每隔一段登入失敗,他就永遠登入不了了。。。
  3. 登入簡單嗎?一個簡單的功能可以很簡單,但也可以很不靠譜,老鳥與菜鳥實現同一種功能可能會考慮很多種情況,為何他會知道這種情況,可能是看書的,可能是看別人寫的程式碼,但更有可能是坑過或者曾被坑過。。。這就是老鳥的價值了。