簡介
Cross-Site Request Forgery(CSRF)是一種Web前端攻擊方式,它通過在站點、email中嵌入一段惡意JS或者HTML代碼,當擁有信任站點登錄態的用戶在訪問這些站點、email時,惡意代碼執行并造成瀏覽器對信任站點發送帶有用戶登錄態的HTTP/S請求,執行一些非用戶意愿的操作
簡單來說,CSRF是在用戶不知情的情況下,通過瀏覽器冒用其身份發起HTTP/S請求
根據站點暴露的功能和擁有登錄態用戶的權限,CSRF可以造成的危害從轉移個人用戶個人資金、修改用戶密碼到用利用管理員身份對整個站點進行修改,在利用站點關系網絡的情況下,CSRF可以直接或間接利用XSS漏洞造成大面積攻擊
CSRF利用的是瀏覽器在用 <a>,<img src="">,<form>,<script> 等標簽向跨域的服務器發送請求時會默認帶上Cookie頭部,而Cookie一般用來維持用戶登錄態,在用戶不知情的情況下,惡意站點跨域發送了帶有用戶Cookie的請求,就等于偽造了用戶的請求,由于是瀏覽器發送的請求,在請求只是讀取數據的情況下,最后也只是被用戶獲取到,沒有什么危害,但是在請求會改變服務器狀態(數據庫內容等)情況下,請求就產生了危害
威脅場景
首先來看看CSRF攻擊的條件
- 沒有CSRF防御的請求接口且接口的功能必須要改變服務器狀態(數據等信息)
- 用戶必須處在登錄狀態
- 構造有利用漏洞請求的頁面并引導用戶通過瀏覽器訪問
接下來的場景里,我們假設自己為Alice,要給Bob轉賬,而Maria是壞人
GET請求下的漏洞利用
在Web系統 bank.com 中比如下一條請求表示Alice會轉賬給Bob,服務器接到請求后會進行轉賬操作
在普通情況下,因為Web頁面有用Cookie等身份驗證,在不知道Cookie的情況下可以保證Alice的操作是正確轉賬給Bob的
GET /transfer.do?acct=BOB&amount=1000 HTTP/1.1
Host: bank.com
Cookie: id=a874920387490283740928374
Maria在 hacker.com 構造了如下的頁面
<a >View</a>
<img src="http://bank.com/transfer.do?acct=MARIA&amount=100000" width="0" height="0" border="0">
Maria在Alice登錄了 bank.com 的時候(也就是瀏覽器記錄了Alice在這個頁面上的Cookie)誘導Alice去訪問 hacker.com ,此時頁面在瀏覽器中加載的過程中 <img src=""> 標簽便會產生一條以Alice身份發送的請求,如果Alice點擊了 <a> 標簽的連接也會發送以Alice身份發送的請求,因為它們都帶有Alice的Cookie
至此滿足的CSRF漏洞利用的三個條件
- 沒有CSRF防御的請求接口且接口的功能必須要改變服務器狀態(數據等信息)
- 用戶必須處在登錄狀態
- 構造有利用漏洞請求的頁面并引導用戶通過瀏覽器訪問
POST請求下的漏洞利用
和上面的GET請求相同,我們類比一下, 繞過詳盡的說明
正常請求如下
POST http://bank.com/transfer.do HTTP/1.1
Cookie: id=aljdflajsdhflasdfasf
acct=BOB&amount=100
Maria構造的惡意頁面如下
<body onload="document.forms[0].submit()">
<form action="http://bank.com/transfer.do" method="POST">
<input type="hidden" name="acct" value="MARIA"/>
<input type="hidden" name="amount" value="100000"/>
<input type="submit" value="View my pictures"/>
</form>
此時誘導有登錄態的Alice去訪問惡意頁面,頁面一加載便會以Alice的身份發送POST請求,改變服務器狀態(某些瀏覽器可能會禁止這種一加載就發表單請求的行為)
其他HTTP方法的利用方式
理論上來講其他的HTTP方法比如OPTIONS和PUT都存在這種問題且利用方式一樣,但是我并沒有實驗,因為其他方式使用還是少的并會越來越少,而且考慮到請求必須能改變服務器狀態的限制,類似OPTIONS的方法也很少有直接改變服務器狀態的,因此略過
JSON Hijacking讀取數據的利用方式
上面說到的CSRF都是改變服務器狀態的寫數據的情況,但存在一種讀數據的情況,就是JSON Hijacking
JSON Hijacking是JSONP帶來的風險,但屬于CSRF的范疇,因為仍然是因為 <script> 標簽發送的請求帶有Cookie造成的
典型的惡意頁面如下
<script>
function hacker(v) {
alert(v.data)
}
</script>
<script src="http://bank.com/?jsoncallbackfunc=hacker>
bank.com 對JSONP的處理大概是
jsoncallbackfunc = request.args.get("jsoncallbackfunc")
return "{}({'data':'data'})".format(jsoncallbackfunc)
此時惡意頁面便可以獲取用戶數據
和XSS結合的蠕蟲
注意:我們討論CSRF漏洞的前提是沒有XSS漏洞
XSS不是CSRF工作的必要條件,但是,XSS可以簡單地讀取頁面內容并用XMLHttpRequest生成CSRF請求,參考[MySpace(Samy) worm事件](https://en.wikipedia.org/wiki/Samy_(computer_worm)]
為了CSRF防御可以起效,沒有XSS漏洞是必要的
修復方式
攻擊可以成立的本質:所有的參數都可以被攻擊者偽造
自動防御CSRF攻擊的一般建議(不需要用戶干預,并且只允許同源,不允許CORS跨域),步驟如下:
- 檢查標準頭部驗證請求是否同源
- 檢查CSRF token
通過標準頭部驗證請求是否同源
有兩步檢查
- 確定Source Origin(request來源同源)
- 確定Target Origin(request去向同源)
這兩個步驟都依賴于檢查HTTP請求頭,雖然用JS可以在瀏覽器上偽造這些頭部,但是一般是不可能做到在受害者瀏覽器CSRF攻擊過程中修改這些頭,除非有XSS漏洞
更重要的是,對于這個推薦的同源檢查,一些HTTP頭不能由JS設置,因為它們位于"禁止"標題列表中,只有瀏覽器本身可以未這些HEADER設定值,這使它們更值得信任,因為XSS也不能修改它們
這里推薦來源檢查依賴三個保護頭:Origin、Referer和HOST
檢查Source Origin
- Origin Header
- IE11在CORS請求中可能不帶Origin
- 302 redirect cross-origin可能不帶
- Referer Header
- 兩個頭部都沒有
- 在沒有CSRF token的情況下建議拒絕請求
- 在可以查詢記錄的情況下這種情況持續一段時間要求用戶重新登錄,沒有站點沒有這種查詢記錄的基礎功能,就拒絕請求
檢查Target Origin
- Host HEADER檢查
- 同樣可能被代理修改
X-Frowarded-Host檢查
- 注意各個主流瀏覽器支持情況
- 匹配URL不可以,因為在有類似反向代理的情況下,URL的值和target origin并不一定一樣,除非可以確定直接來自用戶
兩種都檢查,并且匹配Source Origin和Target Origin是否相同,不同則可能產生CSRF問題
CSRF Token類型的防御(CSRF Token和其他高安全性的頭部)
- Synchronizer (CSRF) Token
- Double Cookie Defense
- Encrypted Token Pattern
- Custom Header - e.g., X-Requested-With: XMLHttpRequest
Synchronizer (CSRF) Token防御
- 任何能使服務器狀態變化的請求(最好使用POST方法),都需要一個安全隨機的Token防止CSRF攻擊
- CSRF Token的特性
- 對每個用戶session唯一
- 長隨機值
- 用Secure Random Generator(一般語言自帶Secure Random庫)和密碼算法生成
- Token用用戶不可見的方式加在POST請求的參數中(From表單的hidden標簽等)
- 當CSRF Token驗證出錯服務器拒絕請求
使用時頁面代碼大致如下
<form action="/transfer.do" method="post">
<input type="hidden" name="CSRFToken"
value="OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWE...
wYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZ...
MGYwMGEwOA==">
…
</form>
實現可以參考的代碼資料:https://github.com/aramrami/OWASP-CSRFGuard
當不得不使用GET方法去改變服務器狀態時,存在Token放在URL中泄露的問題
可能泄露的地方
- 瀏覽器歷史
- HTTP日志文件
- proxies留有日志
- referer頭,如果站點鏈接到外部網站,HTTPS和HTTP轉換中去掉HTTPS的情況
所以在不得不使用GET方法去改變服務器狀態時,CSRF Token可以作為臨時策略,但要加上一個較短的時效
Double Submit Cookie
這種方式和上面不同的是,這個Synchronizer (CSRF) Token分別加到了用戶的Cookie的參數和提交的參數中,
這樣服務器端就不用存儲Token了
因為UI和服務請求未必來自同一服務器(API和頁面不同服務器提供),在UI服務器返回的UI中用JS向API服務器發送請求,需要JS讀取Cookie加到參數中,確保參數有Token的性質,不會被偽造,因此也最好用在POST方法中,而因為攻擊者無法讀取Cookie的值,因此也無法獲取Token的值,不同的服務器也無需存儲用戶Cookie
Encrypted Token Pattern
這種方式和上面的Double Submit Cookie有點類似,不同的是,這種方法即需要服務器端存儲Token,也不需要存儲用戶Session
用戶訪問時用戶ID(用戶認證方式,存在Cookie或者Session中)、時間戳和隨機數等參數加密生成一個Token,服務器端保留加密算法的Key,將Token放在POST方法的參數中,如果用戶請求時解密結果正確則是正常訪問
這種方式的網站站點往往是將Session保存在Cookie中,服務器不記錄Session數據,如Python的Flask框架是我常用的框架,它就使用這種方式
Custom Header
加HTTP Header中增加一個自定義字段,存儲Token
適合Ajax的防御,自定義請求頭部,默認情況下,JS可以定義請求頭,但瀏覽器不允許JS跨域請求,因為不用擔心惡意站點的偽造的請求會帶上這個請求頭
有用戶交互的防御方式
- 發現不正常請求重新認證用戶
- One-Time Token(手機驗證碼)
- CAPTCHA(人機識別的驗證碼)
samesit屬性
Chrome和Firefox支持SameSite屬性,防止向第三方站點送Cookie
Set-Cookie: JSESSIONID=xxxxx; SameSite=Strict Set-Cookie: JSESSIONID=xxxxx; SameSite=Lax
總結
改變服務器狀態的請求只使用POST
注意JSON Hijacking的特殊情況會導致用戶數據因為CSRF被讀取,也就是注意JSONP的使用
使用對用戶透明的安全令牌,和CSRF Token類似,但不是專門防御CSRF Token,是一種良好習慣,重要的是連接開始時生成一個隨機串,和用戶時間等參數綁定
注意CSRF與XSS的區別
- XSS是用戶信任網站,放任來自網站的網站在瀏覽器上任意執行
- CSRF是網站過分信任用戶,放任通過網站設定的訪問控制方式的用戶對服務器狀態進行任意改變
浙公網安備 33010602011771號