6.3 DOM XSS挖掘

了解DOM渲染后有助于我们更好地进行DOM XSS漏洞的挖掘,本节会介绍一些常见的DOM XSS挖掘思路,这些思路都需要弄清楚输入点(sources)和输出点(sinks)是什么,相关内容在第3章中有过简单的说明。

6.3.1 静态方法

静态方法如果要工具化,可以使用下面这个链接提到的正则表达式来匹配:

http://code.google.com/p/domxsswiki/wiki/FindingDOMXSS

比如,输入点匹配的正则表达式如下:

/(location\s*[\[.])|([.\[]\s*["']?\s*(arguments|dialogArguments|innerHTML|write(ln)?|open(Dialog)?|showModalDialog|cookie|URL|documentURI|baseURI|referrer|name|opener|parent|top|content|self|frames)\W)|(localStorage|sessionStorage|Database)/

输出点匹配的正则表达式如下:

/((src|href|data|location|code|value|action)\s*["'\]]*\s*\+?\s*=)|((replace|assign|navigate|getResponseHeader|open(Dialog)?|showModalDialog|eval|evaluate|execCommand|execScript|setTimeout|setInterval)\s*["'\])*\s*\()/

一旦发现页面存在可疑特征,就进行人工分析,这是静态方法的代价,对人工参与要求很高。这个过程可以利用浏览器来达到这个目的,比如,Firefox下用Firebug能统一分析目标页面加载的所有JavaScript脚本,可以用自带的搜索功能,用正则表达式的方式进行目标的搜索非常方便。

6.3.2 动态方法

动态方法很难完美地实现检测引擎,这实际上是一次JavaScript源码动态审计的过程。从输入点到输出点的过程中可能会非常复杂,需要很多步骤,如果要这样一步步地动态跟踪下去,其代价是很高的,如果仅关注输入点与输出点,不关注过程,那么一些逻辑判断的忽视可能会导致漏报,比如,过程中会判断输入点是否满足某个条件,才会进入输出点。

下面先来看一些简单的模型,这有助于我们理解这个动态方法。

比如,如何检测出下面这个DOM XSS?

<script>
eval(location.hash.substr(1));
</script>

1)思路一

借用浏览器自身的动态性,可以写Firefox插件,批量对目标地址发起请求(一个模糊测试过程),请求的形式是:在目标地址后加上#fuzzing内容,比如其中一个模糊测试内容是:var x='d0mx55'。

在响应回来时,我们需要第一时间注入一段脚本劫持常见的输出点函数,劫持方式可以参考2.5.7节的“JavaScript函数劫持”,比如,劫持了eval函数如下:

var _eval=eval;
eval = function(x){
    if(typeof(x)=='undefined'){return;}
    if(x.indexOf('d0mx55')!=-1){alert('found dom xss');}
    _eval(x);
};

当eval(location.hash.substr(1));执行时,实际上是执行我们劫持后的eval,它会判断目标字符串d0mx55是否存在,若存在,则报DOM XSS。

在JavaScript层面劫持innerHTML这样的属性已经没那么容易了,常用的属性劫持可以针对具体的对象设置__defineSetter__方法,比如,如下代码:

window.__defineSetter__('x',function(){alert('hijack x')});
window.x ='xxxxyyyyyyyyyyyy';

当x赋值的时候,就会触发事先定义好的Setter方法。innerHTML属性属于那些节点对象,想劫持具体节点对象的innerHTML,需要事先知道这个具体节点的对象,然后设置__defineSetter__方法。

这样,如果要检测DOM XSS,就要劫持所有的输出点,比较麻烦,有没有更简单的方法?看思路二。

2)思路二

仍然借用浏览器动态执行的优势,写一个Firefox插件,我们完全以黑盒的方式进行模糊测试输入点,然后判断渲染后的DOM树中是否有我们期待的值,比如,模糊测试提交的内容都有如下一段代码:

document.write('d0m'+'x55')

如果这段代码顺利执行了,当前DOM树就会存在d0mx55文本节点,后续的检测工作只要判断是否存在这个文本节点即可,代码如下:

if(document.documentElement.innerHTML.indexOf('d0mx55')!=-1){
     alert('found dom xss');
};

这个思路以DOM树的改变为判断依据,简单且准确,不过同样无法避免那些逻辑判断上导致的漏报。