转载

打造一个自动检测页面是否存在XSS的插件Ⅲ

前言

本章主要是对“form表单XSS检测”进行介绍,以及对反馈页面的修改。上一章我就说过“form表单XSS检测”有点复杂。

所以我就重新开了一章来写,也就是这章。关于代码的构架,请移步到FreeBuf之前文章《 打造一个自动检测页面是否存在XSS的插件Ⅱ 》的第0×01和0×02小节。本章就不涉及代码构架了。

0×01 检测思路

先判断页面是否存在form表单,如果不存在,则不运行相关的检测代码。当页面存在form表单时,则过滤出正确符合条件的form表单,满足下面的条件:

一、Form表单里如果存在img标签,则判断src属性值,查看后缀是是否为jpg、png、jpeg、gif,如果不为这些后缀,说明是“验证码”返回false,因为存在“验证码”的form表单,ajax发送会出错,因为我们不知道获取“验证码”的值。 二、form标签必须存在input的type属性为submit的标签,text、password、radio、checkbox这些必须要有一个存在才可以。

上面筛选的结果只是最初的筛选结果,还需要判断form表单里的input是否存在name值,不然无法构造ajax发送的数据。

然后就是发送了。“form表单XSS检测”主要就是筛选出正确符合条件的麻烦。还有发送时数据的处理也比较麻烦。

0×02 测试环境

根据上面的检测思路,我们需要一个符合思路的测试环境。如下:

一、需要一个form表单里存在图片,而且可以触发XSS。 二、需要一个form表单里存在图片,不可以触发XSS。 三、需要一个form表单里存在以“动态文件”(验证码图片)为后缀的form表单。 四、需要一个没有图片,可以触发XSS的form表单。 五、需要一个没有图片,不可以触发XSS的form表单。

测试环境代码如下。

存在图片,而且可以触发XSS:

<form method="post">     <input type="text" name="xss">     <img src="http://image.3001.net/images/new/logo.png" alt="">     <input type="submit"> </form><br><hr>

存在图片,不可以触发XSS:

<form method="post" action="http://baidu.com/">     <input type="text" name="xss">     <img src="http://loudong.360.cn/images/logo.png" alt="">     <input type="submit"> </form><br><hr>

存在以“动态文件”(验证码图片)为后缀的form表单:

<form action="#" method="post">     <input type="text" name="xss">     <img src="http://passport.360.cn/captcha.php?m=create&app=i360&scene=login&userip=ra6YBkHL%2B8G6xqIXmQwvKw%3D%3D&level=default&sign=818d50&r=1441020794&_=1441020960160">     <input type="submit"> </form><br><hr>

没有图片,可以触发XSS的form表单:

<form  method="get">     <input type="text" name="xss">     <input type="text" name="caonima">     <input type="submit"> </form><br><hr>

没有图片,不可以触发XSS的form表单:

<form  method="get" action="http://360.cn/">     <input type="text" name="asd">     <input type="text" name="wocaonimabi">     <input type="submit"> </form>

效果如下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

光写html代码可不行,没有输出怎么办,下面是PHP输出的代码:

<?php  if(isset($_POST['xss'])){   echo $_POST['xss'];  }  if(isset($_GET['a'])){   echo $_GET['a'];  }  if(isset($_GET['caonima'])){   echo $_GET['caonima'];  } ?> 

0×03 函数变量

建立一个名为form_Xss函数function form_Xss(){…}

下面是form_Xss函数里的变量:

var tureForm;     //存放符合条件的form表单数据 var tureInput;        //存放符合条件的input标签数据 var formImg;      //form表单里的img标签里的src属性值后缀 var actionUrl;        //form表单的action属性的值 var methodType;   //form表单的method属性的值 var sendDataUrl;  //发送时的URL var i;             //第一层for循环的初始变量 var j;             //第二层for循环的初始变量

0×04 form表单筛选

在0×01节说了那么多的判断,就是要筛选符合正确条件的form表单,这里我们不能一直都用for{if…eles…}来完成,太费时间,效率也不高。幸好在JavaScript里,提供了filter这个函数,这个函数会对数组中的每一项都给定一个函数,返回以ture为结果的数组,例如:

var numbers = [1,2,3,4,5]; filterNubmbers = numbers.filter(function(item,index){ return (item > 3) })

会返回number数组里,所有大于3的数组,然后返回一个新的数组,上面的执行结果如下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

item是数组里的项,index是项在数组里的位置。

这里有个坑,后面会遇到,这里说明一下。

fliter函数是提供给Array的,也就是Array.fliter(function(item,index){…}),但是也可以使用Object。也就是Object.filter(function(item,index){…})

只是当为Object对象时,item和index会互换。前面说到item是数组里的项,但是当为Object时,item就是项在数组里的位置,index才是数组里的项。切记,切记,切记。

前奏说完了,开始进入正题。

我们需要获取form表单,再进行判断。于是代码就如下:

tureForm = $("form").filter(function(item,index){});

Jquery返回的是“Object对象”这点切记,也就是说什么的filter其实是对象。那item和index就已经互换了。下面的index就是数组里的项,也就是页面中每一个form表单数据。我们打印看下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

接下来就是获取form里面的img标签里的src属性:

formImg = $(index).find("img").attr("src");

forImg现在就等于img标签里src的值了。下面就是做判断是否存src这个属性:

if(!!formImg){    /*当存在src属性时,运行代码*/ }else{    /*当不存在src属性时,运行的代码*/ }

!!的作用将后面的forImg强制转换为布尔类型的数据。方便if判断。

这里我们还需要一个if,因为很多的网站,为了防止浏览器对图片进行本地缓存(防止图片无法第一时间更新)而在图片地址后面加上随机参数。我们现在就使用if来去掉这些随机参数。因为随机数一般都是类似这样:

http://www.freebuf.com/logo.png?rand=012350154453

这里我们就要去掉?rand=012350154453,只留下http://www.freebuf.com/logo.png字符串。代码如下:

if(formImg.indexOf("?") != "-1"){     formImg = formImg.slice(0,formImg.indexOf("?")); }

当存在"?"字符串时,则删除?后面的字符串,留下删除后的字符。如下图:

打造一个自动检测页面是否存在XSS的插件Ⅲ

这样一来,就去掉了随机数,接下来就是获取文件的后缀名了。

formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);

formImg.lastIndexOf(".")是substr函数的第一个参数,计算formImg字符串从后开始第一个"."的下标位置,为什么不用indexOf呢,因为去掉参数后,后缀也就只有几个字符,而前面则是非常多的字符串。这样一来,速度快一些。获取从最后开始出现的"."字符。而且url地址都会有.。比如freebuf.com或者127.0.0.1这样获取的字符串就不准确了。

formImg.length则是formImg变量的长度,也就是去掉参数的图片地址有多少个字符串。

formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);这一段代码就完成了获取后缀的功能,我们开输出看下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

获取后缀后,接下来就是判断后缀是否符合条件了:

if(formImg == ".png" || formImg == ".jpg" || formImg == ".jpeg" || formImg == ".gif"){     /*当符合上面的条件时,运行代码*/ }

||管道符的作用是,当前为false时,再判断后面的。说详细点就是:

先判断获取的后缀是否为png,如果不为png则再判断是否为jpg,如果不为jpg再判断是否为jpeg,如果不为jpeg判断是否为gif。如果都不是,则不运行代码。至于else就不用写了。为什么呢?因为当没有return时,JavaScript默认为false。就像下面这样:

打造一个自动检测页面是否存在XSS的插件Ⅲ

有的人可能会问为什么不判断是否为php、asp后缀呢?原因很简单,光一个php就有php3、php等格式了。而且只需要在服务端配置下。nihao这样的后缀都可以以php的方式解析,所以说我就使用了图片格式来进行判断。

当判断成功后,接下来就是return的事了,其实这里还需要一个判断,就是判断当前的form表单里的input[type]属性的值是否存在text、password、radio、checkbox其中之一,而且必须存在type=submit。不然还是不符合条件。总不能还写一个if,那也太麻烦了。这里我们就使用return来判断。因为filter函数只返回turn组成的数组,利用这个特性,我们就在return里判断。代码如下:

return $(index).find(":submit").length > 0 && ($(index).find(":text").length > 0 || $(index).find(":password").length || $(index).find(":radio").length > 0 || $(index).find(":checkbox").length > 0);

当前的表单必须存在submit,如果不存在则返回false,如果存在则判断后面的表达式是否为turn,因为&&是必须两个表达式都为turn时,才返回turn。也就是说($(index).find(":text").length > 0 || $(index).find(":password").length || $(index).find(":radio").length > 0 || $(index).find(":checkbox").length > 0)这里的表达式,必须有一个符合条件才可以。

OK,这个时候存在图片的判断已经写完了,那当前的form表单不存在图片怎么办呢?这个时候我们就不需要那么多的if判断图片格式了。我就直接在else里写上:

return $(index).find(":submit").length > 0 && ($(index).find(":text").length > 0 || $(index).find(":password").length || $(index).find(":radio").length > 0 || $(index).find(":checkbox").length > 0);

就可以搞定了。

现在我们来看下反馈是怎么样的:

打造一个自动检测页面是否存在XSS的插件Ⅲ

这个时候可以清楚的看到,第三个form表单。也就是验证码的那么表单,已经被忽视了。

接下来就是判断如果当前页面符合上文这些条件的表单一个都没有,那就跳出这个函数体。代码如下:

if(tureForm.length <= 0){     return false; }

当tureForm一个数组都不存在时,则就跳出这个函数体(form_Xss),就不在向下运行代码了。

现在我们还要在判断当前表单的input是否存在name属性,如果不存在则跳出这个函数体,为什么要判断这个呢,因为没有name值的话,ajax无法构造请求,这样无法发送数据,更别谈检测了。代码如下:

tureForm = $(tureForm).filter(function(item,index){     return (!!$(index).find(":input").attr("name")); }) if(tureForm.length <= 0){     return false; }

filter这段代码完成的功能就是,把当前的form表单重新组合,把具有name属性的input标签组成新的form表单,把不存在name属性的标签给剔除掉。

下面的if是判断当前新的form标签里有没有input了,没有input的话,就跳出循环。

完整的代码如下:

tureForm = $("form").filter(function(item,index){  formImg = $(index).find("img").attr("src");  if(!!formImg){   if(formImg.indexOf("?") != "-1"){    formImg = formImg.slice(0,formImg.indexOf("?"));   }   formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);   if(formImg == ".png" || formImg == ".jpg" || formImg == ".jpeg" || formImg == ".gif"){    return $(index).find(":submit").length > 0 && ($(index).find(":text").length > 0 || $(index).find(":password").length || $(index).find(":radio").length > 0 || $(index).find(":checkbox").length > 0);   }  }else{   return $(index).find(":submit").length > 0 && ($(index).find(":text").length > 0 || $(index).find(":password").length || $(index).find(":radio").length > 0 || $(index).find(":checkbox").length > 0);  } }) if(tureForm.length <= 0){  return false; } tureForm = $(tureForm).filter(function(item,index){   return (!!$(index).find(":input").attr("name")); }) if(tureForm.length <= 0){  return false; } 

0×05 发送数据

现在的tureForm包含了具有符合条件的form表单数据的数组,这里我们就可以使用for循环来依次拼凑&发送&反馈了。代码如下:

for(i = 0;i < tureForm.length;i++){     /*tureForm[i]代表当前的form表单*/ }

接下来就是获取当前form表单的action发送地址和method发送模式了。

actionUrl = $(tureForm[i]).attr("action"); methodType = $(tureForm[i]).attr("method");

actionUrl代表了当前form表单的action字符串,methodType同理。

这里可能又问会问,如果没有action或者method怎么办呢,在说这个之前,我先科普下小知识,当form表单没有action属性时,默认是提交到本页面的,而没有method属性时,默认是以get的请求发送的。知道这些下面的代码就好理解了:

if(actionUrl == undefined || actionUrl == "#" || actionUrl == ""){     actionUrl = href; } if(methodType == undefined || methodType == ""){     methodType = "get"; }

判断是否为undefined是因为当form表单没有action或者method属性时会返回undefined。至于为什么要判断action是否为#,因为#代表的也是本页面。

当不存在action属性或者action为#、空时,action为本页面。method同理。

下面就是去掉当前form表单里的除submit之外所有的input个数:

tureInput = $(tureForm[i]).find("input:not(:submit)").length;

下面就是对这些input的拼凑。这里还需要一个for循环,最外层的for循环是获取当前的form表单,这里的for循环是获取当前form表单里的input标签。

for(j = 0;j < tureInput;j++){     sendData += $(tureForm[i]).find("input:not(:submit)")[j].getAttribute("name") + "=" + onlyString + j + "&"; }

sendData是拼凑后的字符,至于为什么这里不用attr,而是用getAttribute这种原生态JavaScript,因为前面的[j]就已经把前面的jQuery对象转成原生态的JavaScript了,想要使用attr也可以,这样写:

$($(tureForm[i]).find("input:not(:submit)")[j]).getAttribute("name")

只是这样太浪费效率了,jQuery转JavaScript再转jQuery,不值得。

onlyString + j就是当前这个input的唯一标识符了。

当然for循环后肯定会多出一个&,现在我们来把他去掉:

sendDataUrl = sendData.substring(0,sendData.length-1);

因为sendData使用的+=,所以sendDate不能在全部变量里什么,于是我们在for开始的第一行写上var sendData = "";

就可以每一次循环就把sendDate清空。

现在我们来看看反馈:

打造一个自动检测页面是否存在XSS的插件Ⅲ

打造一个自动检测页面是否存在XSS的插件Ⅲ

下面就是利用ajax发送数据了:

$.ajax({  url: actionUrl,  type: methodType,  dataType: 'text',  data: sendDataUrl,  async:false, }) 

Url是action发送地址,type是method发送模式,data就是拼凑的数据。

async:false是以异步发送,防止for+ajax的bug。

当发送成功后,我们需要再在服务端反馈的数据里找到某个form标签里的某个input的唯一标识符。

发送成功的ajax函数是:

.done(function(data){     /*data为发送成功后,服务端的反馈的数据*/ })

先var xss = "";,把存在XSS的数据,存放在里面。

因为这个时候,我们还在最外层的for循环里,所以我们可以使用tureInput(当前form表单除去submit之外的input个数)变量。

我们就拿这个来进行for循环。

for(j = 0;j < tureInput;j++){     if(data.indexOf(onlyString + j) != "-1"){         xss += j + 1 + "|";     } }

If判断当前form发送的数据里存不存在XSS。这里的onlyString + j和上面拼接的数据是一样的,不然无法判断,当data.indexOf(onlyString + j) != "-1"的结果为true时,说明存在XSS,这个时候我们在把存在XSS的数据,放到xss变量里。

至于为什么要+1,是因为tureInput的长度是从0开始的,如果不+1,那到时候在日志反馈的时候,就是第0个input存在XSS漏洞了。至于后面会多出一个|字符,下面再做操作。

然后if判断当前的form存不存在XSS漏洞,其实也就是判断xss变量是否为空。

if(xss == ""){     return false; }else{     /*存在XSS时,要运行的代码*/ }

当不存在时,跳出本函数,注意这个时候的函数体不是form_Xss()了,而是.done(function(data){}这个函数体。切记。

当存在XSS时,先去掉最后面的|字符。

xss = xss.substring(0,xss.length-1);

然后就是回显了,我这里提供三个回显的代码:

一、alert("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞"); //直接弹出,因为页面的form表单无法直观的看到哪个是第一个,哪个是第二个,所以,这里就使用action来做为标识符。就像下面这样:

打造一个自动检测页面是否存在XSS的插件Ⅲ

打造一个自动检测页面是否存在XSS的插件Ⅲ

二、$(tureForm[i]).find("input").eq(xss – 1).css("border"," 3px solid red");直接给存在input的标签外部画上css,看起来直观,但是页面就不怎么好看了,就像下面这样:

打造一个自动检测页面是否存在XSS的插件Ⅲ

三、$("body").append("<img src='http://xss.cn/formXSS.html?host=$" + href + "&$xss=$" + xss + "&$url=$" +actionUrl + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");   //也是我推荐的方式,反馈的结果如下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

完整的代码如下:

for(i = 0;i < tureForm.length;i++){  var sendData = "";  actionUrl = $(tureForm[i]).attr("action");  methodType = $(tureForm[i]).attr("method");  if(actionUrl == undefined || actionUrl == "#" || actionUrl == ""){   actionUrl = href;  }  if(methodType == undefined || methodType == "#"){   methodType = "get";  }  tureInput = $(tureForm[i]).find("input:not(:submit)").length;  for(j = 0;j < tureInput;j++){   sendData += $(tureForm[i]).find("input:not(:submit)")[j].getAttribute("name") + "=" + onlyString + j + "&";  }  sendDataUrl = sendData.substring(0,sendData.length-1);  $.ajax({   url: actionUrl,   type: methodType,   dataType: 'text',   data: sendDataUrl,   async:false,  })  .done(function(data) {   var xss = "";   for(j = 0;j < tureInput;j++){    if(data.indexOf(onlyString + j) != "-1"){     xss += j + 1 + "|";    }   }   if(xss == ""){    return false;   }else{    xss = xss.substring(0,xss.length-1);    // $(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red");    // alert("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞");    $("body").append("<img src='http://xss.cn/formXSS.html?host=$" + href + "&$xss=$" + xss + "&$url=$" +actionUrl + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");   }  }) } 

0×06 重写反馈

在上一节,我们看到第三种方法的反馈,现在就是重写这种反馈。我们这里就直接说代码了,日志环境的重新搭建,请移步FB文章《 打造一个自动检测页面是否存在XSS的插件Ⅱ 》的第0×01和0×02小节观看。

重新后的代码和直接比,是没有什么太大的变化的,就是添加了“默认隐藏”、“点击打开”、“提醒文字”、“把html移到JavaScript之外,JavaScript只负责数据处理”。

因为默认是隐藏,点击在打开,所以我们需要css代码。如下:

<style>  .hide{   display: none;  }  .show{   display: inline-table !important;  }  small{   color: #898FFF;  } </style> 

大体的格式如下:

打造一个自动检测页面是否存在XSS的插件Ⅲ

Script里就是处理数据的,和之前的是没什么太大变化。

这里因为要实现“默认隐藏”和“点击隐藏”,我们在body下面需要再新建一个script标签,代码如下:

<script>  $(".panel-heading").next("table").addClass('hide');  $(".panel-heading").click(function() {   if($(this).next("table").attr("class") == "table hide"){    $(this).next("table").removeClass('hide');    $(this).next("table").addClass('show');   }else{    $(this).next("table").removeClass('show');    $(this).next("table").addClass('hide');   }  }); </script> 

完整的的代码如下:

<!DOCTYPE html> <html> <head>  <meta charset="utf-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <title>XSS反馈</title>  <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script>  <link rel="stylesheet" type="text/css"href="http://apps.bdimg.com/libs/bootstrap/3.3.4/css/bootstrap.css">  <style>   .hide{    display: none;   }   .show{    display: inline-table !important;   }   small{    color: #898FFF;   }  </style> </head> <body> <div class="panel panel-default">  <div>网站URL参数存在XSS漏洞结果</div>  <table>   <thead>    <tr>     <th>序列</th>     <th>网站域名</th>     <th>存在漏洞参数</th>     <th>完整的原URL</th>    </tr>   </thead>   <tbody>    <script>     $.ajax({      url: '/logs/getXSS.log',      type: 'get',      dataType: 'text',     })     .done(function(data) {      var host = "";      var xss = "";      var url = "";      var htmlText = "";      data = data.split("/n");      if(data[data.length-1] == ""){       data.pop();      }      for(var i = 0;i < data.length;i++){       data[i] = data[i].substring(15);       data[i] = data[i].substring(0,data[i].length-11);      }      for(i = 0;i<data.length;i++){       data[i] = data[i].split("&$");       host += data[i][0].split("=$")[1] + " ";       xss += data[i][1].split("=$")[1] + " ";       url += data[i][2].split("=$")[1] + " ";      }      host = host.split(" ");      host.pop();      xss = xss.split(" ");      xss.pop();      url = url.split(" ");      url.pop();      for(i = 0;i < data.length;i++){       htmlText += "<tr><td>"+ (i+1) +"</td><td>" + host[i] + "</td><td>" + xss[i] + "</td><td>" + url[i] + "</td></tr>";      }      $($("tbody")[0]).append(htmlText);     })    </script>   </tbody>  </table> </div> <div class="panel panel-default">  <div>网站Form表单存在XSS漏洞结果 <small>“存在漏洞参数”以 | 为分隔符,1代表第一个input,2代表第二个input。input的位置在action地址的Form表单里</small></div>  <table>   <thead>    <tr>     <th>序列</th>     <th>网站URL</th>     <th>存在漏洞参数</th>     <th>Form表单的action地址</th>    </tr>   </thead>   <tbody>    <script>     $.ajax({      url: '/logs/formXSS.log',      type: 'get',      dataType: 'text',     })     .done(function(data) {      var host = "";      var xss = "";      var url = "";      var htmlText = "";      data = data.split("/n");      if(data[data.length-1] == ""){       data.pop();      }      for(var i = 0;i < data.length;i++){       data[i] = data[i].substring(15);       data[i] = data[i].substring(0,data[i].length-11);      }      for(i = 0;i<data.length;i++){       data[i] = data[i].split("&$");       host += data[i][0].split("=$")[1] + " ";       xss += data[i][1].split("=$")[1] + " ";       url += data[i][2].split("=$")[1] + " ";      }      host = host.split(" ");      host.pop();      xss = xss.split(" ");      xss.pop();      url = url.split(" ");      url.pop();      for(i = 0;i < data.length;i++){       htmlText += "<tr><td>"+ (i+1) +"</td><td>" + host[i] + "</td><td>" + xss[i] + "</td><td>" + url[i] + "</td></tr>";      }      $($("tbody")[1]).append(htmlText);     })    </script>   </tbody>  </table> </div> <script>  $(".panel-heading").next("table").addClass('hide');  $(".panel-heading").click(function() {   if($(this).next("table").attr("class") == "table hide"){    $(this).next("table").removeClass('hide');    $(this).next("table").addClass('show');   }else{    $(this).next("table").removeClass('show');    $(this).next("table").addClass('hide');   }  }); </script> </body> </html> 

现在的页面就是这样:

打造一个自动检测页面是否存在XSS的插件Ⅲ

打造一个自动检测页面是否存在XSS的插件Ⅲ

结语

之前有朋友和我说360也推出了XSS自动检测插件,我也下载安装看了。不能说谁的更好,只能说各有千秋,360的XSS检测插件主要是针对URL的参数进行fuuzing,灵活性比我写的好多了。但是反馈需要提升。同一个参数存在XSS的话,会出现好几个alert弹窗。360的更加趋向于灵活性。我这个更加的趋向于自动化操作。不过360倒是提醒了我平台的重要性,下面是chrome、Maxthon插件下载地址:

合计: http://pan.baidu.com/s/1c0375vU

Chrome:上传插件还要交费,所以我就没上传到chroem应用平台

Maxthon: http://extension.maxthon.cn/detail/index.php?view_id=2899

最后说下,后面会在这个插件的基础上添加“路径扫描”“任意文件读取”等功能。

*本文来自FreeBuf特约作者Black-Hole投稿,属FreeBuf黑客与极客(Freebuf.COM)独家发布,未经允许禁止转载。

正文到此结束
Loading...