得益于HTML5,Web應用中越來越多的邏輯從伺服器端遷移到了用戶端。因而,前端開發人員也需要更多關注安全性方面的問題。在這篇文章中,我會告訴你如何使你的應用更加安全。我會着重描述一些你可能從未聽說過的技術,而不是僅僅告訴你“别忘了對使用者送出的頁面資料做轉義(escape)”。
HTTP?想都别想
當然,我并不想讓你通過FTP或者普通的TCP協定來傳輸你的資料。我的意思是,如果你想讓你的使用者安全地通路你的網站,你應該使用SSL(HTTPS)來加密你的資料傳輸。不僅要加密登陸節點或者關鍵資訊,而是要加密所有的資料。否則當使用者通過公用網絡通路你的應用時,他看見的内容說不定已經被别人“黑”掉了。這就叫中間人攻擊,見下圖:

(譯者注:此處引用的原圖中筆誤,中間人攻擊的英文為Man-In-The-Middle Attack)
如果你使用SSL,所有的資料在發送之前就會被加密,即使攻擊者在網絡中截獲了資料包,他也沒有辦法檢視或者篡改其中的内容。對于提升應用的安全性,這是目前為止最重要的一步。
嚴格傳輸安全性标記(Strict-Transport-Security)
如果你隻想通過SSL來傳輸你的資料,那麼這個HTTP頭屬性會讓你覺得非常好用。如果伺服器端在響應頭中使用了這個标記(你也可以在頁面中使用标簽,不過這樣的話就會存在一個未被加密的請求),那麼所有從用戶端到伺服器端的資料都會被加密。使用方式如下:
Strict-Transport-Security: max-age=3600; includeSubDomains
其中的includeSubDomains屬性是可選的,你可以使用它來加密目前域的子域,所有對子域的通路也會被HTTPS加密。而其中的max-age屬性可以設定在多長的時間範圍内(以秒為機關)需要用SSL對頁面資料傳輸進行加密。不過可惜的是,目前隻有Firefox、Chrome和Opera浏覽器支援這個标記。
Secure和HttpOnly屬性
還有一種方法可以有效增強HTTP和HTTPS通路的安全性,那就是使用Secure和HttpOnly這兩個cookie屬性。前者能確定cookie的内容隻通過SSL連接配接進行傳輸;而後者正好相反。如果你覺得這兩者互相沖突,沒啥用處,那就錯了。它們告訴浏覽器cookie的内容隻能分别通過HTTP(S)協定進行通路,進而避免了被别人輕易竊取,比如JavaScript中的document.cookie.
通過Content-Security-Policy(CSP)标記來減少跨站腳本攻擊(XSS)的危害
如果你覺得依靠XSS過濾器能夠防範所有可能的XSS攻擊,不妨先看一看
這篇文章,再好好思考一下。當然,為整個Web應用都配置上完備的防範措施也會存在一些問題,比如,可能拖累整個網站的性能。不過我還有一招。
這招叫做Content-Security-Policy标記。它能讓你指定網站上所有腳本和圖檔等資源的源站點。此外,它還能阻止所有内聯(inline)的腳本和樣式。即使有人在頁面評論或者回帖中嵌入了腳本标簽,這些腳本代碼也不會被執行。CSP标記一般寫在HTTP頭中(也可以寫在HTML的标簽中),寫法如下:
Content-Security-Policy: policy
其中的policy字段代表一系列CSP屬性,下面列舉一些常用的屬性:
- script-src – 設定可以接受的JavaScript代碼的源站點
- style-src – 設定可以接受的CSS樣式代碼的源站點
- connect-src – 定義浏覽器可以通過XHR、WebSocket或者 EventSource通路哪些站點
- font-src – 設定可以接受的字型檔案的源站點
- frame-src – 定義浏覽器可以通過iframe通路哪些站點
- img-src – 設定可以接受的圖檔的源站點
- media-src – 設定可以接受的音頻和視訊檔案的源站點
- object-src – 設定可以接受的Flash和其它插件的源站點
如果沒有設定上述屬性,那麼浏覽器預設會接受來自任何源站點的腳本和資料。不過浏覽器的預設屬性也能通過default-src屬性來設定。其它的屬性如果沒有設定的話,就會預設采用這個屬性中設定的值。此外,還有一個叫做sandbox的屬性,它可以讓浏覽器以iframe的形式加載頁面。下面是一個CSP頭的例子:
Content-Security-Policy: default-src: 'self'; script-src: https://apis.google.com;
在這個例子中,浏覽器隻會加載源自這個Web應用所在站點的資源(預設源設定為self),以及通過Google API伺服器擷取的腳本。CSP有很多種靈活的用法,如果使用得當,可以有效提升Web應用的安全性。
CSP的缺點
當你使用CSP的時候,有一點千萬要記住:預設情況下,所有的内聯JavaScript腳本都不會被執行。例如:
内聯的事件監聽器:比如
<body onload="main();">
所有的javascript URL:比如
<a href="javascript:doTheClick()"">
之是以這樣,是因為浏覽器無法區分你的内聯腳本和黑客注入的腳本。你需要通過JavaScript的addEventListener或者一些架構中類似的函數來重寫上述腳本。某種程度上來看,這也不算一件壞事,它逼你不得不分離邏輯層的代碼和展現層的代碼,而你本來就應該這麼做。此外,CSP預設還會阻止所有eval()風格的代碼的執行,包括setInterval/setTimeout中的字元串和類似于new Function(‘return false’)之類的代碼。
CSP的可用性
大部分時下流行的浏覽器都支援CSP。Firefox、Chrome和Opera(包括手持裝置上的)使用标準的CSP頭。Safari(包括iOS中的)和安卓上的Chrome使用X-WebKit-CSP頭。IE10(僅支援sandbox屬性)使用X-Content-Security-Policy頭。是以,由于IE的支援(部分支援也是支援),你不僅可以着手使用CSP(除非你用的是Google Chrome Frame之類的東西),而且還能在各種實際的浏覽器中運作,有效地改善Web應用的安全性。
使用跨域資源共享(Cross Origin Resource Sharing)來代替JSONP
目前由于浏覽器同源政策的限制,JSONP常被用于從其它伺服器上擷取資料。通常的辦法是在腳本中寫一個回調函數,然後把回調函數的名字寫在請求的URL中,像下面這樣:
function parseData(data) {
...
}
<script src="http://someserver.com/data?format=jsonp&callback=parseData"></script>
但是這樣做會有很大的安全隐患。如果你請求資料的伺服器被黑了,那麼黑客就能在傳回的資料中植入惡意代碼,進而竊取使用者的隐私資訊。因為在浏覽器看來,通過這種方法擷取的腳本代碼和正常的頁面代碼沒什麼差別。
正确的做法是使用跨域資源共享。它允許資源提供方在響應頭中加入一個特殊的标記,使你能通過XHR來擷取、解析并驗證資料。這樣就能避免惡意代碼在你的應用中執行。
這個方法需要在響應頭中加入的标記如下:
Access-Control-Allow-Origin: allowed origins
這裡可以列舉一些可以接受的資料源,以逗号隔開,使用通配符*來接受所有的資料源。
CORS可用性
目前所有主流的浏覽器都支援,除了Opera Mini。
當然,更重要的是資料提供方能支援跨域資源共享,不過這就不是開發者能說了算的了。
在iframe中使用sandbox屬性
如果你使用iframe加載外部網站的内容,也别忘了它的安全問題。你可以通過iframe的sandbox屬性來增強iframe使用的安全性。通過設定sandbox屬性,可以使得iframe無法随意跳轉頁面,無法執行外部腳本,無法鎖定滑鼠,無法彈出新視窗,無法送出表單,等等。此外頁面上若幹個iframe中加載的所有内容還必須來自唯一的源,也就不能使用localStorage等有悖于同源政策的東西。當然,如果有需要的話,你也可以通過顯式的設定來放寬限制,可以設定的屬性如下:
- allow-same-origin –iframe隻要各自符合同源規範即可
- allow-scripts –iframe中可以執行JavaScript腳本
- allow-forms –iframe中可以送出表單
- allow-pointer-lock –iframe中可以鎖定滑鼠
- allow-popups – iframe中可以彈出新視窗
- allow-top-navigation – iframe中可以完成頁面跳轉
可用性
目前iframe的sandbox屬性受各種主流浏覽器支援,除了Opera Mini以外。
結束語
主要内容就是這些了。希望讀者能通過這篇文章學到一些新技術,能用于今後的項目中,更好的提升Web應用的安全性。得益于HTML5的發展,我們可以在Web上實作越來越豐富的功能。但是我們也不能掉以輕心,因為網絡中攻擊和威脅無處不在,在敲第一行代碼之前就要好好想一想安全性方面的問題。