Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1173034
  • 博文数量: 272
  • 博客积分: 3899
  • 博客等级: 中校
  • 技术积分: 4734
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-15 14:53
文章分类

全部博文(272)

文章存档

2012年(272)

分类: 系统运维

2012-06-27 13:44:10

其实我研究这个东西很久了,在今年年初就做了个残品,无奈一直都缺乏时间好好做完,同时残品的方案也有一定困难,所以我抛弃了那个方案,另外想了思路;

 

最近和同事一起讨论,再参考了商业软件的一些做法,总算是确定了一个思路。

 

我去年写过一篇blog

Javascript的输入输出,以及二次渲染问题

 

就已经在探讨这个问题了,当时的产出是有了一个防御方案。

 

今年7月底的blackhat 2010上,shreeraj shah 做了一个“ ” ,并发布了两个工具用于检测javascript里的安全问题,包括DOM XSS

 

我看了他的成果后,发现这个思路和我是一致的,甚至我还走在了他的前面,但当时遇到不可逾越的障碍。

 

直到前不久,我结合了商业化扫描器的思路,以及和同事讨论后,得出了一个较为靠谱的方案。

 

理论基础:

DOM based XSS 的产生原因,我们只需要关注两个方面

A 脏数据的输入

location

document.referrer

window.name

ajax response

jsonp

form下的inputs

 

B) 脏数据的输出

document.write(ln)

innerHTML =

outterHTML =

window.location 操作

javascript: (伪协议后内容的自定义)

evalsetTimeout setInterval 等直接执行

 

关于DOM XSS 的输入点, "DOM XSS之父" 总结了一个表:


 

 

基于这个理论基础,我们要实现自动化检测,就很好做了。

 

一开始我的想法是,先找到 document.write 等会造成危害的地方,然后回溯变量与函数调用过程,看用户是否能够控制输入。这也是一般挖漏洞的思路。shreeraj shah 也是这个思路。

 

这个思路最大的问题,就是在回溯变量与函数调用过程中,会极其复杂,而且大部分时间会发现做了无用功。

 

我基于firefox greasemonkey 写了一个脚本,其中核心代码部分:


点击(此处)折叠或打开

  1. // Dangerous Methods
  2. var dm = {dw:"document.write", ih:"innerHTML"};

  3. // get All Scripts
  4. var ws, arr, arrline;
  5. ws = document.getElementsByTagName("script");
  6. arr = [];
  7. /*
  8. arr[i] = {
  9.     content = raw_content;
  10.     fn = filename;
  11.     lc = [line1,line2,....];
  12. };

  13. */
  14. arrline = [];

  15. // 变量列表
  16. var vlist = new Array();

  17. // 获取所有scripts内容,包括ajax
  18. for (i=0; i<ws.length; i++){
  19.     var m = {};
  20.     if (ws[i].textContent){
  21.         m.fn = window.location.href;
  22.         m.content = js_beautify(ws[i].textContent);
  23.     arr.push(m);
  24.   } else if (ws[i].src){
  25.       GM_xmlhttpRequest({
  26.       method: 'GET',
  27.       url: ws[i].src,
  28.       headers: {
  29.         'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
  30.         //'Accept': 'application/atom+xml,application/xml,text/xml',
  31.       },
  32.       onload: function(responseData) {
  33.           m.fn = this.url;
  34.           m.content = js_beautify(responseData.responseText);
  35.         arr.push(m);
  36.       }
  37.    });
  38.   }
  39. }


  40. // 移除文本中的静态内容 'zzz', "xxx"
  41. function removeStatic(str, c){
  42.     var s, result, start, end, i;

  43.     if (str.charAt(0) == c){ //第一个引号
  44.         start = 0
  45.     } else {
  46.       for (i=1;i<str.length;i++){
  47.           if (str.charAt(i) == c && str.charAt(i-1) !== '\\'){
  48.               start = i;
  49.               break;
  50.           }
  51.       }
  52.   }
  53.       
  54.   for (i=start+1;i<str.length;i++){ // 第二个引号
  55.       if ( str.charAt(i) == c && str.charAt(i-1) !== '\\'){
  56.         end = i;
  57.         break;
  58.       }
  59.   }

  60.   s = str.substring(0, start) + str.substring(end+1, str.length);
  61.   result = s;
  62.   
  63.   return result;
  64. }

  65.     
  66. // 发现并处理 innerHTML 的函数
  67. function checkInnerHTML(rawScript, n){
  68.     var tl = rawScript.lc[n], r = rawScript.content, fn = rawScript.fn;
  69.     var index, tmp, c;
  70.     
  71.     if (tl.charAt(tl.length-1)!=';'){ // 补上个 ;
  72.         tl = tl+';';
  73.     }
  74.     
  75.   var tlr = tl.replace(new RegExp(" ","g") , ""); // 去除空格
  76.   if (tlr.indexOf('innerHTML=')>0){ // check 注意有多个 innerHTML=的情况;
  77.       index = tlr.indexOf('innerHTML=');
  78.       tmp = tlr.substring(index+10);
  79.       
  80.       // 去除 '', "" 中的内容
  81.     c = '\'';
  82.     while (tmp.indexOf(c)>=0){
  83.         tmp = removeStatic(tmp, c);
  84.     }
  85.     
  86.     c = '"';
  87.     while (tmp.indexOf(c)>=0){
  88.         tmp = removeStatic(tmp, c);
  89.     }

  90.       tmp = tmp.split(';')[0]; // 获取innerHTML的语句 innerHTML=....;

  91.     if (tmp !== ''){
  92.         //vlist.push(tmp);
  93.         
  94.         // 如果全是符号或数字,则也不是变量,变量必然是包含了字母的
  95.         var containAlpha = false;
  96.         for (var i=0;i<tmp.length;i++){
  97.             if ((tmp.charCodeAt(i)>=65 && tmp.charCodeAt(i)<=90) || (tmp.charCodeAt(i)>=97 && tmp.charCodeAt(i)<=122) ){ // a-z,A-Z
  98.                 containAlpha = true;
  99.                 break;
  100.             }
  101.         }
  102.         if (containAlpha == false){
  103.             return -1;
  104.         }

  105.         // 找到函数名,与回溯函数过程
  106.         var f, st='';
  107.         for (var x=n; x>=0; x--){
  108.             if (rawScript.lc[x].indexOf('function')>=0 || x==0){
  109.                 f = rawScript.lc[x];
  110.                 for (var y=x;y<=n;y++){
  111.                     st = st+rawScript.lc[y]+'\r\n';
  112.                 }
  113.                 break;
  114.             }
  115.         }
  116.         

  117.         GM_log('\r\n'
  118.         +'Script: '+fn+' line '+n+':\r\n'
  119.         +'raw: '+tl+'\r\n'
  120.         +'after: '+tmp+'\r\n'
  121.         +'function: '+f+'\r\n'
  122.         +'stack: \r\n'+st+'\r\n'
  123.         );

  124.           return 1;
  125.       }

  126.   } else {
  127.       return -1;
  128.   }
  129. }


  130. // 发现并处理 document.write 的函数
  131. function checkDocumentWrite(rawScript, n){
  132.     var tl = rawScript.lc[n], r = rawScript.content, fn = rawScript.fn;
  133.     var tmp, index, c;
  134.         
  135.   var tlr = tl.replace(new RegExp(" ","g") , ""); // 去除空格
  136.   index = tlr.indexOf('document.write(');
  137.   if (index>=0){
  138.       tmp = tlr.substring(index+15);
  139.   }
  140.   
  141.   // 去除 '', "" 中的内容
  142.   c = '\'';
  143.   while (tmp.indexOf(c)>=0){
  144.    tmp = removeStatic(tmp, c);
  145.   }
  146.   c = '"';
  147.   while (tmp.indexOf(c)>=0){
  148.    tmp = removeStatic(tmp, c);
  149.   }
  150.   
  151.   tmp = tmp.split(')')[0];
  152.   
  153.     if (tmp !== ''){
  154.     // 找到函数名,与回溯函数过程
  155.     var f, st='';
  156.     
  157.     for (var x=n; x>=0; x--){
  158.         if (rawScript.lc[x].indexOf('function')>=0 || x==0){
  159.             f = rawScript.lc[x];
  160.             for (var y=x;y<=n;y++){
  161.                 st = st+rawScript.lc[y]+'\r\n';
  162.             }
  163.             break;
  164.         }
  165.     }

  166.       GM_log('\r\n'
  167.       +'Script: '+fn+' line '+n+':\r\n'
  168.       +'raw: '+tl+'\r\n'
  169.       +'after: '+tmp+'\r\n'
  170.       +'function: '+f+'\r\n'
  171.       +'stack: \r\n'+st+'\r\n'
  172.       );
  173.   }
  174.   return 1;
  175. }

  176. var timer = 0;
  177. var found = false;
  178. // 主函数
  179. var tag = setInterval(function(){
  180.     if (arr.length == ws.length || timer == 2){
  181.         clearInterval(tag);
  182.         
  183.     for (i=0;i<arr.length;i++){
  184.         arr[i].lc = arr[i].content.split('\n');
  185.         
  186.         for (j=0;j<arr[i].lc.length;j++){
  187.             if (arr[i].lc[j].indexOf("innerHTML")>0 ){
  188.               if (checkInnerHTML(arr[i], j) == 1){
  189.                   found = true;
  190.               }
  191.             }

  192.             if (arr[i].lc[j].indexOf("document.write")>=0){
  193.                 if (checkDocumentWrite(arr[i], j) == 1){
  194.                   found = true;
  195.               }
  196.             }
  197.                 
  198.         }
  199.     }
  200.     
  201.     if (found == false){
  202.         GM_log("Not Found!");
  203.     }
  204.     }
  205.     else {
  206.         GM_log('Read: '+arr.length+' Scripts:'+ws.length+' '+'Waiting....');
  207.     }
  208.     timer++;
  209. },2000);
使用效果:

(注意这里其实已经是一个误报了,因为 j 明显是个 int)

后来与同事讨论后,发现从输入端检测变量是否有被污染,从程序实现上会更简单。

我们看IBM Watchfire公布的一张图,他们准备这么做,不过此时尚未发布任何东西。

 

 

url 这个变量是从污染源 document.URL 来的,经过变量传递,陆续污染了 变量 tempArr TARGET

最终变量 TARGET 被输出函数 document.write 输出,造成了XSS

所以这种检测思路就是从输入入手,观察变量传递的过程,最终检查是否有在危险函数输出,中途是否有经过安全函数,比如 htmlencode 之类。

http://blog.watchfire.com/wfblog/2010/11/scanning-for-client-side-javascript-vulnerabilities.html

Acunetix 的思路也类似

http://www.acunetix.com/blog/web-security-zone/articles/dom-xss/

这种是白盒自动化检测的思路,注意它一定是要工作在javascript层,也就是说客户端需要有一个javascript引擎,解析执行了javascript后,去分析,否则会漏掉比较多的内容。比如:

document.write("");

有一段脚本是远程加载的,不解析执行的话,可能就漏掉了。

我之前还想到了另外一种FUZZ的思路,对于单个页面的准确率会非常高,但是资源消耗也会特别大。

1. 混淆所有输入点

2. 页面渲染后,看是否会执行混淆后的脚本,比如 alert(123)

3. 遍历出页面中所有的javascript事件,然后一一触发之,看是否会执行

因为有的dom xss是隐藏在函数里的,只有一些事件能够触发这些函数,比如:

abc

这个FUZZ的思路,应该也是可行的,可以与白盒方法结合起来使用。

阅读(3879) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~