1. 对于flash的addCallback与lso结合的案例,之前乌云已有不少案例了。
但实际上,addCallback的返回值不一定是要从lso来读取,也可以是其它来源。
不难假想以下存储型XSS的场景。
用户-->存储数据-->数据被读取进入flash-->数据进入addcallback返回值。
2. 带着上面这个假想,对QQ空间日志功能中所使用的FLASH文件进行逐个排查,我们定位到了以下这个文件。
http://edu.qzs.qq.com/qzone/app/blog/v6/swf/MusicPlayer.swf ,反编译后,可以看到以下两行代码:
其中,我们可以注意 __onGetSoundFunName 函数的返回值类型是Object,如下所示:
返回值类型是OK的,那么这个返回值我们是否可以控制呢? 根据以上代码,可以看到是 _local1.url, _local1.song ... singer 什么的,
不用去跟踪代码,也大概能猜出,是歌曲地址,歌曲名称,歌手名称等,
而我们在写模板日志,添加歌曲后,发送的请求中,有以下部分:
不难发现,这个data属性中的数据,正好对应 __onGetSoundFunName 函数返回值中的4个值。
根据之前的知识,我们不难构造出利用代码。
3. addCallback要能成功被利用,还有一个关键就是,页面必须调用 addCallback 所注册的函数,也就是说,
如果FLASH中有一个callback
那么JS中必须调用一次 :
才能导致XSS。
在本例中,根据JS代码中的定义, getSoundFunName = getSound,即需要执行一次下面的代码:
4. 为什么要强调这个呢? 因为,在查看日志的时候,页面并不会执行 getSound函数, 这导致我们精心构造的addCallback xss无法被执行。查看了下代码,发现getSound函数,只有在编辑日志,点提交按钮的时候才会执行,
编辑日志。。这。。。显然是自己X自己的节奏。。
5. 那我们就这么放弃了吗? 显然不是,我们得想办法让日志中,执行一次 document["flash ID"].getSound(),方法呢?
直接插入JS代码来执行这句?如果能直接插入JS,我们还用上面这么费力吗?不可取。
事实告诉我们,鸡肋+鸡肋才是王道。
这里我们还注意到:http://edu.qzs.qq.com/qzone/app/blog/v6/swf/MusicPlayer.swf 中还有下面的代码。
而 this.flashInitFunName 来自于
_local2 相当于 root.loaderInfo.parameters,即 flashInitFunName 来自于外部参数 flashInitFunName ,但传入后经过了replaceNotAvaCha函数的过滤。
也就是说,flashInitFunName只允许 数字,字母,下划线和小数点,直接用这个参数来XSS是不行了,但是对于我们调用 document["flash ID"].getSound() 的目的来说,却是完全足够了。
这里 FLASH播放器的ID是 TemplateBlog_MusicPlayer12_flash
那么我们要让FLASH执行 getSound ,需要在当前页面中再插入下面一个FLASH文件即可。
http://edu.qzs.qq.com/qzone/app/blog/v6/swf/MusicPlayer.swf?flashInitFunName=document.TemplateBlog_MusicPlayer12_flash.getSound
为了保证被插入的FLASH不被其它因素影响到JS的执行,我们将其它外部参数都补全。
http://imgcache.qq.com/qzone/app/blog/v6/swf/MusicPlayer.swf?mode=view&skinUrl=http://imgcache.qq.com/qzone/app/blog/v6/swf/Skin1.swf&flashInitFunName=document.TemplateBlog_MusicPlayer12_flash.getSound&soundPlayStateCallBackFunName=window.TemplateBlogMusicPlayer.soundPlayStateCallBack&openSoundSelectCallBackFunName=window.TemplateBlogMusicPlayer.selectMusic&setSoundFunName=setSound&getSoundFunName=getSound&statisticsFunName=window.TemplateBlogMusicPlayer.statistics
6. 最后,我们就是要往日志中插入上面这个FLASH了。。但是显然这个步骤也是十分困难的。为什么困难呢?
6.1 日志中是不允许随意插入指定URL的FLASH的
6.2 模板日志中,会对日志的内容进行parse,parse之后,object标签会被无视掉,从而不会被输出到日志DOM中。
7. 带着6的2个问题,我们一个一个来解决,首先是第一个问题:
我们借助 QQ空间某功能缺陷导致日志存储型XSS - 13 ( WooYun: QQ空间某功能缺陷导致日志存储型XSS - 13 )中提到的技巧,将movie写为 movie,即可绕过腾讯的限制,具体原理不在此贴再说一遍了。。。。
这样一来,我们可以在普通日志中插入这个FLASH了。
8. 但是我们的场景是模板日志, 模板日志中会有以下代码:
其中g_oBlogContent是原始日志内容,经过replace中的parseData之后,我们插入的object标签就会没了。
为了解决这个问题,我们看看 TemplateBlogParser.parseData 函数
content参数被传入,继续查看其中的调用,当我们看到content进入 Title.parse后有希望:
由上图可见,<div name="title>xxxx</div>之间的部分会被当作结果返回,这样一来,我们可以把我们的object标签放入
<div name="title"><object 标签> ... </object></div>
最后<object 标签> ... </object>将会当作内容返回,并输出到DOM中。
9. 综上所述,同一个FLASH,被日志自带功能嵌入一次,执行setSound,插入我们恶意构造的XSS代码, 又被我们通过技巧嵌入一次,实现getSound的调用。
完整的利用代码如下:(发表模板日志时,html字段修改为以上内容,发表。效果见漏洞证明)
10. 当然,同上一篇 13 中一样,由于chrome下classid的问题,导致无法在chrome下执行,本例测试环境为 win7 + ie8