Content Security Policy 入門教程

阮一峰發表於2016-09-13

跨域指令碼攻擊 XSS 是最常見、危害最大的網頁安全漏洞。

為了防止它們,要採取很多程式設計措施,非常麻煩。很多人提出,能不能根本上解決問題,瀏覽器自動禁止外部注入惡意指令碼?

這就是"網頁安全政策"(Content Security Policy,縮寫 CSP)的來歷。本文詳細介紹如何使用 CSP 防止 XSS 攻擊。

一、簡介

CSP 的實質就是白名單制度,開發者明確告訴客戶端,哪些外部資源可以載入和執行,等同於提供白名單。它的實現和執行全部由瀏覽器完成,開發者只需提供配置。

CSP 大大增強了網頁的安全性。攻擊者即使發現了漏洞,也沒法注入指令碼,除非還控制了一臺列入了白名單的可信主機。

兩種方法可以啟用 CSP。一種是透過 HTTP 頭資訊的Content-Security-Policy的欄位。


Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:

另一種是透過網頁的<meta>標籤。


<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

上面程式碼中,CSP 做了如下配置。

  • 指令碼:只信任當前域名
  • <object>標籤:不信任任何URL,即不載入任何資源
  • 樣式表:只信任cdn.example.orgthird-party.org
  • 框架(frame):必須使用HTTPS協議載入
  • 其他資源:沒有限制

啟用後,不符合 CSP 的外部資源就會被阻止載入。

Chrome 的報錯資訊。

Firefox 的報錯資訊。

二、限制選項

CSP 提供了很多限制選項,涉及安全的各個方面。

2.1 資源載入限制

以下選項限制各類資源的載入。

  • script-src:外部指令碼
  • style-src:樣式表
  • img-src:影像
  • media-src:媒體檔案(音訊和影片)
  • font-src:字型檔案
  • object-src:外掛(比如 Flash)
  • child-src:框架
  • frame-ancestors:嵌入的外部資源(比如<frame>、<iframe>、<embed>和<applet>)
  • connect-src:HTTP 連線(透過 XHR、WebSockets、EventSource等)
  • worker-srcworker指令碼
  • manifest-src:manifest 檔案

2.2 default-src

default-src用來設定上面各個選項的預設值。


Content-Security-Policy: default-src 'self'

上面程式碼限制所有的外部資源,都只能從當前域名載入。

如果同時設定某個單項限制(比如font-src)和default-src,前者會覆蓋後者,即字型檔案會採用font-src的值,其他資源依然採用default-src的值。

2.3 URL 限制

有時,網頁會跟其他 URL 發生聯絡,這時也可以加以限制。

  • frame-ancestors:限制嵌入框架的網頁
  • base-uri:限制<base#href>
  • form-action:限制<form#action>

2.4 其他限制

其他一些安全相關的功能,也放在了 CSP 裡面。

  • block-all-mixed-content:HTTPS 網頁不得載入 HTTP 資源(瀏覽器已經預設開啟)
  • upgrade-insecure-requests:自動將網頁上所有載入外部資源的 HTTP 連結換成 HTTPS 協議
  • plugin-types:限制可以使用的外掛格式
  • sandbox:瀏覽器行為的限制,比如不能有彈出視窗等。

2.5 report-uri

有時,我們不僅希望防止 XSS,還希望記錄此類行為。report-uri就用來告訴瀏覽器,應該把注入行為報告給哪個網址。


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

上面程式碼指定,將注入行為報告給/my_amazing_csp_report_parser這個 URL。

瀏覽器會使用POST方法,傳送一個JSON物件,下面是一個例子。


{
  "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
  }
}

三、Content-Security-Policy-Report-Only

除了Content-Security-Policy,還有一個Content-Security-Policy-Report-Only欄位,表示不執行限制選項,只是記錄違反限制的行為。

它必須與report-uri選項配合使用。


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

四、選項值

每個限制選項可以設定以下幾種值,這些值就構成了白名單。

  • 主機名:example.orghttps://example.com:443
  • 路徑名:example.org/resources/js/
  • 萬用字元:*.example.org*://*.example.com:*(表示任意協議、任意子域名、任意埠)
  • 協議名:https:data:
  • 關鍵字'self':當前域名,需要加引號
  • 關鍵字'none':禁止載入任何外部資源,需要加引號

多個值也可以並列,用空格分隔。


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

如果同一個限制選項使用多次,只有第一次會生效。


# 錯誤的寫法
script-src https://host1.com; script-src https://host2.com

# 正確的寫法
script-src https://host1.com https://host2.com

如果不設定某個限制選項,就是預設允許任何值。

五、script-src 的特殊值

除了常規值,script-src還可以設定一些特殊值。注意,下面這些值都必須放在單引號裡面。

  • 'unsafe-inline':允許執行頁面內嵌的&lt;script>標籤和事件監聽函式
  • unsafe-eval:允許將字串當作程式碼執行,比如使用evalsetTimeoutsetIntervalFunction等函式。
  • nonce值:每次HTTP回應給出一個授權token,頁面內嵌指令碼必須有這個token,才會執行
  • hash值:列出允許執行的指令碼程式碼的Hash值,頁面內嵌指令碼的雜湊值只有吻合的情況下,才能執行。

nonce值的例子如下,伺服器傳送網頁的時候,告訴瀏覽器一個隨機生成的token。


Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

頁面內嵌指令碼,必須有這個token才能執行。


<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
  // some code
</script>

hash值的例子如下,伺服器給出一個允許執行的程式碼的hash值。


Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

下面的程式碼就會允許執行,因為hash值相符。


<script>alert('Hello, world.');</script>

注意,計算hash值的時候,<script>標籤不算在內。

除了script-src選項,nonce值和hash值還可以用在style-src選項,控制頁面內嵌的樣式表。

六、注意點

(1)script-srcobject-src是必設的,除非設定了default-src

因為攻擊者只要能注入指令碼,其他限制都可以規避。而object-src必設是因為 Flash 裡面可以執行外部指令碼。

(2)script-src不能使用unsafe-inline關鍵字(除非伴隨一個nonce值),也不能允許設定data:URL。

下面是兩個惡意攻擊的例子。


<img src="x" onerror="evil()">
<script src="data:text/javascript,evil()"></script>

(3)必須特別注意 JSONP 的回撥函式。


<script
src="/path/jsonp?callback=alert(document.domain)//">
</script>

上面的程式碼中,雖然載入的指令碼來自當前域名,但是透過改寫回撥函式,攻擊者依然可以執行惡意程式碼。

七、參考連結

(完)

相關文章