科普:對于XSS和CSRF你究竟了解多少
隨著Web2.0、社交網(wǎng)絡、微博等等一系列新型的互聯(lián)網(wǎng)產(chǎn)品的誕生,基于Web環(huán)境的互聯(lián)網(wǎng)應用越來越廣泛,企業(yè)信息化的過程中各種應用都架設在Web平臺上,Web業(yè)務的迅速發(fā)展也引起黑客們的強烈關注,接踵而至的就是Web安全威脅的凸顯。
黑客利用網(wǎng)站操作系統(tǒng)的漏洞和Web服務程序的SQL注入漏洞等得到Web服務器的控制權限,輕則篡改網(wǎng)頁內容,重則竊取重要內部數(shù)據(jù),更為嚴重的則是在網(wǎng)頁中植入惡意代碼,使得網(wǎng)站訪問者受到侵害。
如今,Web安全成為焦點,但網(wǎng)站的漏洞還是頻頻出現(xiàn),在白帽子們進行網(wǎng)站測試時,恐怕對于SQL注入、XSS跨站、CSRF接觸最多,但對于網(wǎng)站的開發(fā)者們來說,對這些熟知多少?本文從開發(fā)者的角度,對于XSS和CSRF進行簡要概述。
PART1 XSS跨站腳本(Cross-site scripting)
XSS成因概括 :
XSS其實就是Html的注入問題,攻擊者的輸入沒有經(jīng)過嚴格的控制進入了數(shù)據(jù)庫,最終顯示給來訪的用戶,導致可以在來訪用戶的瀏覽器里以瀏覽用戶的身份執(zhí)行Html代碼,數(shù)據(jù)流程如下:攻擊者的Html輸入—>web程序—>進入數(shù)據(jù)庫—>web程序—>用戶瀏覽器。
檢測方法:
//通常有一些方式可以測試網(wǎng)站是否有正確處理特殊字符:
- ><script>alert(document.cookie)</script>
- ='><script>alert(document.cookie)</script>
- "><script>alert(document.cookie)</script>
- <script>alert(document.cookie)</script>
- <script>alert(vulnerable)</script>
- %3Cscript%3Ealert('XSS')%3C/script%3E
- <script>alert('XSS')</script>
- <img src="javascript:alert('XSS')">
- <img src="http://xxx.com/yyy.png" onerror="alert('XSS')">
- <div style="height:expression(alert('XSS'),1)" />(這個僅限 IE 有效)
攻擊手段和目的:
攻擊者使被攻擊者在瀏覽器中執(zhí)行腳本后,如果需要收集來自被攻擊者的數(shù)據(jù)(如cookie或其他敏感信息),可以自行架設一個網(wǎng)站,讓被攻擊者通過JavaScript等方式把收集好的數(shù)據(jù)作為參數(shù)提交,隨后以數(shù)據(jù)庫等形式記錄在攻擊者自己的服務器上。
a. 盜用 cookie ,獲取敏感信息。
b.利用植入 Flash ,通過 crossdomain 權限設置進一步獲取更高權限;或者利用Java等得到類似的操作。
c.利用 iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻擊)用戶的身份執(zhí)行一些管理動作,或執(zhí)行一些一般的如發(fā)微博、加好友、發(fā)私信等操作。
d.利用可被攻擊的域受到其他域信任的特點,以受信任來源的身份請求一些平時不允許的操作,如進行不當?shù)耐镀被顒印?/p>
e.在訪問量極大的一些頁面上的XSS可以攻擊一些小型網(wǎng)站,實現(xiàn)DDoS攻擊的效果。
漏洞的防御和利用:
避免XSS的方法之一主要是將用戶所提供的內容進行過濾,許多語言都有提供對HTML的過濾:
PHP的htmlentities()或是htmlspecialchars()。 Python的cgi.escape()。 ASP的Server.HTMLEncode()。 ASP.NET的Server.HtmlEncode()或功能更強的Microsoft Anti-Cross Site Scripting Library Java的xssprotect(Open Source Library)。 Node.js的node-validator。
使用HTTP頭指定類型:
很多時候可以使用HTTP頭指定內容的類型,使得輸出的內容避免被作為HTML解析。如在PHP語言中使用以下代碼:
header ('Content-Type: text/javascript; charset=utf-8');
即可強行指定輸出內容為文本/JavaScript腳本(順便指定了內容編碼),而非可以引發(fā)攻擊的HTML。
PART2 CSRF:冒充用戶之手
示意圖:
XSS 是實現(xiàn) CSRF 的諸多途徑中的一條,但絕對不是唯一的一條。一般習慣上把通過 XSS 來實現(xiàn)的 CSRF 稱為 XSRF。
CSRF 顧名思義,是偽造請求,冒充用戶在站內的正常操作。我們知道,絕大多數(shù)網(wǎng)站是通過 cookie 等方式辨識用戶身份(包括使用服務器端 Session 的網(wǎng)站,因為 Session ID 也是大多保存在 cookie 里面的),再予以授權的。所以要偽造用戶的正常操作,最好的方法是通過 XSS 或鏈接欺騙等途徑,讓用戶在本機(即擁有身份 cookie 的瀏覽器端)發(fā)起用戶所不知道的請求。
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
1.登錄受信任網(wǎng)站A,并在本地生成Cookie。
2.在不登出A的情況下,訪問危險網(wǎng)站B。
看到這里,你也許會說:“如果我不滿足以上兩個條件中的一個,我就不會受到CSRF的攻擊”。是的,確實如此,但你不能保證以下情況不會發(fā)生:
1.你不能保證你登錄了一個網(wǎng)站后,不再打開一個tab頁面并訪問另外的網(wǎng)站。
2.你不能保證你關閉瀏覽器了后,你本地的Cookie立刻過期,你上次的會話已經(jīng)結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數(shù)人都會錯誤的認為關閉瀏覽器就等于退出登錄/結束會話了……)
3.上圖中所謂的攻擊網(wǎng)站,可能是一個存在其他漏洞的可信任的經(jīng)常被人訪問的網(wǎng)站。
上面大概地講了一下CSRF攻擊的思想,下面我將用幾個例子詳細說說具體的CSRF攻擊,這里我以一個銀行轉賬的操作作為例子(僅僅是例子,真實的銀行網(wǎng)站沒這么傻:>)
示例1:
銀行網(wǎng)站A,它以GET請求來完成銀行轉賬的操作,如:
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危險網(wǎng)站B,它里面有一段HTML的代碼如下:
- <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登錄了銀行網(wǎng)站A,然后訪問危險網(wǎng)站B,噢,這時你會發(fā)現(xiàn)你的銀行賬戶少了1000塊……
為什么會這樣呢?原因是銀行網(wǎng)站A違反了HTTP規(guī)范,使用GET請求更新資源。在訪問危險網(wǎng)站B的之前,你已經(jīng)登錄了銀行網(wǎng)站A,而B中的img以GET的方式請求第三方資源(這里的第三方就是指銀行網(wǎng)站了,原本這是一個合法的請求,但這里被不法分子利用了),所以你的瀏覽器會帶上你的銀行網(wǎng)站A的Cookie發(fā)出Get請求,去獲取資源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,結果銀行網(wǎng)站服務器收到請求后,認為這是一個更新資源操作(轉賬操作),所以就立刻進行轉賬操作……
示例2:
為了杜絕上面的問題,銀行決定改用POST請求完成轉賬操作。
銀行網(wǎng)站A的WEB表單如下:
- <form action="Transfer.php" method="POST">
- <p>ToBankId: <input type="text" name="toBankId" /></p>
- <p>Money: <input type="text" name="money" /></p>
- <p><input type="submit" value="Transfer" /></p>
- </form>
后臺處理頁面Transfer.php如下:
- <?php
- session_start();
- if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
- {
- buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
- }
- >
危險網(wǎng)站B,仍然只是包含那句HTML代碼:
- <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例1中的操作一樣,你首先登錄了銀行網(wǎng)站A,然后訪問危險網(wǎng)站B,結果…..和示例1一樣,你再次沒了1000塊~T_T,這次事故的原因是:銀行后臺使用了$_REQUEST去獲取請求的數(shù)據(jù),而$_REQUEST既可以獲取GET請求的數(shù)據(jù),也可以獲取POST請求的數(shù)據(jù),這就造成了在后臺處理程序無法區(qū)分這到底是GET請求的數(shù)據(jù)還是POST請求的數(shù)據(jù)。在PHP中,可以使用$_GET和$_POST分別獲取GET請求和POST請求的數(shù)據(jù)。在JAVA中,用于獲取請求數(shù)據(jù)request一樣存在不能區(qū)分GET請求數(shù)據(jù)和POST數(shù)據(jù)的問題。
示例3:
經(jīng)過前面2個慘痛的教訓,銀行決定把獲取請求數(shù)據(jù)的方法也改了,改用$_POST,只獲取POST請求的數(shù)據(jù),后臺處理頁面Transfer.php代碼如下:
- <?php
- session_start();
- if (isset($_POST['toBankId'] && isset($_POST['money']))
- {
- buy_stocks($_POST['toBankId'], $_POST['money']);
- }
- ?>
然而,危險網(wǎng)站B與時俱進,它改了一下代碼:
- <html>
- <head>
- <script type="text/javascript">
- function steal()
- {
- iframe = document.frames["steal"];
- iframe.document.Submit("transfer");
- }
- </script>
- </head>
- <body onload="steal()">
- <iframe name="steal" display="none">
- <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">
- <input type="hidden" name="toBankId" value="11">
- <input type="hidden" name="money" value="1000">
- </form>
- </iframe>
- </body>
- </html>
如果用戶仍是繼續(xù)上面的操作,很不幸,結果將會是再次不見1000塊……因為這里危險網(wǎng)站B暗地里發(fā)送了POST請求到銀行!
總結一下上面3個例子,CSRF主要的攻擊模式基本上是以上的3種,其中以第1,2種最為嚴重,因為觸發(fā)條件很簡單,一個img就可以了,而第3種比較麻煩,需要使用JavaScript,所以使用的機會會比前面的少很多,但無論是哪種情況,只要觸發(fā)了CSRF攻擊,后果都有可能很嚴重。
理解上面的3種攻擊模式,其實可以看出,CSRF攻擊是源于WEB的隱式身份驗證機制!WEB的身份驗證機制雖然可以保證一個請求是來自于某個用戶的瀏覽器,但卻無法保證該請求是用戶批準發(fā)送的!
如何防御?
請求令牌(一種簡單有效的防御方法):
首先服務器端要以某種策略生成隨機字符串,作為令牌(token),保存在 Session 里。然后在發(fā)出請求的頁面,把該令牌以隱藏域一類的形式,與其他信息一并發(fā)出。在接收請求的頁面,把接收到的信息中的令牌與 Session 中的令牌比較,只有一致的時候才處理請求,處理完成后清理session中的值,否則返回 HTTP 403 拒絕請求或者要求用戶重新登陸驗證身份
令牌來防止 CSRF 有以下幾點要注意:
a.雖然請求令牌原理和驗證碼有相似之處,但不應該像驗證碼一樣,全局使用一個 Session Key。因為請求令牌的方法在理論上是可破解的,破解方式是解析來源頁面的文本,獲取令牌內容。如果全局使用一個 Session Key,那么危險系數(shù)會上升。原則上來說,每個頁面的請求令牌都應該放在獨立的 Session Key 中。我們在設計服務器端的時候,可以稍加封裝,編寫一個令牌工具包,將頁面的標識作為 Session 中保存令牌的鍵。
b.在 ajax 技術應用較多的場合,因為很有請求是 JavaScript 發(fā)起的,使用靜態(tài)的模版輸出令牌值或多或少有些不方便。但無論如何,請不要提供直接獲取令牌值的 API。這么做無疑是鎖上了大門,卻又把鑰匙放在門口,讓我們的請求令牌退化為同步令牌。
c.第一點說了請求令牌理論上是可破解的,所以非常重要的場合,應該考慮使用驗證碼(令牌的一種升級,目前來看破解難度極大),或者要求用戶再次輸入密碼(亞馬遜、淘寶的做法)。但這兩種方式用戶體驗都不好,所以需要產(chǎn)品開發(fā)者權衡。
d.無論是普通的請求令牌還是驗證碼,服務器端驗證過一定記得銷毀。忘記銷毀用過的令牌是個很低級但是殺傷力很大的錯誤。我們學校的選課系統(tǒng)就有這個問題,驗證碼用完并未銷毀,故只要獲取一次驗證碼圖片,其中的驗證碼可以在多次請求中使用(只要不再次刷新驗證碼圖片),一直用到。
如下也列出一些據(jù)說能有效防范 CSRF,其實效果甚微或甚至無效的做法:
a.通過 referer 判定來源頁面:referer 是在 HTTP Request Head 里面的,也就是由請求的發(fā)送者決定的。如果我喜歡,可以給 referer 任何值。當然這個做法并不是毫無作用,起碼可以防小白。但我覺得性價比不如令牌。
b.過濾所有用戶發(fā)布的鏈接:這個是最無效的做法,因為首先攻擊者不一定要從站內發(fā)起請求(上面提到過了),而且就算從站內發(fā)起請求,途徑也遠遠不知鏈接一條。比如
<img src="./create_post.php" />
就是個不錯的選擇,還不需要用戶去點擊,只要用戶的瀏覽器會自動加載圖片,就會自動發(fā)起請求。
c.在請求發(fā)起頁面用 alert 彈窗提醒用戶:這個方法看上去能干擾站外通過 iframe 發(fā)起的 CSRF,但攻擊者也可以考慮用 window.alert = function(){}; 把 alert 弄啞,或者干脆脫離 iframe,使用 Flash 來達到目的。
總體來說,目前防御 CSRF 的諸多方法還沒幾個能徹底無解的。 作為開發(fā)者,我們能做的就是盡量提高破解難度。當破解難度達到一定程度,網(wǎng)站就逼近于絕對安全的位置了
參考文獻
[1].Preventing CSRF
[2].Security Corner: Cross-Site Request Forgeries
[3].《Web安全測試之跨站請求偽造(CSRF)》
[4].百度百科-CSRF、XSS