打造一个自动检测页面是否存在XSS的插件(完结篇)

摘要

原创作者:FreeBuf特约作者Black-Hole

本章主要是为上三章填坑的(结尾附链接),因为在这期间根据多个用户的意见,发现了不少的bug,因为最近在着手写另一个项目(年底见),没多少时间,但是总要为用户着想。就抽空写了这一章。当然如果光是填坑就没什么意思了。我还会增加几个小功能。想知道是什么?那就看下去吧。

0×01: 当form存在多个img时,会获取第一个,无法遍历下面的img

这个是“form表单检测XSS”功能的bug。是一个基友“温瞳”提出来的。我看了下代码,确实出现了这个问题。因为在第三章搭建测试环境那一小节的时候,我所测试的环境只存在一个img图片,并没有考虑到form表单出现多个图片的问题,这个bug会造成什么危害呢。就是当form表单的一个img是php结尾,但是第二个img是以png结尾,这将导致本次form直接丢掉,不再进行判断筛选。如果一个是png结尾,第二个是php结尾,那程序还会继续向下执行,占用空间也占用时间。

我们来看下代码:

打造一个自动检测页面是否存在XSS的插件(完结篇)

问题就出现这一行formImg = $(index).find("img").attr("src");

因为在jquery里,attr会自动获取第一个DOM的src属性,即使前面是多个DOM。

在这里我重构了tureForm变量。

原本的代码就可以删除了,也就是下面的代码:

打造一个自动检测页面是否存在XSS的插件(完结篇)

接下来就要走向重构之路了。

104行代码没有变,还是:

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

首先我们先新建一个变量,用于储存img标签里src属性值的后缀。

var imgArray = [];

接下来就是把当前的img标签储存到imgArray里。这里使用了map函数,用来循环对象里的每一个键值。

$(index).find("img").map(function(number,imgSrc){     #code... });

还记得我在第三章说的,当every、filter、forEach、map、some这些函数的处理对象时Object对象,而不是Array数组时,传入函数里的第一个函数和第二个函数会调换么?这里的$(index).find("img")就是一个对象,所以在传个处理函数的时候,使用的是number,imgSrc。如果是数组的话,就是imgSrc,number顺序了。切记,顺序不能搞乱。

然后就是把src值压入imgArray数组:

imgArray.push($(imgSrc).attr("src"));

OK,让我们看下现在的返回值吧。

打造一个自动检测页面是否存在XSS的插件(完结篇)

打造一个自动检测页面是否存在XSS的插件(完结篇)

OK,现在已经压入数组了。接下来就是判断imgArray的长度是否大于0了,如果大于0,说明当前的form表单存在img图片。如果不大于0则不存在img图片。

if(imgArray.length > 0){     #存在img图片所运行的code... }else{     #不存在img图片所运行的code... }

现在我们先写当存在img图片时运行的代码。

先对imgArray循环:

for(i = 0;i < imgArray.length;i++){      #code... }

然后就是截取src地址,只保留后缀了

if(imgArray[i].indexOf("?") != "-1"){     imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?")); } formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);

这段代码和之前的代码除了变量名不一样,其他地方都是一样的。把formImg变量改为imgArray[i]就行了。

接下来就是判断后缀了。

if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){     return false; }else{     return ($(index).prepend().find(":input:not(:submit)").length > 0); }

当当前的form表单存在img标签,而且后缀不为png、jpg、jpeg、gif时就返回false,也就是说当前的form表单被丢弃了。如果后缀符合png、jpg、jpeg、gif其中一个。则再进行判断,当前form表单除了type=submit之外所有的input标签的长度是否大于0,

($(index).find(":input:not(:submit)").length > 0)这段代码的字符、效率都比原来好很多。原来的代码是:

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

显而易见。这次的代码比原来的代码好很多。而且比原来的代码准确率也高了很多。

因为index是当前的form表单DOM,我们使用find函数搜索当前form表单下符合选择器条件的DOM,find(":input:not(:submit)")意思就是说检索当前form表单里所有的input标签,并且除了type为submit的input标签。

OK,现在这个bug已经完全修复了。下面是完整的代码:

tureForm = $("form").filter(function(item,index){     var imgArray = [];     $(index).find("img").map(function(number,imgSrc){         imgArray.push($(imgSrc).attr("src"));     });     if(imgArray.length > 0){         for(i = 0;i < imgArray.length;i++){             if(imgArray[i].indexOf("?") != "-1"){                 imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?"));             }             imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);             if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){                 return false;             }else{                 return ($(index).find(":input:not(:submit)").length > 0);             }         }     }else{         return ($(index).find(":input:not(:submit)").length > 0);     } })

其他地方不需要再改动了。

0×02:在第一次筛选后,只能获取第一个input的name值

这个bug是我在实际操作中遇到的。接下来就来说说这个bug是多么可恶又是怎么死亡的吧。

这段代码出现在第121行(未修改版本,修改上一个bug后,行数在128行)

让我们看看这段代码长什么样子吧:

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

和上一个bug的情况是一样的。都是因为attr会自动获取第一个DOM的name属性。

让我们先来看看这段代码导致危害性吧。

如果当前页面有一个form表单是下面这样的:

<form method="post">     <input type="text">     <input type="text" name="xss">     <input type="submit"> </form>

那上面这个存在bug的代码,会直接把这个form表单丢弃,而不再做任何的操作与判断。准确性将大大降低。甚至比一个0×01节的bug还要严重的多。

为什么会这个form表单会被直接丢弃呢,我们来深入的了解下。

因为这一个form没有img,也存在除submit之外其他的input的标签。符合第一次筛选。于是这个form表单成功的进入到了第二个筛选代码处。Filter代码我就不解释了,之前有解释过。我们直接来看return代码:

$(index).find(":input").attr("name")
这里的index就是这个form表单,等价于$("form").find(":input").attr("name");

我们来看下。实际的输出:

打造一个自动检测页面是否存在XSS的插件(完结篇)

我们可以看到返回undefined,也就是没有获取到。接下来就是修改这段代码了。

tureForm = $(tureForm).filter(function(item,index){})不变。

先定义一个变量,用来储存input标签里的name属性值:

var inputName = $(index).find(":input:not(:submit)");

以上面的form标签为例,这段代码会返回:

<input type="text"> <input type="text" name="xss">

这两个input标签。然后使用for循环inputName变量。

for(i = 0;i < inputName.length;i++){     #code... }

我们先来看看现在的输出:

打造一个自动检测页面是否存在XSS的插件(完结篇)

打造一个自动检测页面是否存在XSS的插件(完结篇)

接下来就是if判断当前的input是否存在name属性值了。

if(inputName[i].getAttribute("name")){     return true; })

               

getAttribute函数是JavaScript原生的方法。和jquery里的attr 函数效果是一样的。如果没有找到name值,那么会隐性返回undefined,而undefined在if里代表的是false。

因为外部函数使用的filter,所以我们只需要返回true就可以了。

上面这个if 是为了让大家了解的。之前也说过return也可以当做if来使用。在实际上可以使用下面的代码:

return (inputName[i].getAttribute("name"));

效果是一样的。完整的代码如下:

tureForm = $(tureForm).filter(function(item,index){     var inputName = $(index).find(":input:not(:submit)");     for(i = 0;i < inputName.length;i++){             return (inputName[i].getAttribute("name"));     } })

0×03:添加白名单

这个是在FreeBuf评论区的“冰海”提出来的建议。这个功能也确实应该补上。

因为之前的代码是在window全局里。无法使用return false;(return只能在function函数里使用)来阻止代码向下执行。

而“白名单”就是需要先判断,当当前域名在“白名单”列表里,就不再向下执行。这点在window全局里是无法使用return false;实现的。那就只用使用类似:

throw new Error('xxxx');

这种直接抛出错误,来让浏览器不再向下执行的hack方法了。以为你文章是面对新手,我就不使用这种hack方法了。

先在之前写JavaScript代码外部,添加匿名函数:

(function(){      #之前写的JavaScript代码  })();

如下:

打造一个自动检测页面是否存在XSS的插件(完结篇)

先新建一个变量urlWhiteList,用于存储URL白名单地址。

var urlWhiteList = ['baidu.com','360.cn','google.com'];

默认白名单是baidu.com、360.cn、google.cn。

下面就是for循环这个数组了。

for(var i = 0;i < urlWhiteList.length;i++){     #code... }

OK了后,就是if判断当前域名是否存在白名单里:

if(urlWhiteList[i].indexOf(host) != "-1"){     return false; }

这里为什么要使用indexOf,而不使用if(urlWhiteList[i] == host)呢。

因为这样的话,可以使白名单里的域名,无论二级域名还是三级域名,都可以存在白名单,而不用每一个二级域名、三级域名都要添加到白名单里。当然如果你不想使用这个方法,可以使用我上面说的代码:

if(urlWhiteList[i] == host)替换掉if(urlWhiteList[i].indexOf(host) != "-1"),就行了。完整代码如下: var urlWhiteList = ['baidu.com','360.cn','google.com','xss.cn']; for(var i = 0;i < urlWhiteList.length;i++){     if(urlWhiteList[i].indexOf(host) != "-1"){         return false;     } }

因为maxthon(遨游浏览器)没有提供储存用户输入的api接口,所以没有办法像chrome那样直接一个html+input添加。而chrome插件开发我还没有深入了解过他的API接口。所以想要添加白名单,大伙可以把插件下载到本地。解压后,打开base.js。Ctrl+F搜索urlWhiteList关键字,在数组后面添加白名单就行了。添加之后,打包按照就OK了。

Maxthon方法,打开http://bbs.maxthon.cn/forum.php?mod=viewthread&tid=611580

下载 mx-插件系统.zip ,里面的MxPacker可以解压、打包插件。

Chrome方法,请自行百度查询。

其他小修改:

在“form_Xss()”(form表单检测XSS)功能反馈处。直接在input标签里添加value值:

之前是

alert("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞");

这个方法不咋样,很不直观。于是我在之前加上了使边框颜色变成红色:

$(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red");

但是还是不怎么直观,于是我就添加了一段代码,使value值改变。代码如下:

$(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red")                                         .val("此输入框存在XSS ");

打造一个自动检测页面是否存在XSS的插件(完结篇)

下面是实际页面的反馈:

打造一个自动检测页面是否存在XSS的插件(完结篇)

怎么样,是不是比之前更加直观了呢。

其他问题与解答:

Q:有些img的src地址是乱写的。程序能准确的判别出来么?

A:可以的,在这里我们先假设下,当前的网页form表单里存在这样一段img标签代码:

<img src="testtesttest" alt="" />

在0×01节的重构代码中,有这样一段:代码

imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);

如果src等于testtesttest,那imgArray[i]就等于testtesttest字符串了。我们现在再来看下,进一步的代码:

"testtesttest".substr("testtesttest".lastIndexOf("."),"testtesttest".length);

反馈如下:

打造一个自动检测页面是否存在XSS的插件(完结篇)

为什么会这样呢,因为lastIndexOf搜索不到字符串的时候,会返回-1,而"testtesttest".length又是testtesttest字符串的长度,subsyr是截取。导致返回testtesttest字符串的最后一个字符t。而在下面的代码里。是判断是否为png、jpg、jphe、gif:

if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){     return false; }else{     return ($(index).find(":input:not(:submit)").length > 0); }

显然不是了,那么当的form表单就直接运行else里的代码了。

完整代码:

(function(){     var onlyString = 'woainixss<>"';     var protocol = window.location.protocol;     var host = window.location.host;     var href = window.location.href;     var hostPath;     var urlPath;     var urlWhiteList = ['baidu.com','360.cn','google.com'];     for(var i = 0;i < urlWhiteList.length;i++){         if(urlWhiteList[i].indexOf(host) != "-1"){             return false;         }     }     if(href.indexOf("?") != "-1"){         hostPath = href.slice(0,href.indexOf("?"));     }else{         hostPath = href;     }     urlPath = hostPath.split("/").splice(3);     if(location.search != ""){         parameter_Xss();     }     if(href.split("/")[3] != ""){         pseudoStatic_Xss();     }     if($("form").length > 0){         form_Xss();     }     function parameter_Xss(){ //URL参数检测XSS         var i;         var parameter = location.search.substring(1).split("&");         var url = protocol + "//" + host + "/" + urlPath.join("/") + "?";         for(i = 0;i < parameter.length;i++){             var parameterData = parameter[i];             parameter[i] = parameter[i].split("=")[0] + "=" + parameter[i].split("=")[1] + onlyString;             $.ajax({                 url: url + parameter.join("&"),                 type: 'get',                 dataType: 'text',                 async:false,             })             .done(function(data) {                 if(data.indexOf(parameter[i].split("=")[1]) != "-1"){                     alert("当前URL参数" +  parameter[i].split("=")[0] + "存在XSS漏洞");                     // $("body").append("<img src='http://xss.cn/getXSS.html?host=$" + host + "&$xss=$" + parameter[i].split("=")[0] + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");                 }             })             parameter[i] = parameterData;         }     }     function pseudoStatic_Xss(){    //伪静态检测XSS         var fileURL;         var fileUrlXss;         var url;         var xss = "";         if(urlPath[urlPath.length-1].indexOf(".") != "-1"){             fileURL = urlPath.pop();             fileUrlXss = fileURL.split(".")[0] + onlyString + "." + fileURL.split(".")[1]             $.ajax({                 url: protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileUrlXss,                 type: 'get',                 dataType: 'text',                 async:false,             })             .done(function(data) {                 if(data.indexOf(fileUrlXss) != "-1"){                     xss += fileURL + "|";                 }             })         }else{             fileURL = "";             if(urlPath[urlPath.length-1] == ""){                 urlPath.pop();             }         }         for(var i = 0;i < urlPath.length;i++){             urlPath[i] += onlyString;             url = protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileURL;             $.ajax({                 url: url,                 type: 'post',                 dataType: 'text',                 async:false,             })             .done(function(data){                 if(data.indexOf(urlPath[i]) != "-1"){                     xss += urlPath[i].substring(0,urlPath[i].length-11) + "|";                 }             })             urlPath[i] = urlPath[i].substring(0,urlPath[i].length-11);         }         if(xss == ""){             return false;         }else{             xss = xss.substring(0,xss.length-1);             alert("当前伪静态路径或者文件" + xss + "存在XSS漏洞");             // $("body").append("<img src='http://xss.cn/getXSS.html?host=$" + host + "&$xss=$" + xss + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");         }     }     function form_Xss(){ //form表单检测XSS         var tureForm;         var tureInput;         var formImg;         var actionUrl;         var methodType;         var sendData = "";         var sendDataUrl;         var i;         var j;         tureForm = $("form").filter(function(item,index){             var imgArray = [];             $(index).find("img").map(function(number,imgSrc){                 imgArray.push($(imgSrc).attr("src"));             });             if(imgArray.length > 0){                 for(i = 0;i < imgArray.length;i++){                     if(imgArray[i].indexOf("?") != "-1"){                         imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?"));                     }                     imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);                     if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){                         return false;                     }else{                         return ($(index).find(":input:not(:submit)").length > 0);                     }                 }             }else{                 return ($(index).find(":input:not(:submit)").length > 0);             }         })         if(tureForm.length <= 0){             return false;         }         tureForm = $(tureForm).filter(function(item,index){             var inputName = $(index).find(":input:not(:submit)");             for(i = 0;i < inputName.length;i++){                     return (inputName[i].getAttribute("name"));             }         })         if(tureForm.length <= 0){             return false;         }         for(i = 0;i < tureForm.length;i++){             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);                     console.log("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞");                     $(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red")                                                             .val("此输入框存在XSS ");                     // $("body").append("<img src='http://xss.cn/formXSS.html?host=$" + href + "&$xss=$" + xss + "&$url=$" +actionUrl + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");                 }             })         }     } })()

Maxthon、chrome插件下载地址:http://pan.baidu.com/s/1c0375vU

Maxthon插件下载地址:http://extension.maxthon.cn/detail/index.php?view_id=2899

结语:

在修改代码的时候,我发现自己还是有很多的不足,代码不够模块化,导致修改一处,而牵动全身。本来是想添加几个功能的“ajax xss检测”“IP xss检测”“user-agent xss检测”“数据包头 XSS检测”但是时间真的不够。因为在着手写另一个项目,如果这个“自动检测XSS插件”的牛逼指数算1的话,那我现在写的项目,就是10+,敬请期待。

至于“ajax xss检测”、“IP xss检测”、“user-agent xss检测”、“数据包头 XSS检测”等功能可能要明年开始写或者不再添加。这一章就算是“打造一个自动检测页面是否存在XSS的插件”系列文章的最后一章了。

《打造一个自动检测页面是否存在XSS的插件》系列文章链接:1/2/3

结局福利:下个月我会做一个送书小活动(《解密家用路由器0day漏洞挖掘技术》、《模糊测试强制发掘安全漏洞的利器》、《爱上单片机》、《android开发实战》、《Web之困》等),最低八成新(邮费自付哦~),到时候会在FreeBuf小酒馆发起,请多多关注。

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

原创作者:FreeBuf特约作者Black-Hole

本章主要是为上三章填坑的(结尾附链接),因为在这期间根据多个用户的意见,发现了不少的bug,因为最近在着手写另一个项目(年底见),没多少时间,但是总要为用户着想。就抽空写了这一章。当然如果光是填坑就没什么意思了。我还会增加几个小功能。想知道是什么?那就看下去吧。

0×01: 当form存在多个img时,会获取第一个,无法遍历下面的img

这个是“form表单检测XSS”功能的bug。是一个基友“温瞳”提出来的。我看了下代码,确实出现了这个问题。因为在第三章搭建测试环境那一小节的时候,我所测试的环境只存在一个img图片,并没有考虑到form表单出现多个图片的问题,这个bug会造成什么危害呢。就是当form表单的一个img是php结尾,但是第二个img是以png结尾,这将导致本次form直接丢掉,不再进行判断筛选。如果一个是png结尾,第二个是php结尾,那程序还会继续向下执行,占用空间也占用时间。

我们来看下代码:

问题就出现这一行formImg = $(index).find("img").attr("src");

因为在jquery里,attr会自动获取第一个DOM的src属性,即使前面是多个DOM。

在这里我重构了tureForm变量。

原本的代码就可以删除了,也就是下面的代码:

接下来就要走向重构之路了。

104行代码没有变,还是:

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

首先我们先新建一个变量,用于储存img标签里src属性值的后缀。

var imgArray = [];

接下来就是把当前的img标签储存到imgArray里。这里使用了map函数,用来循环对象里的每一个键值。

$(index).find("img").map(function(number,imgSrc){     #code... });

还记得我在第三章说的,当every、filter、forEach、map、some这些函数的处理对象时Object对象,而不是Array数组时,传入函数里的第一个函数和第二个函数会调换么?这里的$(index).find("img")就是一个对象,所以在传个处理函数的时候,使用的是number,imgSrc。如果是数组的话,就是imgSrc,number顺序了。切记,顺序不能搞乱。

然后就是把src值压入imgArray数组:

imgArray.push($(imgSrc).attr("src"));

OK,让我们看下现在的返回值吧。

OK,现在已经压入数组了。接下来就是判断imgArray的长度是否大于0了,如果大于0,说明当前的form表单存在img图片。如果不大于0则不存在img图片。

if(imgArray.length > 0){     #存在img图片所运行的code... }else{     #不存在img图片所运行的code... }

现在我们先写当存在img图片时运行的代码。

先对imgArray循环:

for(i = 0;i < imgArray.length;i++){      #code... }

然后就是截取src地址,只保留后缀了

if(imgArray[i].indexOf("?") != "-1"){     imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?")); } formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);

这段代码和之前的代码除了变量名不一样,其他地方都是一样的。把formImg变量改为imgArray[i]就行了。

接下来就是判断后缀了。

if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){     return false; }else{     return ($(index).prepend().find(":input:not(:submit)").length > 0); }

当当前的form表单存在img标签,而且后缀不为png、jpg、jpeg、gif时就返回false,也就是说当前的form表单被丢弃了。如果后缀符合png、jpg、jpeg、gif其中一个。则再进行判断,当前form表单除了type=submit之外所有的input标签的长度是否大于0,

($(index).find(":input:not(:submit)").length > 0)这段代码的字符、效率都比原来好很多。原来的代码是:

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

显而易见。这次的代码比原来的代码好很多。而且比原来的代码准确率也高了很多。

因为index是当前的form表单DOM,我们使用find函数搜索当前form表单下符合选择器条件的DOM,find(":input:not(:submit)")意思就是说检索当前form表单里所有的input标签,并且除了type为submit的input标签。

OK,现在这个bug已经完全修复了。下面是完整的代码:

tureForm = $("form").filter(function(item,index){     var imgArray = [];     $(index).find("img").map(function(number,imgSrc){         imgArray.push($(imgSrc).attr("src"));     });     if(imgArray.length > 0){         for(i = 0;i < imgArray.length;i++){             if(imgArray[i].indexOf("?") != "-1"){                 imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?"));             }             imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);             if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){                 return false;             }else{                 return ($(index).find(":input:not(:submit)").length > 0);             }         }     }else{         return ($(index).find(":input:not(:submit)").length > 0);     } })

其他地方不需要再改动了。

0×02:在第一次筛选后,只能获取第一个input的name值

这个bug是我在实际操作中遇到的。接下来就来说说这个bug是多么可恶又是怎么死亡的吧。

这段代码出现在第121行(未修改版本,修改上一个bug后,行数在128行)

让我们看看这段代码长什么样子吧:

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

和上一个bug的情况是一样的。都是因为attr会自动获取第一个DOM的name属性。

让我们先来看看这段代码导致危害性吧。

如果当前页面有一个form表单是下面这样的:

<form method="post">     <input type="text">     <input type="text" name="xss">     <input type="submit"> </form>

那上面这个存在bug的代码,会直接把这个form表单丢弃,而不再做任何的操作与判断。准确性将大大降低。甚至比一个0×01节的bug还要严重的多。

为什么会这个form表单会被直接丢弃呢,我们来深入的了解下。

因为这一个form没有img,也存在除submit之外其他的input的标签。符合第一次筛选。于是这个form表单成功的进入到了第二个筛选代码处。Filter代码我就不解释了,之前有解释过。我们直接来看return代码:

$(index).find(":input").attr("name")
这里的index就是这个form表单,等价于$("form").find(":input").attr("name");

我们来看下。实际的输出:

我们可以看到返回undefined,也就是没有获取到。接下来就是修改这段代码了。

tureForm = $(tureForm).filter(function(item,index){})不变。

先定义一个变量,用来储存input标签里的name属性值:

var inputName = $(index).find(":input:not(:submit)");

以上面的form标签为例,这段代码会返回:

<input type="text"> <input type="text" name="xss">

这两个input标签。然后使用for循环inputName变量。

for(i = 0;i < inputName.length;i++){     #code... }

我们先来看看现在的输出:

接下来就是if判断当前的input是否存在name属性值了。

if(inputName[i].getAttribute("name")){     return true; })

               

getAttribute函数是JavaScript原生的方法。和jquery里的attr 函数效果是一样的。如果没有找到name值,那么会隐性返回undefined,而undefined在if里代表的是false。

因为外部函数使用的filter,所以我们只需要返回true就可以了。

上面这个if 是为了让大家了解的。之前也说过return也可以当做if来使用。在实际上可以使用下面的代码:

return (inputName[i].getAttribute("name"));

效果是一样的。完整的代码如下:

tureForm = $(tureForm).filter(function(item,index){     var inputName = $(index).find(":input:not(:submit)");     for(i = 0;i < inputName.length;i++){             return (inputName[i].getAttribute("name"));     } })

0×03:添加白名单

这个是在FreeBuf评论区的“冰海”提出来的建议。这个功能也确实应该补上。

因为之前的代码是在window全局里。无法使用return false;(return只能在function函数里使用)来阻止代码向下执行。

而“白名单”就是需要先判断,当当前域名在“白名单”列表里,就不再向下执行。这点在window全局里是无法使用return false;实现的。那就只用使用类似:

throw new Error('xxxx');

这种直接抛出错误,来让浏览器不再向下执行的hack方法了。以为你文章是面对新手,我就不使用这种hack方法了。

先在之前写JavaScript代码外部,添加匿名函数:

(function(){      #之前写的JavaScript代码  })();

如下:

先新建一个变量urlWhiteList,用于存储URL白名单地址。

var urlWhiteList = ['baidu.com','360.cn','google.com'];

默认白名单是baidu.com、360.cn、google.cn。

下面就是for循环这个数组了。

for(var i = 0;i < urlWhiteList.length;i++){     #code... }

OK了后,就是if判断当前域名是否存在白名单里:

if(urlWhiteList[i].indexOf(host) != "-1"){     return false; }

这里为什么要使用indexOf,而不使用if(urlWhiteList[i] == host)呢。

因为这样的话,可以使白名单里的域名,无论二级域名还是三级域名,都可以存在白名单,而不用每一个二级域名、三级域名都要添加到白名单里。当然如果你不想使用这个方法,可以使用我上面说的代码:

if(urlWhiteList[i] == host)替换掉if(urlWhiteList[i].indexOf(host) != "-1"),就行了。完整代码如下: var urlWhiteList = ['baidu.com','360.cn','google.com','xss.cn']; for(var i = 0;i < urlWhiteList.length;i++){     if(urlWhiteList[i].indexOf(host) != "-1"){         return false;     } }

因为maxthon(遨游浏览器)没有提供储存用户输入的api接口,所以没有办法像chrome那样直接一个html+input添加。而chrome插件开发我还没有深入了解过他的API接口。所以想要添加白名单,大伙可以把插件下载到本地。解压后,打开base.js。Ctrl+F搜索urlWhiteList关键字,在数组后面添加白名单就行了。添加之后,打包按照就OK了。

Maxthon方法,打开http://bbs.maxthon.cn/forum.php?mod=viewthread&tid=611580

下载 mx-插件系统.zip ,里面的MxPacker可以解压、打包插件。

Chrome方法,请自行百度查询。

其他小修改:

在“form_Xss()”(form表单检测XSS)功能反馈处。直接在input标签里添加value值:

之前是

alert("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞");

这个方法不咋样,很不直观。于是我在之前加上了使边框颜色变成红色:

$(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red");

但是还是不怎么直观,于是我就添加了一段代码,使value值改变。代码如下:

$(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red")                                         .val("此输入框存在XSS ");

下面是实际页面的反馈:

怎么样,是不是比之前更加直观了呢。

其他问题与解答:

Q:有些img的src地址是乱写的。程序能准确的判别出来么?

A:可以的,在这里我们先假设下,当前的网页form表单里存在这样一段img标签代码:

<img src="testtesttest" alt="" />

在0×01节的重构代码中,有这样一段:代码

imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);

如果src等于testtesttest,那imgArray[i]就等于testtesttest字符串了。我们现在再来看下,进一步的代码:

"testtesttest".substr("testtesttest".lastIndexOf("."),"testtesttest".length);

反馈如下:

为什么会这样呢,因为lastIndexOf搜索不到字符串的时候,会返回-1,而"testtesttest".length又是testtesttest字符串的长度,subsyr是截取。导致返回testtesttest字符串的最后一个字符t。而在下面的代码里。是判断是否为png、jpg、jphe、gif:

if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){     return false; }else{     return ($(index).find(":input:not(:submit)").length > 0); }

显然不是了,那么当的form表单就直接运行else里的代码了。

完整代码:

(function(){     var onlyString = 'woainixss<>"';     var protocol = window.location.protocol;     var host = window.location.host;     var href = window.location.href;     var hostPath;     var urlPath;     var urlWhiteList = ['baidu.com','360.cn','google.com'];     for(var i = 0;i < urlWhiteList.length;i++){         if(urlWhiteList[i].indexOf(host) != "-1"){             return false;         }     }     if(href.indexOf("?") != "-1"){         hostPath = href.slice(0,href.indexOf("?"));     }else{         hostPath = href;     }     urlPath = hostPath.split("/").splice(3);     if(location.search != ""){         parameter_Xss();     }     if(href.split("/")[3] != ""){         pseudoStatic_Xss();     }     if($("form").length > 0){         form_Xss();     }     function parameter_Xss(){ //URL参数检测XSS         var i;         var parameter = location.search.substring(1).split("&");         var url = protocol + "//" + host + "/" + urlPath.join("/") + "?";         for(i = 0;i < parameter.length;i++){             var parameterData = parameter[i];             parameter[i] = parameter[i].split("=")[0] + "=" + parameter[i].split("=")[1] + onlyString;             $.ajax({                 url: url + parameter.join("&"),                 type: 'get',                 dataType: 'text',                 async:false,             })             .done(function(data) {                 if(data.indexOf(parameter[i].split("=")[1]) != "-1"){                     alert("当前URL参数" +  parameter[i].split("=")[0] + "存在XSS漏洞");                     // $("body").append("<img src='http://xss.cn/getXSS.html?host=$" + host + "&$xss=$" + parameter[i].split("=")[0] + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");                 }             })             parameter[i] = parameterData;         }     }     function pseudoStatic_Xss(){    //伪静态检测XSS         var fileURL;         var fileUrlXss;         var url;         var xss = "";         if(urlPath[urlPath.length-1].indexOf(".") != "-1"){             fileURL = urlPath.pop();             fileUrlXss = fileURL.split(".")[0] + onlyString + "." + fileURL.split(".")[1]             $.ajax({                 url: protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileUrlXss,                 type: 'get',                 dataType: 'text',                 async:false,             })             .done(function(data) {                 if(data.indexOf(fileUrlXss) != "-1"){                     xss += fileURL + "|";                 }             })         }else{             fileURL = "";             if(urlPath[urlPath.length-1] == ""){                 urlPath.pop();             }         }         for(var i = 0;i < urlPath.length;i++){             urlPath[i] += onlyString;             url = protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileURL;             $.ajax({                 url: url,                 type: 'post',                 dataType: 'text',                 async:false,             })             .done(function(data){                 if(data.indexOf(urlPath[i]) != "-1"){                     xss += urlPath[i].substring(0,urlPath[i].length-11) + "|";                 }             })             urlPath[i] = urlPath[i].substring(0,urlPath[i].length-11);         }         if(xss == ""){             return false;         }else{             xss = xss.substring(0,xss.length-1);             alert("当前伪静态路径或者文件" + xss + "存在XSS漏洞");             // $("body").append("<img src='http://xss.cn/getXSS.html?host=$" + host + "&$xss=$" + xss + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");         }     }     function form_Xss(){ //form表单检测XSS         var tureForm;         var tureInput;         var formImg;         var actionUrl;         var methodType;         var sendData = "";         var sendDataUrl;         var i;         var j;         tureForm = $("form").filter(function(item,index){             var imgArray = [];             $(index).find("img").map(function(number,imgSrc){                 imgArray.push($(imgSrc).attr("src"));             });             if(imgArray.length > 0){                 for(i = 0;i < imgArray.length;i++){                     if(imgArray[i].indexOf("?") != "-1"){                         imgArray[i] = imgArray[i].slice(0,imgArray[i].indexOf("?"));                     }                     imgArray[i] = imgArray[i].substr(imgArray[i].lastIndexOf("."),imgArray[i].length);                     if((imgArray[i] != ".png")&&(imgArray[i] != ".jpg")&&(imgArray[i] != ".jpeg")&&(imgArray[i] != ".gif")){                         return false;                     }else{                         return ($(index).find(":input:not(:submit)").length > 0);                     }                 }             }else{                 return ($(index).find(":input:not(:submit)").length > 0);             }         })         if(tureForm.length <= 0){             return false;         }         tureForm = $(tureForm).filter(function(item,index){             var inputName = $(index).find(":input:not(:submit)");             for(i = 0;i < inputName.length;i++){                     return (inputName[i].getAttribute("name"));             }         })         if(tureForm.length <= 0){             return false;         }         for(i = 0;i < tureForm.length;i++){             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);                     console.log("当前页面action为" + actionUrl + "的form表单第" + xss + "个input存在XSS漏洞");                     $(tureForm[i]).find("input").eq(xss - 1).css("border"," 3px solid red")                                                             .val("此输入框存在XSS ");                     // $("body").append("<img src='http://xss.cn/formXSS.html?host=$" + href + "&$xss=$" + xss + "&$url=$" +actionUrl + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");                 }             })         }     } })()

Maxthon、chrome插件下载地址:http://pan.baidu.com/s/1c0375vU

Maxthon插件下载地址:http://extension.maxthon.cn/detail/index.php?view_id=2899

结语:

在修改代码的时候,我发现自己还是有很多的不足,代码不够模块化,导致修改一处,而牵动全身。本来是想添加几个功能的“ajax xss检测”“IP xss检测”“user-agent xss检测”“数据包头 XSS检测”但是时间真的不够。因为在着手写另一个项目,如果这个“自动检测XSS插件”的牛逼指数算1的话,那我现在写的项目,就是10+,敬请期待。

至于“ajax xss检测”、“IP xss检测”、“user-agent xss检测”、“数据包头 XSS检测”等功能可能要明年开始写或者不再添加。这一章就算是“打造一个自动检测页面是否存在XSS的插件”系列文章的最后一章了。

《打造一个自动检测页面是否存在XSS的插件》系列文章链接:1/2/3

结局福利:下个月我会做一个送书小活动(《解密家用路由器0day漏洞挖掘技术》、《模糊测试强制发掘安全漏洞的利器》、《爱上单片机》、《android开发实战》、《Web之困》等),最低八成新(邮费自付哦~),到时候会在FreeBuf小酒馆发起,请多多关注。

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

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: