安全 – CSP (Content Security Policy)

前言

之前講過 CSRF。防 Cookie hacking 的。

也介紹過防 XSS 的 HtmlSanitizer

今天再介紹 CSP。

 

參考

Content Security Policy 介紹

MDN – Content-Security-Policy

 

CSP (Content Security Policy) 介紹

它是遊覽器其中一種防 hack 機制。除 IE 以外,modern browser 老早就全部支持了,所以可以安心用。

它主要是防 html 裏要加載的 resource。

比如 HTML 想加載 JavaScript, Image 等等。

首先遊覽器會去檢查 CSP config,然後驗證這些 resource 是否符合 config 要求,如果 ok 才加載,不 ok 就報錯,不加載。

此外它還可以防 inline JavaScript 的執行,還有網頁被 ifame 嵌套等等。算是滿全面的防 hack 機制。

 

Config CSP

same origin resource

CSP 可以設置在 header,也可以放到 HTML 的 meta 裏。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

這是一個用 meta 定義 CSP 的例子。所有的 config 都會寫到 content 裏。分隔符是空格和分號。

用 header 的話,key 是 Content-Security-Policy,value 是 default-src 'self'。

CSP 可以配置不同 src 的條件,比如 script-src 指的是 JavaScript,img-src 指的是圖片,而 default-src 指的是所有 src 默認的條件。

上面這句 default-src 'self' 意思是 HTML 裏所有要加載的 resource 必須來自於 self / 同域 / same-origin。

<script src="/script.js"></script>
<script src="https://192.168.1.152:44300/script.js"></script>

假設我的 origin 是 http://localhost:5148,上面 /script.js 可以加載,但是 https://192.168.1.152:44300/script.js 就不行,因爲它不是 same origin。

運行的結果就是遊覽器會報錯。

wss: 和 https:

除了 https://192.168.1.152:44300/script.js,還有一個 resource 也被拒絕了 wss://localhost:61341,這個是 ASP.NET Core development mode 情況下開啓的 websocket,作用是自動刷新 browser。

如果我們想 allow 它,可以這樣配置。

<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss:">

加了一個 wss:,我沒有聲明完整的 origin,只聲明瞭 protocol,所有隻要是 websocket 請求,不管什麼 origin 都被允許。類似的設置還有 https: 表示只要是 https 安全請求就允許。

inline script

'self' 並不能 bypass inline script。

<script>alert('inline script');</script>

CSP 防禦下,inline script 會報錯。

unsafe-inline

<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss: 'unsafe-inline'">

添加 unsafe-inline 就可以 bypass inline script 了,但是這個方法是相對不安全的操作,除非我們可以完全信賴 inline script。

更安全的 by pass 方式是通過 sha256。我們把 inline script 拿去 sha256 + base64 得到 hash。

然後這樣配置 CSP

<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss: 'sha256-HgfVE1WaRXdD1n+LcUazTiP/FMatVgqvpPh9iAxr2qE='">

只要 inline script 的內容 sha256 後和 CSP 匹配,那麼遊覽器才允許執行代碼。

要得到這個 sha256 有很多種方式

1. Online Sha256

2. Chrome 報錯的時候會提供 sha256 的 hash,參考上面的 error。

3. 通過 C#

var script = "alert('inline script');";
var sha256Script = SHA256.HashData(Encoding.UTF8.GetBytes(script));
var base64Hash = Convert.ToBase64String(sha256Script);
Console.WriteLine(base64Hash);

third party resource

<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss: https://192.168.1.152:44300">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss: https://192.168.1.152:44300/script.js">

<script src="https://192.168.1.152:44300/script.js"></script>

我們可以指定信任的 origin,比如 https://192.168.1.152:44300 表示所有這個 origin 的 resource 都可以加載運行。

如果只是單個 resource 就寫完整 URL,https://192.168.1.152:44300/script.js

如果我們不是很信任這個 origin 的 script,我們還可以添加一個 sha256 驗證。

<script src="https://192.168.1.152:44300/script.js" integrity="sha256-tZpBEqmrY4CizbfYTAoo3wFhDJOpv6HGhMzwex2TTMs=" crossorigin="anonymous" ></script>

只要 resource 的內容和 hash 不匹配,那就會報錯。

best practice

最起碼可以 set 一個 https:,確保所有通信是加密的

<meta http-equiv="Content-Security-Policy" content="default-src https:">

‘self’,只相信自己也是一個好習慣。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

對你相信的 thrid party 開放

<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://192.168.1.152:44300">

只開放一些 third party resource 

<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://192.168.1.152:44300/script.js">

用 sha256 確保 script 是預期中的

<meta http-equiv="Content-Security-Policy" content="default-src 'self' wss: 'sha256-HgfVE1WaRXdD1n+LcUazTiP/FMatVgqvpPh9iAxr2qE='">

<script src="https://192.168.1.152:44300/script.js" integrity="sha256-tZpBEqmrY4CizbfYTAoo3wFhDJOpv6HGhMzwex2TTMs=" crossorigin="anonymous" ></script>
<script>alert('inline script');</script>

nonce

參考:MDN – nonce

nonce 是一個比較弱的防 hack 機制。用上面完整的 CSP 會更理想。

我是看到 Facebook Page Embed generate 出來的 code 有放,所以這裏才順便提一下。

假設我們沒有用 sha256,'self' 這些 CSP 來防 hack,並且我們被 XSS 了。hacker 插入了一個 inline script 到我們的頁面。

nonce 的防 hack 方式是這樣,首先後端需要 generate nonce 隨機數。

這裏給一個 ASP.NET Core Razor Pages 的例子

public void OnGet()
{
  static string GenerateCryptoNonce()
  {
    var rng = RandomNumberGenerator.Create();

    byte[] bytes = new byte[16];
    rng.GetBytes(bytes);

    string nonce = Convert.ToBase64String(bytes);

    return nonce;
  }

  Nonce = GenerateCryptoNonce();

  Response.Headers.Append("Content-Security-Policy", $"default-src 'self' wss: 'nonce-{Nonce}'");
}

nonce 需要用 cryptographically 128 bit (16 bytes) 生成,然後轉 base64。

然後把 nonce 放到 CSP config 裏,還有每一個 script 中。

<script nonce="@Model.Nonce">
  alert('ok')
</script>

遊覽器在執行 script 之前,會先看它是否有 nonce,並且需要和 response header 中的 CSP nonce 匹配。

如果有匹配就執行,沒有就不報錯。

hacker 通過 XSS 插入的 script 沒辦法知道 nonce 隨機數,所以最終 hacker script 不會被遊覽器執行,網頁也就安全了。

瞭解了它的原理,確實,它也沒有很安全,所以大家還是按上面 best practice 做會更理想。

frame-ancestors and X-Frame-Options

frame-ancestors 是用來取代 X-Frame-Options 的,它們的作用是聲明網頁是否允許被其它網頁 iframe 嵌入。通常是不允許的啦。

注意:它只可以通過 HTTP header 方式去聲明,HTML meta 不可以哦。

我的 Index 想嵌套 About 進來 iframe。如果沒有 CSP 這個操作是 ok 的。

現在我們去配置 CSP 阻止它。

在 about 的 response header 加上 CSP frame-ancestors 'none' 完全不允許任何網頁嵌入。

效果

只允許同域嵌入

把 'none' 換成 'self' 就可以了。

允許指定的 origin 嵌入

放入指定的 origin 就可以了,可以放多個,分隔符是空格。

defualt-src + frame-ancestors 的寫法是

default-src 'self'; frame-ancestors https://192.168.1.152:44300 https://192.168.1.152:4200

分隔符是分號。

 

總結

CSP 是遊覽器的一種安全機制。可以用來限制 HTML 加載的 resource (e.g. script, img)。

比如限制 resource 只能是同域,或者指定可信賴的 origin。甚至可以通過 sha256 確保加載的內容是預期的。

另外它還可以防 XSS inline script 還有防網頁被其它網頁 ifram 嵌入等等。

這個是 Apple 官網返回的 CSP

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章