Content Security Policy

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規則的資源

定義規則

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規則資訊。

CSP Mitigator使用

插件內容配置完成以後,點擊start開始測試。進入業務頁面,查看控制台有哪些資源不符合csp規則,然後再根據報錯將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規則的資源會被禁止載入,同時會進行上報處理。

參考