XSS-Jquery.html()+DOM破坏

目录

[靶场网址:​ https://xss.pwnfunction.com/challenges/ww3/ ​](#靶场网址: https://xss.pwnfunction.com/challenges/ww3/)

分析代码:

Jquery.html()解析原理:

DOM-clobbering

JS作用域&作用域链

​编辑


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

分析代码:

javascript 复制代码
<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的调用来加载到页面。他会对不规范的标签进行修复。

javascript 复制代码
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()方法

javascript 复制代码
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。

最终传参:

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

代码执行成功!

相关推荐
逐·風3 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫3 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦4 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子5 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享5 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf7 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨7 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL7 小时前
npm入门教程1:npm简介
前端·npm·node.js
小白白一枚1118 小时前
css实现div被图片撑开
前端·css