安全 – 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

 

posted @ 2023-11-06 01:41  兴杰  阅读(748)  评论(0)    收藏  举报