DVWA全級別通關筆記(三)-- CSRF(跨站請求偽造)


引言

結合DVWA中的CSRF模塊源碼對CSRF漏洞進行一下總結分析。

CSRF,全稱Cross-site request forgery,翻譯過來就是跨站請求偽造,是指利用受害者尚未失效的身份認證信息(cookie、會話等),誘騙其點擊惡意鏈接或者訪問包含攻擊代碼的頁面,在受害人不知情的情況下以受害者的身份向(身份認證信息所對應的)服務器發送請求,從而完成非法操作(如轉賬、改密等)。CSRF與XSS最大的區別就在於,CSRF並沒有盜取cookie而是直接利用。

環境

metasploitable2(包含DVWA)

burpsuit

CSRF

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

?> 

  GET方式得到三個參數,change、password_new、password_conf。如果password_new和password_conf相同,那么更新數據庫,並沒有任何防CSRF的措施。這里我們有多重攻擊方式,比如直接構造鏈接、構造短鏈接和構造攻擊頁面。

構造鏈接:

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

  既然參數是GET方式傳遞的,那么我們可以直接在URL鏈接中設置參數,如果用戶用登陸過該網站的瀏覽器(服務器會驗證cookie)打開這個鏈接,那么將直接把參數傳遞給服務器,因為服務器並沒有防CSRF的措施,所以直接可以攻擊成功,密碼將被改為123456。到那時如果用戶在沒有登陸過這個網站的瀏覽器上打開這個鏈接,並不會更改密碼,而是跳轉到登錄界面。因為服務器在接受訪問時,首先還要驗證用戶的cookie,如果瀏覽器上並沒有之前登錄留下的cookie,那攻擊也就無法奏效。這么看來CSRF攻擊的關鍵就是利用受害者的cookie向服務器發送偽造請求。

短鏈接:

  上面的攻擊鏈接太明顯的,參數直接就在URL中,這樣很容易就會被識破,為了隱藏URL,可以使用生成短鏈接的方式來實現。就是將原鏈接轉換等一個比較短的URL鏈接,打開短鏈接和打開原鏈接等價的。

 

構造攻擊頁面:

真實CSRF攻擊中,攻擊者為了隱藏自己的攻擊手段,可能構造一個假的頁面,然后放在公網上,誘導受害者訪問這個頁面,如果受害者訪問了這個頁面,那么受害者就會在不知情的情況下完成了CSRF攻擊。自己測試可以寫一個本地頁面,也可以利用burpsuit直接生成攻擊頁面代碼。方法如下:

1、抓取更改密碼的數據包,利用engagement tools生成CDRF PoC,訪問點擊提交之后就可以更改密碼。

 

2、復制生成的html代碼即為我們需要的攻擊頁面代碼。

 

3、訪問點擊提交之后就可以更改密碼。

 

medium

  先上源碼:

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

?> 

  stripos(a,b)返回 b 存在於 a,字符串開始的位置,字符串起始位置為0,如果未發現 b 則返回false。代碼檢查了保留變量HTTP_REFERER (http包頭部的Referer字段的值,表示來源地址)是否包含SERVER_NAME(http包頭部的 Host 字段表示要訪問的主機名)。針對這一過濾規則,我們只要想辦法繞過,那么我們后面的代碼和low級別的基本都一樣了,很容易實現CSRF攻擊。由於我是本地phpstudy搭建的DVWA,所以http包中Host字段就是本機---127.0.0.1,而Referer字段就是本地搭建的DVWA頁面的地址,故也包含127.0.0.1。所以這個對於本地搭建的DVWA是無效的,但是在現實場景中一般是不包含的,所以我們可以通過更改頁面文件名來繞過stripos函數。繞過方法:

假如服務器地址為192.168.66.66,即為SERVER_NAME,我們只需要把我們構造的惡意頁面文件名改為192.168.66.66.html,HTTP_REFERER就會包含192.168.66.66.html,就可以繞過stripos了。

 

 

 

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機制,由checkToken函數來實現,用戶每次訪問更改密碼頁面時,服務器會返回一個隨機的token,之后每次向服務器發起請求,服務器會優先驗證token,如果token正確,那么才會處理請求。所以我們在發起請求之前需要獲取服務器返回的user_token,利用user_token繞過驗證。這里我們可以使用burpsuit的CSRF Token Tracker插件可以直接繞過user_token驗證。使用步驟如下:

1、安裝CSRF Token Tracker插件

 

 

 2、進入插件之后添加Host和Name

 

 

3、抓包repeat就ok了

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級別的源碼中也使用驗證user_Token和原始密碼來防止CSRF,如果沒有當前密碼無法進行修改密碼。db->prepare采用的是PDO模式,防止SQL注入。

CSRF防御

Set-Cookie:SameSite

禁止第三方網站帶Cookies,可以在響應頭Set-Cookie設置SameSite屬性,表示Cookie為同源網站而非第三方網站。

驗證碼校驗或CSRF Token

在請求地址中添加token驗證或者驗證碼校驗,驗證碼和CSRF Token都具有隨機性。缺點就是可能會降低用戶體驗。

驗證HTTP Referer字段

Referer字段表示http請求的來源地址,服務器對每一個請求驗證Referer值,查看請求來源是否合法,可以在一定程度上防御CSRF攻擊。

參考

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

Web 安全防御戰 - 淺談CSRF


免責聲明!

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



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