DVWA 通關指南:Cross Site Request Forgery (CSRF)


Cross Site Request Forgery (CSRF)

CSRF is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. With a little help of social engineering (such as sending a link via email/chat), an attacker may force the users of a web application to execute actions of the attacker's choosing.
CSRF 是一種攻擊手段,它迫使直接用戶在當前經過身份驗證的 web 應用程序上執行不需要的操作。借助社會工程(如通過電子郵件/聊天發送鏈接),攻擊者可能會迫使 web 應用程序的直接用戶執行攻擊者選擇的操作。
A successful CSRF exploit can compromise end user data and operation in case of normal user. If the targeted end user is the administrator account, this can compromise the entire web application.
在正常用戶的情況下,成功利用 CSRF 會危害直接用戶的數據和操作。如果目標直接用戶是管理員帳戶,這可能會危及整個 web 應用程序。
This attack may also be called "XSRF", similar to "Cross Site scripting (XSS)", and they are often used together.
這種攻擊也可以稱為 “XSRF”,類似於“跨站點腳本(XSS)”,它們經常一起使用。
Your task is to make the current user change their own password, without them knowing about their actions, using a CSRF attack.
你的任務是使用CSRF攻擊讓當前用戶在不知道自己的行為的情況下更改自己的密碼。

Low Level

There are no measures in place to protect against this attack. This means a link can be crafted to achieve a certain action (in this case, change the current users password). Then with some basic social engineering, have the target click the link (or just visit a certain page), to trigger the action.
開發者目前還沒有針對這一種攻擊的措施,這意味着可以構建一個鏈接來實現某個操作(在本例中,更改當前用戶的密碼)。然后用一些基本的社會工程,讓目標點擊鏈接(或只是訪問某個頁面)來觸發動作。

源碼審計

源碼如下,服務器收到修改密碼的請求后,將使用 GET 方法接收參數 password_new 和 password_conf,並校驗這 2 個參數是否相同。如果相同,說明用戶輸入新密碼並且確認過了,就會在數據庫中修改密碼。同時我們也可以看到這段源碼中,並沒有任何的防 CSRF 機制。

<?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);
}

?> 

值得一提的是服務器對請求的發送者會利用 cookie 進行身份驗證,這個在源碼中無法體現。

攻擊方式

攻擊者會構造一個 url 如下,當用戶點擊這個連接,並且他的登錄狀態(例如 cookie)還未過期時,他的密碼就會被修改掉。

http://(你的 DVWA 的 url)?password_new=123&password_conf=123&Change=Change#


當然這個 url 還是很明顯的,攻擊者一般為了讓用戶上當會使用諸如 url 壓縮或者進行 url 編碼等操作,讓你看不出是修改密碼的頁面。或者是構造一個假的頁面如下,這個頁面會把修改密碼的界面隱藏,因此從表面上看這像是個普通的 404 頁面。受害者點擊這個連接訪問時,會誤認為自己點了個已經炸掉了的連接,但是 CSRF 攻擊已經完成了。

<img src="(你的 DVWA 的 url)?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>


值得一提的是,CSRF 是利用受害者的 cookie 向服務器發送偽造請求,所以如果受害者使用了沒有 cookie 的另一個瀏覽器登錄時,攻擊不會觸發。

Medium Level

For the medium level challenge, there is a check to see where the last requested page came from. The developer believes if it matches the current domain, it must of come from the web application so it can be trusted.
對於中等級別,網頁會有一個檢查以查看最后請求的頁面來自何處。開發人員認為如果它與當前域匹配,那么它必須來自該 web 應用程序才能被信任。
It may be required to link in multiple vulnerabilities to exploit this vector, such as reflective XSS.
可能需要鏈接多個漏洞來利用此漏洞,例如反射 XSS。

源碼審計

源碼如下,此時網頁就不是直接裝載變量了,而是添加了一些對 CSRF 的防御機制。PHP 的 stripos() 函數用於查找字符串在另一字符串中第一次出現的位置(不區分大小寫),SERVER 是一個包含了諸如頭信息(header)、路徑(path)、以及腳本位置(script locations)等等信息的數組。開發者檢查了保留變量 HTTP_REFERER(http 包頭的 Referer 參數的值,表示來源地址)中是否包含 SERVER_NAME(http 包頭的 Host 參數,要訪問的主機名),希望通過驗證 http 來源的機制抵御 CSRF 攻擊。

<?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);
}

?> 

Referer 字段指示訪問這個網頁的來源,也就是說當訪問這個網頁的來源是其本身時,才可以完成改密碼操作。

攻擊方式

由於 http 包頭的 Referer 字段值中必須包含主機名,所以可以將用於攻擊的網頁文件名改為 http 報頭的 host 字段。例如改密碼的頁面位於 192.168.101.2 上,而攻擊頁面位於 10.10.10.10 上,此時我們可以把攻擊頁面的文件名命名為 “192.168.101.2.html”,這樣在校驗 Referer 字段值時就會通過從而完成 CSRF 攻擊。

High Level

In the high level, the developer has added an "anti Cross-Site Request Forgery (CSRF) token". In order by bypass this protection method, another vulnerability will be required.
開發人員添加了一個“反 CSRF 令牌”,需要聯合使用另一個漏洞才能繞過此保護方法。

源碼審計

源碼如下,代碼加入了 Anti-CSRF token 機制,當用戶每次訪問改密頁面時,服務器會返回一個隨機的 token。向服務器發起請求時,需要提交 token 參數,而服務器在收到請求時會檢查 token,只有 token 正確時才會處理客戶端的請求。

<?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();

?> 

攻擊方式

現在要想進行 CSRF 攻擊就必須獲取到用戶的 token,而要想獲取到 token 就必須利用用戶的 cookie 去訪問修改密碼的頁面,然后截取服務器返回的 token 值。這里可以利用 XSS(Stored) 的 high 級別的漏洞,我們注入一個攻擊腳本,使得每次打開頁面時都彈出 token 值。
注入的 payload 如下,別忘了 high 級別的 XSS(Stored) 需要抓包后改 name 參數。

<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

Impossible Level

In the impossible level, the challenge will extent the high level and asks for the current user's password. As this cannot be found out (only predicted or brute forced), there is not an attack vector here.
該級別下網頁要求用戶提供當前使用的密碼,由於用戶當前使用的密碼是無法發現的(只能爆破),因此這里沒有攻擊載體。

<?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();

?> 

總結

CSRF 跨站請求偽造攻擊是攻擊者利用目標用戶的身份,以目標用戶的名義執行某些錯誤操作的攻擊方式,會極大地威脅用戶的權益。CSRF 攻擊的 2 個重點是:

  1. 目標用戶登錄了網站,並且能正常執行該網站的操作;
  2. 目標用戶訪問了攻擊者制作的攻擊頁面。

CSRF 漏洞的防御方式是驗證請求的 Referer 字段值,如果該字段值是以自己的網站開頭的域名,則說明該請求是來源於自己,就可以通過驗證進行訪問。當該字段值是其他網頁的域名或者空白時,就說明這有可能是 CSRF 攻擊,這時候就應該拒絕這個請求。
不過這種方法能起到的作用有限,因為攻擊者可以用其他方式繞過驗證。還有一種更合適的方式是在請求中放入攻擊者不能夠偽造的信息,例如使用 Anti-CSRF token 機制,讓訪問者需要通過一個隨機生成的 token 進行驗證。還有可以通過一些密保問題,因為這些問題的答案理論上只有訪問者自己知道,攻擊者也無法偽造。

參考資料

新手指南:DVWA-1.9全級別教程之CSRF


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM