CSRF
基本原理
CSRF即Cross Site Request Forgery跨站請(qǐng)求偽造,即通過偽造用戶身份來向網(wǎng)站發(fā)出請(qǐng)求的一種攻擊方式。主要原理為:
- 用戶訪問了正常的網(wǎng)站A,生成Cookie
- 攻擊者引誘用戶訪問惡意網(wǎng)站B
- 網(wǎng)站B攜帶用戶的cookie向A網(wǎng)站發(fā)出請(qǐng)求
- A將B當(dāng)作是用戶執(zhí)行請(qǐng)求
攻擊者在用戶不知情的情況下冒充用戶在A網(wǎng)站執(zhí)行了操作
攻擊類型
GET類型
POST類型
鏈接類型
防御方法
-
阻止不明外域的訪問
- 同源檢測(cè):校驗(yàn) Referer 頭
- Samesite Cookie
-
提交時(shí)要求附加本域才能獲取的信息
- 驗(yàn)證Token
- 雙重Cookie驗(yàn)證
Webgoat題解
3 Basic Get CSRF Exercise

題目的意思是在登錄Webgoat的情況下通過一個(gè)外部的源觸發(fā)請(qǐng)求,在響應(yīng)中便能拿到flag。
先點(diǎn)擊Submit Query,得到響應(yīng):

這里提示我們當(dāng)前這個(gè)請(qǐng)求是從同一個(gè)源發(fā)出的,查看headers:

這里有三個(gè)屬性:
- Host表示請(qǐng)求的目的地,包括域名和端口號(hào)
- Origin表示請(qǐng)求是從哪發(fā)起的,包括協(xié)議、域名、端口號(hào)
- Referer表示當(dāng)前頁(yè)面的來源完整地址,包括協(xié)議、域名、查詢參數(shù)
這里我們用burpsuite抓包后嘗試修改Origin為一個(gè)外部網(wǎng)站:

沒有成功,那么試試修改Referer:

進(jìn)一步,我們直接把Referer頭去掉:

竟然也可以,看來這里的機(jī)制還是通過檢測(cè)Referer來判斷發(fā)出的請(qǐng)求是否同源。
這里有一個(gè)問題,為什么修改Origin不起作用,而修改Referer起作用了。
4 Post a review on someone else’s behalf

第4題的要求以當(dāng)前登錄WebGoat身份的用戶提交評(píng)論,這里的要求也是從外部站點(diǎn)提交:

因此我們像上一題一樣,修改Referer為外部站點(diǎn),實(shí)際上,我們這樣的直接抓包修改header是在走捷徑,正常的方式是構(gòu)造一個(gè)釣魚網(wǎng)站將執(zhí)行請(qǐng)求的鏈接寫入,并誘導(dǎo)用戶點(diǎn)擊,點(diǎn)擊時(shí)瀏覽器會(huì)自動(dòng)把cookie添加在headers中,從而達(dá)到偽造用戶身份進(jìn)行請(qǐng)求。

刷新一下則會(huì)發(fā)現(xiàn)我們的評(píng)論出現(xiàn)了,本題通過。
7 CSRF and content-type
這道題目要求從以json形式從外部提交表單,以實(shí)現(xiàn)CSRF攻擊:

因此關(guān)鍵點(diǎn)如下:
- 以json形式提交表單數(shù)據(jù)
- 跨域提交
這里涉及幾個(gè)知識(shí)點(diǎn)需要提前說明一下:
JSON數(shù)據(jù)
全稱為JavaScript Object Notation,是一種記錄數(shù)據(jù)的格式標(biāo)準(zhǔn),形式為key:value。比如:
{
"name": "Ares",
"email":"Ares@BS.com",
"Company":"Black Swan"
}
在網(wǎng)頁(yè)中常用json格式提交表單數(shù)據(jù)。
XHR
全稱 XMLHttpRequest是一種用于與服務(wù)器進(jìn)行交互的瀏覽器API,通過XHR可以在不刷新頁(yè)面的情況下請(qǐng)求特定URL,獲取數(shù)據(jù)。在載入json數(shù)據(jù)時(shí),就需要用到 XHR對(duì)象。
CORS
全稱Cross Resource Share 跨域資源共享,是一種基于HTTP頭的機(jī)制,用于標(biāo)識(shí)出瀏覽器可以訪問哪些不同源的域、協(xié)議和端口。
CORS允許瀏覽器向跨源服務(wù)器發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制。
CORS包含了一種預(yù)檢請(qǐng)求的機(jī)制,預(yù)檢的作用就是把將要發(fā)送的HTTP請(qǐng)求去掉body,只保留請(qǐng)求頭發(fā)送,以此來檢測(cè)服務(wù)器是否允許該請(qǐng)求。預(yù)檢請(qǐng)求的方法就是OPTIONS。
瀏覽器將CORS請(qǐng)求分為兩類:簡(jiǎn)單請(qǐng)求與非簡(jiǎn)單請(qǐng)求
簡(jiǎn)單請(qǐng)求
滿足以下條件:
-
請(qǐng)求方法為以下三種:
- HEAD
- GET
- POST
-
HTTP頭信息不超出以下字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:必須為這三個(gè)值其一
application/x-www-form-urlencoded、multipart/form-data、text/plain
對(duì)于簡(jiǎn)單請(qǐng)求,瀏覽器會(huì)在請(qǐng)求頭中包含一個(gè)Origin字段,標(biāo)明此請(qǐng)求的來源,如果服務(wù)器認(rèn)為該來源不在許可的范圍內(nèi),則會(huì)返回一個(gè)不包含Access-Control-Alllow-Origin的字段,瀏覽器就會(huì)發(fā)現(xiàn)請(qǐng)求出錯(cuò)。如果請(qǐng)求來源在許可范圍內(nèi),服務(wù)器將返回包含以下字段的響應(yīng):
Access-Control-Allow-Origin: http://api.bob.com /*值為*或者Origin字段的值/
Access-Control-Allow-Credentials: true /*表示是否允許發(fā)送Cookie)
Access-Control-Expose-Headers: FooBar /*表示getResponseHeader()方法可以拿到其他字段的值
Content-Type: text/html; charset=utf-8
非簡(jiǎn)單請(qǐng)求就是不滿足上述兩個(gè)條件的其他請(qǐng)求,例如使用POST、DELETE方法,content-Type為application/json。
非簡(jiǎn)單請(qǐng)求在執(zhí)行正式請(qǐng)求之前會(huì)以OPTIONS方法發(fā)送一個(gè)預(yù)檢請(qǐng)求,這個(gè)預(yù)檢請(qǐng)求就是把真實(shí)的請(qǐng)求頭單獨(dú)拉出來,先進(jìn)行一次查詢,探測(cè)服務(wù)器是否允許來自當(dāng)前域名的請(qǐng)求以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有獲得服務(wù)器的允許答復(fù),瀏覽器才會(huì)發(fā)出正式的請(qǐng)求。
詳情參考:https://blog.csdn.net/yexudengzhidao/article/details/100104134
再回到這個(gè)題

我們?nèi)绻苯犹峤话?code>json數(shù)據(jù)的請(qǐng)求,是非簡(jiǎn)單請(qǐng)求,就需要經(jīng)過CORS預(yù)檢,然而CSRF是一個(gè)跨域提交的漏洞。因此這道題目有兩種場(chǎng)景:
-
未驗(yàn)證Content-Type:在
form中拼接json數(shù)據(jù)提交 - 驗(yàn)證Content-Type:
我們嘗試第一種方式:
構(gòu)造 POC如下:
<html>
<title>JSON CSRF PIC </title>
<form action="http://172.17.0.1:8080/WebGoat/start.mvc#lesson/CSRF.lesson/6" method="POST" enctype="text/plain" >
<input name='{"name":"WebGoat","email":"webgoat@webgoat.org","subject":"suggestions","message":"' value='WebGoat is the best!!"}' type='hidden'>
</form>
<script>
document.forms[0].submit();
</script>
</html>
這里的關(guān)鍵也就是上文所說的將 json拼接在input屬性中,也就是下面這行:
<input name='{"name":"WebGoat","email":"webgoat@webgoat.org","subject":"suggestions","message":"' value='WebGoat is the best!!"}' type='hidden'>
通過構(gòu)造將需要提交的json數(shù)據(jù)分為兩段,分別拼接在name與value后的值中,交由瀏覽器解析即可。解析結(jié)果如下:

可以看到message中的值多了一個(gè)=,這是因?yàn)闉g覽器在解析input中的值時(shí),會(huì)自動(dòng)以name=value形式補(bǔ)全:

就算不寫value的值,瀏覽器依然會(huì)自動(dòng)不全=:

這就是為什么如果只將json放在name里,不寫value值的話,json數(shù)據(jù)末尾會(huì)多一個(gè)=,自然也就無法解析成功了。
按道理來說將構(gòu)造的html文件從瀏覽器打開就應(yīng)該已經(jīng)成功了,但我卻收到了如下響應(yīng):

對(duì)比一下從網(wǎng)頁(yè)正常提交的請(qǐng)求頭

修改為:

就可以拿到flag啦!