跨域指令碼攻擊 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.org
和third-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-src
:worker
指令碼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.org
,https://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'
:允許執行頁面內嵌的<script>
標籤和事件監聽函式unsafe-eval
:允許將字串當作程式碼執行,比如使用eval
、setTimeout
、setInterval
和Function
等函式。- 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-src
和object-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>
上面的程式碼中,雖然載入的指令碼來自當前域名,但是透過改寫回撥函式,攻擊者依然可以執行惡意程式碼。
七、參考連結
- CSP Is Dead, Long Live CSP! , by Lukas Weichselbaum
- An Introduction to Content Security Policy, by Mike West
(完)