转载

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

前言:

这一版的变化比 上一版 有点大,基本等于重写了。因为整个代码的构架被我修改了,更加的直观,及方便修改和定位。这一版我修复了上一版两处BUG(后文会说),添加了伪静态检测XSS(URL处的路径及文件)。

0×01 网站结构:

我把之前写的xss.html修改为getXSS.html。增加了formXSS.html来接受form表单的xss(下一章会专门来说这个功能)。把日志放在了logs文件里。如下:

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

修改 log 日志文件的话, nginx 的配置文件也要改的。下面是 nginx 配置后的 nginx 代码:

location /getXSS.html{     access_log E:/WWW/xss/logs/getXSS.log xss; } location /formXSS.html{     access_log E:/WWW/xss/logs/formXSS.log xss; }

下面的 ajax 代码我就使用 logs/xxx.html 为接受地址了。

0×02 代码的构架:

因为在写的时候,想把这个插件不止光做成“网站XSS漏洞检测”,以后再添加一些其他的漏洞检测脚本。而且添加了判断,使得在应该的时候再运行脚本,比如当前的网页URL没有参数,那就不运行URL参数XSS漏洞检测脚本,也可以大大的提升脚本的运行效率。于是我把几个常用的变量写在外面,函数里面就可以直接调用,不用再声明( JavaScript 变量声明提升 ),然后在声明的下面使用 if 判断使用哪些脚本。再把每一个功能都封装成一个函数。

构架如下:

变量声明 变量操作(在if前面,防止if直接跳到函数里,而没有运行) if判断(跳到哪个函数) 功能函数

变量声明

var onlyString = 'woainixss<>'; //唯一标识符 var protocol = window.location.protocol;  //网站使用的网络协议(http、https等) var host = window.location.host;   //网站的主域名(*.com、*.cn等,例:test.cn) var href = window.location.href;   //网站的完整URL(协议+域名+参数+锚) var hostPath; //用来存放网站除去参数的字符串(协议+域名+锚) var urlPath;  //用来存放网站URL路径的数组(例:test.cn/test/xss/123  urlPath = ['test','xss’,'123'])

变量操作:

if(href.indexOf("?") != "-1"){  //如果url存在?字符串(存在?基本就存在参数了)     hostPath = href.slice(0,href.indexOf("?"));      //去除参数,只留下“协议+域名+锚” }else{     hostPath = href;  //不存在?则把完整的url赋值给hostPath。 } urlPath = hostPath.split("/").splice(3);      //以“/”为分隔符,把路径分割成数组。

因为协议后面会有两个 // ,而 .com 等域名后面还会有一个 / ,这些都是要抛弃的。只留下路径。反馈如下:

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

因为多余的“/”存在,会多出3个数组,使用splice(3)去除就行了。

If判断:

if(location.search != ""){  //当参数不为空时,跳转到parameter_Xss函数里  parameter_Xss(); } if(href.split("/")[3] != ""){   //当完整的URL里第三个/后存在字符串,则跳转到pseudoStatic_Xss函数里  pseudoStatic_Xss(); } if($("form").length > 0){    //当页面存在form表单,就跳转到form_Xss函数里  form_Xss(); } 

功能函数:

function parameter_Xss(){...}      //URL参数检测XSS function pseudoStatic_Xss(){...}       //伪静态检测XSS function form_Xss(){...}     //form表单检测XSS

0×03:URL参数检测XSS的BUG修改:

第一处BUG是我在测试的时候发现的: 也是我的疏忽。我在调试代码的时候,没有把各个可能出现的环境考虑到。下面是问题代码:

var onlyString = 'woainixss'; var protocol = window.location.protocol; var hrefHost = window.location.host; var parameter = location.search.substring(1).split("&"); var url = protocol + "//" + hrefHost + "/?"; for(var i = 0;i < parameter.length;i++){     url += parameter[i].split("=")[0] + "=" + onlyString + parameter[i].split("=")[1] + '"&'; }

这段代码的问题出现哪里呢?就是在给url变量赋值的时候出现的。我没有考虑到路径的问题。就直接把域名加上参数加上唯一标识符。比如测试的网页是test.cn/test/xss/123?a=1&b=2,但是此时的url就为test.cn/?a=1&b=2。直接把路径给忽略了。这就导致待检测的网页无法正确的被正确的处理。

下面是我修改后的代码:

var url = protocol + "//" + host + "/" + urlPath.join("/") + "?";

至于怕没有路径而导致的报错,大可不必担心。如果不存在路径,urlPath.join("/")则会返回空字符串。

第二处是我第一节时提到的BUG: 当网站存在两个参数时,代码会自动把每一个参数的值都加上“唯一标识符符”,然后再交给ajax发送数据。如果这时第一个参数存在XSS,但是第二个参数不能变,否则就无法触发XSS。这个问题在第一版的时候是无法解决的,在这一版就完美的解决了。下面是我重写的代码:

var i; //for循环里的i var parameter = location.search.substring(1).split("&"); //把URL字符以&分割成字符串 var url = protocol + "//" + host + "/" + urlPath.join("/") + "?";  //拼接成新的URL字符。(例:http://test.cn/test/xss/123?) for(i = 0;i < parameter.length;i++){   //for循环,有多少个参数就循环多少次  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"){    $("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; } 

下面就来说说for语句里的代码的意思。

var parameterData = parameter[i];

parameter[i]代表的是当前参数的字符,如图:

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

因为下面的操作代码会使当前的parameter[i]值改变,而我们需要再最后再把他改回来,那就需要赋值了,最后的时候我们再输入

parameter[i] = parameterData;

反过来就可以搞定了。

parameter[i] = parameter[i].split("=")[0] + "=" + parameter[i].split("=")[1] + onlyString;

把当前的参数重新定义, parameter[i].split("=")[0] 的是获取当的参数的字符串,如下:

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

parameter[i].split("=")[1]是获取当前参数的值,如下:

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

onlyString就是“唯一标识符”了,除去ajax,剩下的结果就是下面这样:

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

拼接URL参数的代码,我放到ajax里了,也就是url + parameter.join("&"),结果如下:

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

先是第一个参数与“唯一标识符”拼接,然后恢复原样。再把第二个参数与“唯一标识符”拼接。依次下去。

Ajax代码相比上次修改了url:url + parameter.join("&")。然后再加上async:false,让ajax为同步,如果为异步的话,只会取for循环最后一次结果,而改为同步的话,才会正常的循环ajax。其他的就没怎么变。

0×04 伪静态检测XSS:

一开始的时候,我想了很久“如何判断页面是否为伪静态”,网上也有说,是利用document.lastModifie来查看到网页的最后更新时间,如果网页采用的是静态,则日期时间和我们电脑的系统时间不一样。如果网页采用的是伪静态,则日期时间和我们电脑的系统时间完全一致。 但是需要发送两次ajax,来判断时间是否不同,我感觉太浪费效率了。后来我想了下,伪静态的规则一般都是把参数设置为路径,如下:

Test.cn/test.php?id=xss/123

修改后:

Test.cn/test/xss/123。

下面是nginx配置的规则代码:

location /test {     rewrite ^(/.+)/ /test.php?id=$1 last; }

那我只需要检测当前网页的路径及文件就行了。所以在if判断的时候,我写的是if(href.split("/")[3] != ""){…}就是来判断当前网页是否存在路径。

var fileURL;  //存放文件名字符串的变量 var fileUrlXss;   //把文件的与“唯一标识符”拼接后的字符 var url;   //ajax发送的url var xss = "";     //存在xss的参数

我们需要先判断当前url是否存在文件,也就是xxx.html、xxx.php等。如果存在就把xxx与“唯一标识符”拼接,再发送。也就是原本是xxx.html文件,拼接后就成为xxxwoiainixss<>.html了,有人可能会问这有什么用,在伪静态看来,xxx.html只是一个参数,就像freebuf的某一个页面:http://www.freebuf.com/tools/74654.html tools、74654其实就是一个参数而已。并非真的存在tools这个目录和74654.html这个文件。

下面就开始说存在文件的情况下如何修改和发现是否存下XSS。

我们先判断是否存在文件,使用

if(urlPath[urlPath.length-1].indexOf(".") != "-1")

就行了。

urlPath是以/为分隔符而切割的数据,urlPath.length-1则是最后一个数组。indexOf(".")则是判断最后一个数组是否存在“.”字符串,因为如果是文件的话,肯定有“.”的。输出的结果如下:

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

然后当if为true时,也就是存在文件时,我们就要进行拼接发送数据了。

fileURL = urlPath.pop();    //删除并获取最后一个数组,并赋值给fileURL变量

(其实也就是文件的字符串,例:xxx.html、xxx.php)。

下面就是拼凑字符串了:

fileUrlXss = fileURL.split(".")[0] + onlyString + "." + fileURL.split(".")[1]

fileURL.split(".")[0]是获取文件名(除去后缀)

onlyString 则是“唯一字符串”

fileURL.split(".")[1]是获取文件的后缀字符串。

拼凑的结果就是:

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

然后交给ajax来发送数据就行了。

发送的url为: protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileUrlXss

协议+域名+路径+文件。完整的代码如下:

$.ajax({  url: protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileUrlXss,  type: 'get',  dataType: 'text',  async:false, }) .done(function(data) {  if(data.indexOf(fileUrlXss) != "-1"){   xss += fileURL + "|";  } }) 

.done(function(data){…})是当ajax发送成功,目标网站返回200状态码时,执行的代码。Data就是发送数据后的网页HTML源码。然后配上if来判断发送数据后的网页源码里是否存在文件名与“唯一字符串”拼接后的结果。如果存在则把文件名字符串加到xss结尾。结果如下:

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

只所以使用+=是为了让后面的路径也添加到xss变量里。

下面就说说else里的代码。

fileURL = ""; if(urlPath[urlPath.length-1] == ""){     urlPath.pop(); }

先把文件的变量给清空,后面字符串拼接的话,也只会与空字符相拼接,并不会造成什么影响。而if里的代码是去掉“/”字符串的。

因为在之前的“变量操作”里,有这样一段代码:

urlPath = hostPath.split("/").splice(3);

把URL以“/”分割成数组,但是这里存在一个BUG,在伪静态里或者正常的链接,经常会有这样的URL:test.cn/test/xss/123/,没有文件,只有路径。导致的结果就是多出一个空数组,如下:

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

我们需要把最后一个空数组给去掉,但是有的URL是test.cn/test/xss/123这样的,而这样的URL是不存在BUG的,如下:

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

所以我们需要一个if判断,判断最后一个数组是否为空。如果为空,则剔除最后一个数据,也就是使用pop函数,完整代码为:

urlPath.pop();

OK,文件XSS检测依据OK了,下面就是对URL路径进行检测了。

for(var i = 0;i < urlPath.length;i++){...}

因为这时的urlPath存储的是当前网页URL路径的数组,他的长度就决定了有多少个路径。这里的意思就是循环的次数与路径多少 是一样的。

然后让当前的数组值与“唯一字符串”拼接。

urlPath[i] += onlyString;

拼接后,再与其他变量、字符串拼接:

url = protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileURL;

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

至于无法复原的情况,加上下面的代码就可以解决了:

urlPath[i] = urlPath[i].substring(0,urlPath[i].length-11);

因为woainixss<>字符是11位,而且在原本字符串的后面,那我们只需要去掉字符串最后11位就可以恢复了。如图:

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

OK,下面就是在他们之间发送ajax请求了。

因为是在for循环里,所以我们要使用异步执行。也就是async:false。

然后使用if判断是否存在XSS漏洞:

if(data.indexOf(urlPath[i]) != "-1"){     xss += urlPath[i].substring(0,urlPath[i].length-11) + "|"; }

上面说了,urlPath[i].substring(0,urlPath[i].length-11)是去掉“唯一标识符”后的字符串,把存在的漏洞加到xss变量的结尾,这个时候,最后面的字符一定是|,下面将会把它去掉。

For循环好后,我们在for循环的外面,使用if来判断是否存在XSS。

if(xss == ""){     return false; }else{ .... }

当xss为空(不存在XSS漏洞)时,则返回false,来跳出function。else里的代码则是存在XSS时,操作的代码。我们先把最后一个字符串|去掉。

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

去掉之后,就是发送字符串到我们的服务端了。

$("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;'>");

这段代码和之前的“URL参数检测XSS”代码是一样的。无需修改,当然了,如果你不想发送,可以使用alert来把结果弹出来。alert(xss)。

完整的代码如下:

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 = "";   console.log(urlPath)   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);   $("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;'>");  } } 

结尾:

form表单XSS查询,我打算放到下一章来说,因为这个功能代码比上两个功能的代码总和还多,逻辑也有点复杂,用到了JavaScript里的“闭包”和“filter函数”,再加上for循环里再套一个for循环,还需要重写xss.cn(xss结果反馈网页)。我个人觉得这个功能值得重新开一章节来说。如果放到这章的话,字数肯定会过6000字,我想读者也没那么多的时间来读。更别谈理解学习了。

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

正文到此结束
Loading...