1. 程式人生 > >HTML5安全:內容安全策略(CSP)簡介

HTML5安全:內容安全策略(CSP)簡介

        前言:HTML5出現後,網路安全更加受到廣泛的關注。Web對於網路安全有哪些改進?我們如何來面對越來越危險的網路欺詐和攻擊?下面的文章談到了W3C對於這個問題的最新解決方案。未來有機會的話,我會針對XSS、P3P、同源策略、CORS(跨域資源共享)和CSP進行關於HTML5內容安全的現場分享。

        ------------------------華麗的分界線

        注意:本文所討論的API還未最終確定,請在自己的專案中謹慎使用。

        全球資訊網的安全策略植根於同源策略。例如http://blog.csdn.net/hfahe的程式碼只能訪問http://blog.csdn.net

的資料,而沒有訪問http://www.baidu.com的許可權。每個來源都與網路的其它部分分隔開,為開發人員構建了一個安全的沙箱。理論上這是完美的,但是現在攻擊者已經找到了聰明的方式來破壞這個系統。

        這就是XSS跨站指令碼攻擊,通過虛假內容和誘騙點選來繞過同源策略。這是一個很大的問題,如果攻擊者成功注入程式碼,有相當多的使用者資料會被洩漏。

        現在我們介紹一個全新的、有效的安全防禦策略來減輕這種風險,這就是內容安全策略(ContentSecurity Policy,CSP)。

來源白名單

XSS攻擊的核心是利用了瀏覽器無法區分指令碼是被第三方注入的,還是真的是你應用程式的一部分。例如Google +1按鈕會從

https://apis.google.com/js/plusone.js載入並執行程式碼,但是我們不能指望從瀏覽器上的圖片就能判斷出程式碼是真的來自apis.google.com,還是來自apis.evil.example.com。瀏覽器下載並執行任意程式碼的頁面請求,而不論其來源。


        CSP定義了Content-Security-PolicyHTTP頭來允許你建立一個可信來源的白名單,使得瀏覽器只執行和渲染來自這些來源的資源,而不是盲目信任伺服器提供的所有內容。即使攻擊者可以找到漏洞來注入指令碼,但是因為來源不包含在白名單裡,因此將不會被執行。

        以上面Google +1按鈕為例,因為我們相信apis.google.com提供有效的程式碼,以及我們自己,所以可以定義一個策略,允許瀏覽器只會執行下面兩個來源之一的指令碼。

        Content-Security-Policy:script-src 'self' https://apis.google.com

        是不是很簡單?script-src可以為指定頁面控制指令碼相關許可權。這樣瀏覽器只會下載和執行來自http://apis.google.com和本頁自身的指令碼。

        一旦我們定義了這個策略,瀏覽器會在檢測到注入程式碼時丟擲一個錯誤(請注意是什麼瀏覽器)。

內容安全策略適用於所有常用資源

        雖然指令碼資源是最明顯的安全隱患,但是CSP還提供了一套豐富的指令集,允許頁面控制載入各種型別的資源,例如如下的型別:

  • content-src:限制連線的型別(例如XHR、WebSockets和EventSource)
  • frame-src:列出了可以嵌入的frame的來源。例如frame-src https://youtube.com只允許嵌入YouTube的視訊。。
  • img-src:定義了可載入影象的來源。
  • media-src:限制視訊和音訊的來源。
  • object-src:限制Flash和其他外掛的來源。
  • style-src:類似於Script-src,只是作用於css檔案。

        例如,你有一個應用需要從內容分發網路(CDN,例如https://cdn.example.net)載入所有的資源,並且知道不需要任何frame和外掛的內容,你的策略可能會像下面這樣:

  1. Content-Security-Policy:default-src https://cdn.example.net; frame-src 'none'; object-src 'none'  

細節

        我在例子裡使用的HTTP頭是Content-Security-Policy,但是現代瀏覽器已經通過字首來提供了支援:Firefox使用x-Content-Security-Policy,WebKit使用X-WebKit-CSP。未來會逐步過渡到統一的標準。

        策略可以根據每個不同的頁面而設定,這提供了很大的靈活度。因為你的站點可能有的頁面有Google +1的按鈕,而有的則沒有。

        每個指令的來源列表可以相當靈活,你可以指定模式(data:, https:),或者指定主機名在一個範圍(example.com,它匹配主機上的任意來源、任意模式和任意埠),或者指定一個完整的URI(https://example.com:443,特指https協議,example.com域名,443埠)。

        你在來源列表中還可以使用四個關鍵字:

  • “none”:你可能期望不匹配任何內容
  • “self”:與當前來源相同,但不包含子域
  • “unsafe-inline”:允許內聯Javascript和CSS
  • “unsafe-eval”:允許文字到JS的機制例如eval

        請注意,這些關鍵詞需要加引號。

沙箱

        這裡還有另外一個值得討論的指令:sandbox。和其他指令有些不一致,它主要是控制頁面上採取的行為,而不是頁面能夠載入的資源。如果設定了這個屬性,頁面就表現為一個設定了sandbox屬性的frame一樣。這對頁面有很大範圍的影響,例如防止表單提交等。這有點超出了本文的範圍,但是你可以在HTML5規範的“沙箱標誌設定”章節找到更多資訊。

有害的內聯程式碼

CSP基於來源白名單,但是它不能解決XSS攻擊的最大來源:內聯指令碼注入。如果攻擊者可以注入包含有害程式碼的script標籤(<script>sendMyDataToEvilDotCom();</script>),瀏覽器並沒有好的機制來區分這個標籤。CSP只能通過完全禁止內聯指令碼來解決這個問題。

        這個禁止項不僅包括指令碼中嵌入的script標籤,還包括內聯事件處理程式和javascrpt:這種URL。你需要把script標籤的內容放到一個外部檔案裡,並且用適當的addEventListener的方式替換javascript:和<a… onclick=”[JAVASCRIPT]”>。例如,你可能會把下面的表單:

  1. <script>
  2.   function doAmazingThings() {  
  3.     alert('YOU AM AMAZING!');  
  4.   }  
  5. </script>
  6. <buttononclick='doAmazingThings();'>Am I amazing?</button>
        重寫為下面的形式:
  1. <!-- amazing.html -->
  2. <scriptsrc='amazing.js'></script>
  3. <buttonid='amazing'>Am I amazing?</button>
  4. // amazing.js  
  5. function doAmazingThings() {  
  6.   alert('YOU AM AMAZING!');  
  7. }  
  8. document.addEventListener('DOMContentReady', function () {  
  9.   document.getElementById('amazing')  
  10.           .addEventListener('click', doAmazingThings);  
  11. });  
        無論是否使用CSP,以上的程式碼其實有更大的優點。內聯JavaScript完全混合了結構和行為,你不應該這麼做。另外外聯資源更容易進行瀏覽器快取,開發者更容易理解,並且便於編譯和壓縮。如果採用外聯程式碼,你會寫出更好的程式碼。

        內聯樣式需要以同樣的方式進行處理,無論是style屬性還是style標籤都需要提取到外部樣式表中。這樣可以防止各式各樣神奇的資料洩漏方式

        如果你必須要有內聯指令碼和樣式,可以為script-src or style-src屬性設定'unsafe-inline值。但是不要這樣做,禁止內聯指令碼是CSP提供的最大安全保證,同時禁止內聯樣式可以讓你的應用變得更加安全和健壯。這是一個權衡,但是非常值得。

Eval

        即便攻擊者不能直接注入指令碼,他可能會誘使你的應用把插入的文字轉換為可執行指令碼並且自我執行。eval() , newFunction() , setTimeout([string], ...) 和setInterval([string], ...) 都可能成為這種危險的載體。CSP針對這種風險的策略是,完全阻止這些載體。

        這對你構建應用的方式有一些影響:

        通過內建的JSON.parse解析JSON,而不依靠eval。IE8以後的瀏覽器都支援本地JSON操作,這是完全安全的。

        通過行內函數代替字串來重寫你setTimeout和setInterval的呼叫方式。例如:    

  1. setTimeout("document.querySelector('a').style.display = 'none';", 10);   

        可以重寫為:

  1. setTimeout(function () { document.querySelector('a').style.display = 'none'; }, 10);   
避免執行時的內聯模版:許多模版庫都使用new Function()以加速模版的生成。這對動態程式來說非常棒,但是對惡意文字來說存在風險。

報告

        CSP可以在伺服器端阻止不可信的資源對使用者來說非常有用,但是對於獲取各種傳送到伺服器的通知來說對我們卻非常有用,這樣我們就能識別和修復任何惡意指令碼注入。為此你可以通過report-uri指令指示瀏覽器傳送JSON格式的攔截報告到某個地址。

  1. Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;  

        報告看起來會像下面這樣:

  1. {  
  2.   "csp-report": {  
  3.     "document-uri": "http://example.org/page.html",  
  4.     "referrer": "http://evil.example.com/",  
  5.     "blocked-uri": "http://evil.example.com/evil.js",  
  6.     "violated-directive": "script-src 'self' https://apis.google.com",  
  7.     "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"  
  8.   }  
  9. }  

        其中包含的資訊會幫助你識別攔截的情況,包括攔截髮生的頁面(document-uri),頁面的referrer,違反頁面策略的資源(blocked-uri),所違反的指令(violated-directive)以及頁面所有的內容安全策略(original-policy)。

現實用法

        CSP現在在Chrome 16+和Firefox 4+的瀏覽器上可用,並且它在IE10上預計會獲得有限的支援。Safari目前還不支援,但是WebKit每晚構建版已經可用,所以預計Safari將會在下面的迭代中提供支援。

        下面讓我們看一些常用的用例:

        實際案例1:社會化媒體widget

  • Google +1 button包括來自https://apis.google.com的指令碼,以及嵌入自https://plusone.google.com的iframe。你的策略需要包含這些源來使用Google +1的按鈕。最簡單的策略是script-srchttps://apis.google.com; frame-src https://plusone.google.com。你還需要確保Google提供的JS片段存放在外部的JS檔案裡。
  • Facebook的Like按鈕有許多種實現方案。我建議你堅持使用iframe版本,因為它可以和你站點的其它部分保持很好的隔離。這需要使用frame-src https://facebook.com指令。請注意,預設情況下,Facebook提供的iframe程式碼使用的是相對路徑//facebook.com,請把這段程式碼修改為https://facebook.com,HTTP你沒有必要可以不使用。

        其它的平臺有相似的情況,可以類似的解決。我建議把default-src設定為none,然後檢視控制檯來檢查你需要使用哪些資源來確保widget正常工作。

        使用多個widget非常簡單:只需要合併所有的策略指令,記住把同一指令的設定都放在一起。如果你想使用上面這三個widget,策略看起來會像下面這樣:

  1. script-src https://apis.google.com https://platform.twitter.com; frame-src https://plusone.google.com https://facebook.com https://platform.twitter.com  

        實際案例2:防禦

        假設你訪問一個銀行網站,並且希望確保只加載你所需的資源。在這種情況下,開始設定一個預設的許可權來阻止所有的內容(default-src ‘none’),並且從這從頭構建策略。

        比如,銀行網站需要從來自https://cdn.mybank.net的CDN載入影象、樣式和指令碼,並且通過XHR連線到https://api.mybank.com/來拉取各種資料,還需要使用frame,但是frame都來自非第三方的本地頁面。網站上沒有Flash、字型和其他內容。這種情況下我們可以傳送最嚴格的CSP頭是:   

  1. Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; frame-src 'self'   

        實際案例3:只用SSL

        一個婚戒論壇管理員希望所有的資源都通過安全的方式進行載入,但是不想真的編寫太多程式碼;重寫大量第三方論壇內聯指令碼和樣式的程式碼超出了他的能力。所以以下的策略將會是非常有用的:

  1. Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'  

        儘管default-src指定了https,指令碼和樣式不會自動繼承。每個指令將會完全覆蓋預設資源型別。

未來

        W3C的Web應用安全工作組正在制定內容安全策略規範的細節,1.0版本將要進入最後修訂階段,它和本文描述的內容已經非常接近。而[email protected]郵件組正在討論1.1版本,瀏覽器廠商也在努力鞏固和改進CSP的實現。

        CSP 1.1在畫板上有一些有趣的地方,值得單獨列出來:

        通過meta標籤新增策略:CSP的首選設定方式是HTTP頭,它非常有用,但是通過標記或者指令碼設定會更加直接,不過目前還未最終確定。WebKit已經實現了通過meta元素進行許可權設定的特性,所以你現在可以在Chrome下嘗試如下的設定:在文件頭新增<metahttp-equiv="X-WebKit-CSP" content="[POLICY GOES HERE]">。

        你甚至可以在執行時通過指令碼來新增策略。

        DOM API:如果CSP的下一個迭代添加了這個特性,你可以通過Javascript來查詢頁面當前的安全策略,並根據不同的情況進行調整。例如在eval()是否可用的情況下,你的程式碼實現可能會有些許不同。這對JS框架的作者來說非常有用;並且API規範目前還非常不確定,你可以在規範草案的指令碼介面章節找到最新的迭代版本。

        新的指令:許多新指令正在討論中,包括script-nonce:只有明確指定的指令碼元素才能使用內聯指令碼;plugin-types:這將限制外掛的型別;form-action:允許form只能提交到特定的來源。

        如果你對這些未來特性的討論感興趣,可以閱讀郵件列表的歸檔或者加入郵件列表。