0x01 内容安全策略(Content Security Policy)
1 简介
2 开启CSP:启用后不符合CSP的外部资源就会被阻止加载
- HTTP头信息的Content-Security-Policy字段
1
| header("Content-Security-Policy: default-src https:; report-uri /csp-violation-report-endpoint/");
|
1 2 3 4 5 6 7
| <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.org和third-party.org 框架(frame):必须使用HTTPS协议加载 其他资源:没有限制
|
0x02 CSP限制选项
1 资源加载限制
default-src:用来设置默认值
1
| Content-Security-Policy: default-src 'self' //限制所有的外部资源,都只能从当前域名加载
|
- 如果同时设置某个单项限制(比如font-src)和 default-src,前者会覆盖后者,即字体文件会采用 font-src 的值,其他资源依然采用 default-src 的值
1 2 3 4 5 6 7 8 9 10 11
| 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-src:worker脚本 manifest-src:manifest 文件
|
2 URL限制
1 2 3
| frame-ancestors:限制嵌入框架的网页 base-uri:限制<base#href> form-action:限制<form#action>
|
3 其他限制
1 2 3 4
| block-all-mixed-content:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启) upgrade-insecure-requests:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议 plugin-types:限制可以使用的插件格式 sandbox:浏览器行为的限制,比如不能有弹出窗口等。
|
report-uri、report-to
1 2
| Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser; //浏览器会使用POST方法,发送一个JSON对象。将注入行为报告给/my_amazing_csp_report_parser这个 URL。
|
- 尽管report-to指令旨在取代已弃用的report-uri指令, report-uri已经从Web标准中删除,但在大多数浏览器中仍不受支持。因此,可以同时指定report-to和report-uri。
0x03 选项值
1 2 3 4 5 6
| 主机名:example.org,https://example.com:443 路径名:example.org/resources/js/ 通配符:*.example.org,*://*.example.com:*(表示任意协议、任意子域名、任意端口) 协议名:https:、data: 关键字'self':当前域名,需要加引号 关键字'none':禁止加载任何外部资源,需要加引号
|
多个值可以并列,并用空格来分隔
- 如果同一个限制选项使用多次,只有第一次会生效。
- 如果不设置某个限制选项,就是默认允许任何值。
1
| Content-Security-Policy: script-src 'self' https:
|
script-src的特殊值
- 除了常规值,script-src还可以设置一些特殊值。
1 2
| 'unsafe-inline':允许执行页面内嵌的<script>标签和事件监听函数 'unsage-eval':允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。
|
nonce值和hash值
1 2 3 4
| <?php Header("Content-Security-Policy: script-src 'nonce-".$random." '"); ?> <script nonce="<?php echo $random?>">
|
1 2 3 4 5 6 7
| 服务器发送网页的时候,告诉浏览器一个随机生成的token。 Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
页面内嵌脚本,必须有这个token才能执行。 <script nonce=EDNnf03nceIOfn39fn3e9h3sdfa> </script>
|
- hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
1 2 3 4
| 服务器给出一个允许执行的代码的hash值:(注意,计算hash值的时候,script标签不算在内。) Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=' 下面的代码就会允许执行,因为hash值相符: <script>alert('Hello, world.');</script>
|
strict-dynamic
- SD 意味着可信 js 生成的 js 代码是可信的。这个 CSP 规则主要是用来适应各种各样的现代前端框架,通过这个规则,可以大幅度避免因为适应框架而变得松散的 CSP 规则。
1
| Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic'
|
0x04 注意
(1)script-src和object-src是必设的,除非设置了default-src
- 因为攻击者只要能注入脚本,其他限制都可以规避。而object-src必设是因为 Flash 里面可以执行外部脚本
- script-src和object-src源列表不能包含含有攻击者可控制response的安全相关部分的源地址,或包含不安全的库。否则可以被绕过
(2)script-src不能使用unsafe-inline关键字(除非伴随一个nonce值),也不能允许设置data:URL。否则,可以被绕过.
(3)必须特别注意 JSONP 的回调函数
0x05 CSP实验
1 CSP测试
1
| document.write("<h1>This is a local js.</h1>");
|
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="https://raw.githubusercontent.com/ashin9/MDPic/master/secblog/20180706110443.png" height="300" width="300"> <iframe src="http://ashin9.github.io" width="200" height="200"></iframe> <iframe src="https://ashin9.github.io" width="200" height="200"></iframe> </body> </html>
|
1 2 3 4 5 6
| <meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.bootcss.com; child-src https:"> 脚本:只信任当前域名 插件:不信任任何URL,即不加载任何资源 样式表:只信任cdn.bootcss.com 框架(frame):必须使用HTTPS协议 其他资源:没有限制
|
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"/>
|
1 2
| <iframe src="http://ashin9.github.io" width="200" height="200"></iframe> <iframe src="https://ashin9.github.io" width="200" height="200"></iframe>
|
1
| <img src="https://raw.githubusercontent.com/ashin9/MDPic/master/secblog/20180706110443.png" height="300" width="300">
|
结果
- 外部资源引用失败,控制台给出了报错信息
- HTTP协议的frame加载失败,控制台给出了报错信息
- 其他可以加载的img资源和本地的js脚本都加载成功

2 report-uri测试
尽管report-to指令旨在取代已弃用的report-uri指令, report-uri已经从Web标准中删除,但在大多数浏览器中仍不受支持。因此,可以同时指定report-to和report-uri。
在支持的浏览器中report-to,report-uri指令将被忽略。
HTTP头信息的Content-Security-Policy字段开启CSP
1 2 3
| <?php header("Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /csp_report"); ?>
|
- 代码中利用的策略是限制外部非cdn.example.com域名下的样式表,并把报告发送到/csp_report目录。
1 2 3 4 5 6 7 8 9 10 11
| <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.bootcss.com/weui/0.4.0/style/weui.min.css查看实验结果。

- 由上图可以看到发送出了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脚本被警报了。

unsage-eval
将script-src值设置为unsafe-eval,这里还要开启内置事件的开关unsafe-inline才能执行内置事件。
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>
|

- 执行结果是可以看到利用eval函数将字符串当作代码执行了。
nonce值
- header头设置了script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’这里的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>o
<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>
|

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