DOM XSS奥义

一些我们普遍了解的XSS 基本已经让大部分开发者们警醒,也都做了足够充分的教训,所以想要像以前那样随意的挖洞就不可能了,但是XSS 是无穷的,只要能深入源码,总会有所发现。

常常程序会动态加载json 数据,同域可以用ajax,而不同域时,就需要跨域请求,有一种方法是jsonp,利用callback回调完成跨域,而在调用外部数据的时候还会带上一些参数,而如果这些参数可控,我们就可以试图去挖掘漏洞。

对于跨域的请求来说,最常见的形式是这样:

1
somescript.src="http://otherdomain.com/xx?jsonp=callback"

而为了方便,callback 后边会带上一些参数,有一些参数是用户可控的,那时候,就会造成困扰了:

1
somescript.src="http://otherdomain.com/xx?jsonp=callback&id="+id;

如果其中的ID 可控,那就很有可能会带来问题,这算是一种地址可控,而地址可控分为三种形式:

一种是,完全可控,也就是src 后边的内容可以直接替换掉,这种可以直接利用,替换成我们的JS 地址。

一种是部分可控:

1
script src="/path/xxx/[路径可控]/1.js"

这种情况下一般是在同域下寻找一个有漏洞的上传点,上传些文件什么的,以便利用。

第三种情况是参数可控:

1
script src="/xxxx/json.php?callback=xxxx¶m1=yyy¶m2=[参数可控]"

以乌云某例为例:
http://sse1.paipai.com/comm_json?callback=commentListCallBack&dtag=1&ac=1&cluster=1&sellquality=0&NewProp=&Property=256&PageNum=1&PageSize=48&OrderStyle=80&Address=&SaleType=1&degree=1&AuthType=2&BeginPrice=&EndPrice=&KeyWord=2012%20%D0%C2&OnlineState=2&Paytype=4&ranking=&sClassid='aaaaaaaa&t=1354854681

经过简单在这个页面测试,我们可以发现,其中的callback, dtag, ranking 是可控的。不过可控的元素还是会被过滤的,比如常见的尖括号就一定会被过滤。

实际在使用中,访问这个跨域数据的是以下URL:
http://bag.paipai.com/search_list.shtml?type=&callback=alert(1);&np=11&pro=256&searchtype=2&cs=0010000&keyword=&PTAG=20058.13.13

因为dtag,ranking 是放在双引号里的,过滤了双引号,基本很难有可以应用的地方。而callback 则不是,如果能构造一个callback=alert(1) ,就可以执行XSS,不过在我们发现写在一开始的callback 并不能直接去改变值来控制它,我们可以想办法通过后边可控的参数,用&来分隔后再来一个callback=alert(1)来覆盖前边的callback。

不过一般来说,如果你在构造URL 的时候,如果使用了&,那就会直接认为你这是分隔符,这个方法就失效了。而如果我们试图使用%26 这个URL 编码来代替,但是它在传递的时候,并不会解码,所以也不会让服务器解析的时候才认定他是分隔符。

所以,只能去从源码里找漏洞,我们找到他的search.js脚本,定位到那一块观察上下文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function init() {
var keyword = decodeURIComp($getQuery('keyword')),
type = $getQuery('type'),
searchtype = $getQuery('searchtype');
option.keyword = keyword;
option.classId = type;
option.searchType = searchtype || option.searchType;
option.beginPrice = $getQuery('bp');
option.endPrice = $getQuery('ep');
option.NewProp = $getQuery('np') || $getQuery('newprop');
option.property = $getQuery('pro') || option.property;
option.cid = $getQuery('cid');
option.Paytype = $getQuery('pt') || option.Paytype;
option.hongbaoKeyword = $getQuery('hb');
option.conditionStatus = $getQuery('cs') || option.conditionStatus;
option.showType = $getQuery('show') || option.showType;
option.mode = $getQuery('mode') || option.mode;
option.address = decodeURIComp($getQuery('adr'));
option.orderStyle = $getQuery('os') || option.orderStyle || 80;
option.hideKeyword = $getQuery('hkwd') == "true" ? true: false;
option.ptag.currentPage = $getQuery('ptag') || $getQuery('PTAG');
var pageIndex = $getQuery('pi'),
pageSize = $getQuery('ps');
option.pageIndex = (pageIndex && $isPInt(pageIndex)) ? pageIndex * 1: option.pageIndex;
option.pageSize = (pageSize && $isPInt(pageSize)) ? pageSize * 1: option.pageSize;
};

这里的脚本,就是jason参数和当前页面获得参数的一些关系,而其中有一个函数让我们看到了希望: decodeURLComp,它在传进来的时候,会被解码一次,有木有想起什么。对于这个keyword,如果我们使用了URL 编码传%26进去,他会解码成&,那么我们直接使用%26callback=alert(1),那就可以会被解码成一个分隔符,然后出发我们的漏洞。

构造URL:
http://bag.paipai.com/search_list.shtml?type=213280&np=11&pro=256&searchtype=2&cs=0010000&keyword=%26callback=eval(String.fromCharCode(97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41));void&PTAG=20058.13.13

抓个包,可以看到接收的json 数据已经得到改变:

弹窗就是自然的了。

其实,这个漏洞已经显得有些运气成分了,也可以说是开发者在业务的逻辑关系变得复杂之后,往往就缺乏足够的安全意识,去处理这些跨域安全问题了,往往在源码上,会造成一些漏洞。上边的例子我们也能看到,本身在过滤的逻辑上,已经很难寻找漏洞,但是因为开发者在处理流程的时候,没有去思考它可能的上下文关系,也就主动创造了一个漏洞出来。

DOM XSS 的内容,大概也就这么多了。在DOM XSS 漏洞的挖掘中,最常用的自动化挖掘方式,其实就是利用爬虫和抓包重放,爬虫通过遍历某网站的各种结构URL,然后抓包重放去构造独特的字符替换掉URL中那些可控的参数,通过服务器返回的状态,和内容,去挖掘可能存在的不安全因素。

而挖掘到不安全因素,只是XSS 最早的第一步,现在,那些最常见的漏洞已经基本销声匿迹,需要的是通过分析源码,寻找某个点的上下文关系,通过理清逻辑关系,寻找开发者在其中的疏漏,才能创造出合适的XSS。

script>