Content Security Policy
- 2020 年 9 月 4 日
- 筆記
Content Security Policy(內容安全策略,簡稱csp)用於檢測並阻止網頁載入非法資源的安全策略,可以減輕xss攻擊帶來的危害和數據注入等攻擊。本文講述的內容主要有如何使用csp和業務接入csp流程這兩部分。
簡介
csp主要工作是定義一套頁面資源載入白名單規則,瀏覽器使用csp規則去匹配所有資源,禁止載入不符合規則的資源,同時將非法資源請求進行上報。
csp作用:減輕網頁被xss攻擊後所受到的危害。實際上csp是在xss攻擊發生後才起作用,阻止請求注入的非法資源,csp並不是直接阻止xss攻擊的發生。
使用方式
csp主要有兩種使用方式,分別是設置響應頭Content-Security-Policy
和使用meta
標籤。
響應頭
在網頁html請求的響應頭中進行定義,定義方式:
Content-Security-Policy: 指令1 指令值1 指令值2; 指令2 指令值1;
例子:
Content-Security-Policy: srcipt-src 'self' *.test.com'; img-src: https: data:;
後面會重點講解csp中具體存在哪些指令和指令值,可以在定義規則中看到具體的介紹。
meta
在網頁html文件中進行定義,定義方式:
<meta
http-equiv="Content-Security-Policy"
content="指令1 指令值1 指令值2; 指令2 指令值1;"
>
例子:
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="srcipt-src 'self' *.test.com'; img-src: https: data:;"
>
</head>
<body>...</body>
</html>
注意:由於html文檔是從上至下進行解析,因此,meta盡量寫在最前面,保證能夠對所有資源請求進行約束。
效果
csp規則匹配到的資源都能夠正常請求,一旦有非法資源請求,瀏覽器就會立即阻止,阻止的效果如下
定義規則
csp規則內容主要由指令和指令值這兩部分構成,指令用於定義資源類型,指令值用於定義資源請求地址規則。
例子:
Content-Security-Policy: srcipt-src 'self' *.test.com';
上面csp規則中,script-src
是腳本資源載入指令,'self'
和*.test.com
是指令值,當載入js腳本的時候,只有滿足指令值定義的規則才能正常載入。
指令
指令 | 說明 | 示例 |
---|---|---|
default-src | 定義所有類型資源默認載入策略,當下面這些指令未被定義的時候,瀏覽器會使用default-src 定義的規則進行校驗 |
'self' *.test.com |
script-src | 定義JavaScript資源載入策略 | 'self' js.test.com |
style-src | 定義樣式文件的載入策略 | 'self' css.test.com |
img-src | 定義圖片文件的載入策略 | 'self' img.test.com |
font-src | 定義字體文件的載入策略 | 'self' font.test.com |
connect-src | 定義XHR、WebSocket等請求的載入策略,當請求不符合定義的規則時,瀏覽器會模擬一個響應狀態碼為400的響應 | 'self' |
object-src | 定義<object> <embed> <applet> 等標籤的載入策略 |
'self' |
media-src | 定義<audio> <video> 等多媒體html標籤資源載入策略 |
'self' |
frame-src | 【已廢棄】定義iframe標籤資源載入策略(新指令:child-src) | 'self' |
sandbox | 對請求的資源啟用sandbox,類似於iframe中的sandbox功能 | allow-scripts |
report-uri | 定義上報地址。當有資源被攔截的時候,瀏覽器帶著被攔截的資源資訊請求這個uri。(只在響應頭中定義才能生效) | //csp.test.com/report |
child-src | 【csp2】定義子窗口(iframe、彈窗等)的載入策略 | 'self' *.test.com |
form-action | 【csp2】定義表單提交的源規則 | 'self' |
frame-ancestors | 【csp2】定義當前頁面可以被哪些頁面使用ifram、object進行載入 | 'self' |
plugin-types | 【csp2】定義頁面允許載入哪些插件 |
指令值
指令值 | 說明 | 示例 |
---|---|---|
* |
所有內容都正常載入 | img-src *; |
'none' |
不允許載入任何資源 | img-src 'none'; |
'self' |
允許載入同源資源 | img-src 'self'; |
'unsafe-inline' |
允許載入inline內容(例如:style、onclick、inline js、inline css等) | srcript-url 'unsafe-inline'; |
'unsafe-eval' |
允許載入動態js程式碼(例如:eval() ) |
script-src 'unsafe-eval'; |
http: |
允許載入http協議資源 | img-src http:; |
https: |
允許載入https協議資源 | img-src https:; |
data: |
允許使用data協議(css中載入base64圖片使用的就是data協議) | img-src data:; |
域名 | 允許載入該域名下所有https協議資源 | img-src test.com; |
路徑 | 允許載入該路徑下所有https協議資源 | img-src test.com/img/; |
通配符 | *.test.com 允許載入子域名下所有https協議的資源(任意子域名);*://*.test.com:* 這個匹配邏輯原意是任意協議、任意子域名、任意埠,但在實際測試過程中發現這個指令值沒有任何作用 |
img-src *.test.com; |
實際業務開發
前面我們了解了csp基本用法,接下來講述下業務接入csp流程。由於瀏覽器會禁止載入違反csp規則的資源,因此,我們需要對csp進行一系列驗證後,才能正式上線,下面將帶著大家一步一步的完成業務csp規則的部署。
整理資源地址
web頁面中,瀏覽器有提供performance.getEntries()
介面,用於獲取網頁載入的資源資訊,其中initiatorType
(資源類型)屬性可以知道資源屬於哪個指令,name
(資源地址)可以定義指令值有哪些。
為了簡便,下面將只定義default-src
這一個指令,所有資源載入檢測都直接走default-src
定義的規則。
const entries = window.performance.getEntries()
const names = entries.map(info => info.name);
生成csp規則
const parseReg = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
const httpList = [];
const httpsList = [];
const otherProtocol = [];
// 分離https、http、其他協議資源
names.forEach(str => {
const parse = parseReg.exec(str);
// 實測 *://*.test.com:*並不能匹配test.com任意協議、任意子域名、任意埠,因此這裡將http和https分離
if (['https', 'http'].includes(parse[1])) {
const domain = parse[3];
const midProduct = domain.split('.');
const midProductLen = midProduct.length;
const childDomain = midProductLen > 2 ? `*.${midProduct.slice(midProductLen - 2).join('.')}` : domain;
if (parse[1] === 'https') {
httpsList.push(childDomain);
} else {
httpList.push(`//${childDomain}`);
}
} else {
otherProtocol.push(parse[1]);
}
});
const domains = new Set(otherProtocol.concat(httpsList).concat(httpList))
const defaultSrc = [...domains].join(' ');
這樣我們就能夠得到一個比較簡單的default-src
指令值,如果項目中還存在inlin的程式碼或者使用了eval
函數動態執行js程式碼,就需要在default-src
中配置額外的值,具體應該添加什麼內容,大家可以在指令值看到。
本地測試
在Google瀏覽器插件應用,添加CSP Mitigator
插件,然後配置頁面地址、csp規則資訊。
插件內容配置完成以後,點擊start開始測試。進入業務頁面,查看控制台有哪些資源不符合csp規則,然後再根據報錯將csp規則補全。
如果本地測試,沒有出現違背csp規則的資源載入時,就可以考慮將csp規則放到現網進行測試。只是此時放入現網使用的響應頭不是Content-Security-Policy
,而是Content-Security-Policy-Report-Only
,下面一個板塊將詳細介紹現網測試。
現網測試
一般情況下,瀏覽器會禁止載入違反csp規則的資源,這對於csp準確度要求比較高,如果我們不小心遺漏了某個規則,將會影響到頁面正常展示,這無疑會給開發者帶來巨大的壓力。瀏覽器為了解決這一問題,提供了Content-Security-Policy-Report-Only
響應頭,對於違反csp規則的資源,只進行上報處理,不禁止載入資源,這樣我們可以在不影響業務使用的情況下,使用上報的非法資源數據,來逐漸補全csp規則。
資源被阻止後,瀏覽器上報的內容如下:
如果上報的被阻止數據中存在合法資源,我們就需要將該資源寫入規則中,更新後,我們可以將規則寫到CSP Mitigator
插件中,然後在頁面控制台中使用fetch
函數去請求之前上報的資源地址,如果控制台沒有報禁止載入的問題,那說明最新的csp規則是有效的。經過一段時間優化後,csp優化好以後,繼續寫入Content-Security-Policy-Report-Only
響應頭中進行觀察。
正式上線
如果確認csp上報的資源只有非法資源了,此時便可以將響應頭改成Content-Security-Policy
。當響應頭改為Content-Security-Policy
以後,違背csp規則的資源會被禁止載入,同時會進行上報處理。
參考
- Content Security Policy 介紹
- Content Security Policy 入門教程
- Content Security Policy Level 2 介紹
- Content Security Policy (CSP)
- performance.getEntries()