转载

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

前言:

还记得刚玩Web安全时,就想着要是能有一个自动挖掘XSS漏洞的软件就好了。然后我发现了Safe3、JSky、AWVS、Netsparker等等,但是误报太多,而且特别占内存。后来发现了fiddler的一个插件也可以检测当前浏览的页面是否存在XSS,但是不利于查看。于是就有了本文。

本文将从每段代码,每个步骤讲解“如何自己写一个自动检测网页是否存在XSS”的插件。

0×01 构思很重要:

我现在是在做前端,学了那么久。教会了我一件事,就是干活之前先构思好、规划好。只需要大体的说下就下,比如这个插件,我先是在本子上写了下面的计划:

(一)获取当前网站的URL

(二)获取参数,并对其格式化为二维数组,有助于后面的获取,格式如下:

[        [参数,值], [参数,值] ]

(后来因发现会使代码臃肿,就放弃了这个想法)

(三)对“值”与“唯一标识符”拼接。

(四)Ajax对当前网页的URL发送数据。

(五)返回200状态码后,进行查找“唯一标识符”。

(六)查找成功后,发送到服务器端。

构思,并不是后面代码就必须遵守。只是给出一个可行的方案,然后在这个方案上优化一下。

我现在的网页是 http://test.cn/?a=1&b=2&c=3 ,后面就根据这个URL来进行操作。

0×02先从JavaScript开始:

为了以后添加新功能,我们就把每一个功能封装成一个函数。防止代码混乱不堪。

一、这个功能是检测当前网页是否存在XSS的,我们就起一个function xssCheck(){}函数。

二、我们需要判断当前这个url是否存在参数,如果是http://freebuf.com/这样的url,没有参数就不就运行下面的代码,这里就需要一个if判断。代码如下:

if(location.search == ""){        return false; }

三、我们需要设置五个变量。如下:

var onlyString = 'woainixss';       //设置“唯一标识符” var protocol = window.location.protocol;      //获取当前URL的协议,如http:或者https: var hrefHost = window.location.host;    //获取当前URL的域名,如www.freebuf.com var parameter = location.search.substring(1).split("&");     //去掉前面的"?",再把参数以&为分隔符存为数组。 var url = protocol + "//" + hrefHost + "/?";        //拼接一个新的URL。如http://www.freebuf.com/?

下面是chrome返回的结构,方便理解:

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

四、 接下来就是比较重要的一步了,把“唯一标识符”与“参数的值”进行拼接,因为不确定参数的个数,我采用 for 循环:

for(var i = 0;i < parameter.length;i++){        url += parameter[i].split("=")[0] + "=" + onlyString + parameter[i].split("=")[1] + '"&'; }

parameter.length 的长度是为 2( 包括 0) ,从上图可以看到。这段代码有点难理解,我们直接输出,看下:

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

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

说的简单一点就是对 parameter 里的数组值以 = 为分隔符重新化为数组。 parameter[0].split("=")[0] 就是代表就是当前网页 URL 第一个参数的属性, parameter[0].split("=")[1] 就是代表就是当前网页 URL 第一个参数的值。

然后 parameter 后面的 [i] 循环三次(有多少参数,就循环多少次)。

现在的 url 变量为 http://test.cn/?a=woainixss1"&b=woainixss2"&c=woainixss3"&

至于为什么每个参数的值后面都有 " 一个双引号,是为了确保 XSS 的准确性,这个小方法,至少能保证检测 XSS 时的误报率减少到 5% 。剩下的 5% 是网站除了没有对 " 进行过滤,其他都做了过滤。当然这种情况很少很少。如果想把误报率减小到 0% 的话,可以把

var onlyString = 'woainixss';

改成:

var onlyString = 'onclick/'///&#alert()<>"';

这个唯一标识符为的结果为:

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

你以为这样就 OK 了么?不。上面的代码存在一个 bug ,那就是 url 后面会多出一个 "&" ,从而导致 ajax 请求要分割时出现一个空数组。如何解决呢?加上下面的代码就可以解决了:

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

五、 OK 之后,就到了最重要的地方。发送请求及回馈查找。这个时候就要用到 ajax 了,有些人会问 ajax 不是会遇到跨域问题么,这点无须担心,因为你使用 ajax 请求的 url 和原本的 url 都是一样的,所以无需担心跨域问题。那为什么你要写成插件呢?因为你总不能每打开一个请求都输入下代码,有插件的话,就好办多了。这里说下插件的特性:

不受跨域影响、每打开一次页面都会加载你所写的 JavaScript 代码。

先写出 ajax 的框架:

$.ajax({  url: url,  type: 'get',  dataType: 'text', }) .done(function(data) {  /* Code */ }) 

上面的 ajax 代码里, url 就是我们构造好的 url type 是选择发送数据时采用的方法。 dataType 是选择返回的数据以什么样的方式回馈,这里的 text 就是 html 代码。也就是网站的源码。

.done 是当发送数据请求成功时执行的代码。 function(data) 里的 data 就是 ajax 请求成功后的源码。

为什么不写 .fail(function(data) {….}) 呢?因为我们这个是检测 XSS 的,如果返回失败,就说明修改参数后,会导致网站出现非 200 的状态码,也就是 404 状态码或者其他的状态码。这个时候就说明参数无 XSS 。(其实这里还有一个 bug ,就是假设网站有两个参数,你修改第一个参数的时候会返回 404 状态码,修改第二个参数的时候会触发 XSS 。但是我我这个只有一个 ajax ,导致两个参数乧做了修改,这样的话及时第二个有 XSS 漏洞,也无法检测到,之所以没写,是不想把代码搞得太复杂,这个月应该会写第二版,把这个 bug 补上,再加上检测 POST XSS 漏洞检测。因为有人 JavaScript 并不是很熟,所以没有写的多么复杂,在此说声抱歉,以后会补上。)

现在就开始在 done 里写上处理代码:

我们先新建一个变量,来储存出现 XSS 漏洞的参数 var xss = ""; 为什么要在后面加上 ="" 呢?因为下面会使用 += 运算符,不加的话会返回 NaN (非数字)。

然后就是 for 循环用 indexOf 函数查找源码里是否存在“唯一标识符”。代码如下:

for(i = 0;i < parameter.length;i++){ /*indexOf函数*/ }

parameter.length 就是当前参数的数量。

然后就是在 for 里写上 indexOf 代码了。这里使用 if 来判断当前“唯一标识符”是否存在当前源码里:

if(data.indexOf(onlyString + parameter[i].split("=")[1] + '"') != "-1"){     xss += parameter[i].split("=")[0] + "|"; }

onlyString + parameter[0].split("=")[1] + '"' 是“唯一标识符”与第一个参数的值拼接的结果

data.indexOf(xxx) != "-1" 使用 indexOf 搜索字符串的时候,如果没搜索到,会返回 -1 ,这里就的意思是,当网站存在“唯一标识符”时运行 if 里的代码。

xss += parameter[i].split("=")[0] + "|"; 把出现 XSS 的参数以 | 为分割(这里后面会多处一个 "|" 字符,后面会处理。)假设网站的 b c 参数存在 XSS ,那么会返回 "b|c|"

这里存在一个 bug ,那就是如果没有 XSS 漏洞怎么办?下面的代码会解决:

if(xss == ""){     return false; }else{ /*处理代码*/ }

当没有变量不存在 XSS 漏洞,返回 false

下面就开始处理上面的 bug ,和发送代码了:

xss = xss.substring(0,xss.length-1); //img标签里的src属性,为你的服务器地址。 //如果不想加上远程地址,可以吧下面的代码修改为alert(xss) $("body").append("<img src='http://xss.cn/xss.html?host=$" + hrefHost + "&$xss=$" + xss + "&$url=" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>")

第一段是去掉xss变量后的最后一个字符串,也就是"|"。

第二段就是发送数 据到远程服务器。当然你可以修改为 alert(xss) ,但是如果网站存在 XSS ,就是弹,太烦人了。

为什么要加$呢,为了让split正确的分割。因为window.location.href里也有参数是&和=,导致split分割时,多分割几个数组。

后面要加随机数,加随机数,加随机数,重要的事情说三遍。调试的时候被这个坑惨了。 刷新页面或者打开页面时,log会无变化,因为这时候的图片已经被浏览器缓存了。

OK ,代码已经全部 OK ,下面的完整的代码:

function xssCheck(){  if(location.search == ""){   return false;  }  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 = url.substring(0,url.length-1);  $.ajax({   url: url,   type: 'get',   dataType: 'text',  })  .done(function(data) {   var xss = "";   for(i = 0;i < parameter.length;i++){    if(data.indexOf(onlyString + parameter[i].split("=")[1] + '"') != "-1"){     xss += parameter[i].split("=")[0] + "|";    }   }   if(xss == ""){    return false;   }else{    xss = xss.substring(0,xss.length-1);    //img标签里的src属性,为你的服务器地址。    //如果不想加上远程地址,可以吧下面的代码修改为alert(xss)    $("body").append("<img src='http://xss.cn/xss.html?host=$" + hrefHost + "&$xss=$" + xss + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");   }  }) } xssCheck(); 

0×03配置远程服务器:

因为我已经一年没有碰 PHP MySql 了,所以这里就是用 JavaScript 来完成服务器端的配置。这里使用 win7+phpstudy 来配置。

打开 phpstudy 配置“站点域名管理”,添加一个 xss.cn 域名。

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

记得重启 nginx

然后打开 host 文件,把 xss.cn 指向本地:

127.0.0.1 xss.cn 127.0.0.1 www.xss.cn

然后在在网站目录下,新建一个 xss.html 。里面内容可以为空。

修改 nginx.conf vhosts.conf 文件。

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

增加一个日志模块,名为 xss

然后在 vhosts.conf 文件,配置 xss 文件的动作及添加

access_log E:/WWW/xss/xss.log xss;

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

加上这段代码:

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

然后重启下 nginx 服务器。就行了,这里我们在 index.php 里面加上

<?php     echo $_GET['b']; echo $_GET['c']; ?>

来实验下:

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

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

可以看到代码被成功渲染了,这个时候你的 xss 网站根目录会生成一个 xss.log 日志文件,我们来看下:

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

可以看到 host 是出现 XSS 漏洞的域名, xss 是出现漏洞的 XSS 参数。 url 是完整的原始 URL

这个只是我们测试的网页,不来点实战有点过不去。当当网的搜索页面的 key 存在 XSS URL 如下:

http://daphne.dangdang.com/list.html?key=hello&inner_cat=all&sort_type=sort_xlowprice_asc

打开后,在控制台输入我们的代码,然后打开 xss.log 文件:

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

成功了。

0×04对日志格式化:

现在的日志不是人看的。简直侮辱了使用者的智商。

下面就是对日志进行格式化的操作。因为一年没碰 php 了,下面还是使用 JavaScript 来进行格式化。

xss.cn 的根目录下创建一个 index.html 文件

在开头我们需要远程调用 jquery bootstrap

<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">

JavaScript 读取 xss.log 日志文件,再对每条格式化,显现:

使用 JavaScript 读取本地文件我还真没有做过,网上百度都是再说使用 new ActiveXObject 来读取,但是 ActiveXObject 的局限性很大,总不能在 IE 下打开反馈页面。这里介绍一个我自己想到的一个小技巧,使用 ajax 。之前生成 xss.log 文件的时候,我们就把 xss.log 放到了 xss.cn 的根目录下,这样一样就可以使用 ajax 来获取了(只读)。

先创建一个 ajax 模块:

$.ajax({     url: '/xss.log',     type: 'get',     dataType: 'text', }) .done(function(data){/* 对xss.log的操作 */})

这里的 data 就是 xss.log 的本文。

先创建四个变量,分别是储存域名的 host 、和储存变量的 xss 、储蓄完整的原 URL url 、储蓄 html 代码的 htmlText

var host = ""; var xss = ""; var url = ""; var htmlText = '<div class="panel panel-default"><div>网站存在XSS漏洞结果</div><table><thead><tr><th>序列</th><th>网站域名</th><th>存在漏洞参数</th><th>完整的原URL</th></tr></thead><tbody>';

现在需要对日志进行初步的排版,先让我们看下 ajax 取到 xss.log 文件的内容:

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

现在我们要求吧本文转成数组,那么下面一段代码就行:

data = data.split("/n");

现在再来看看:

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

因为 nginx 生成日志的时候对对日志重开一个头,也就是 xss.log 文件的结尾会多出一个空格,防止下次写数据时,写到一行。这里我们就需要对最后一个空数组删除:

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

之所以不能直接用 data.pop(); ,是因为不确定后面是否有回车,因为人工有的时候会不小心删除。

去掉干扰符。因为日志里有着很多不需要的字符。比如 GET / HTTP/1.1 ,接下来就是把他们删除,因为他们的位置是固定的,我们就不需要用正则:

for(var i = 0;i < data.length;i++){        data[i] = data[i].substring(15); //删除GET /字符        data[i] = data[i].substring(0,data[i].length-11); //删除HTTP/1.1字符 }

data.length 是当前 xss.log 文章的行数。也就是数组里的个数。也是出线 XSS 漏洞的网站的个数。

里面的两个处理代码不能写到一起,会照成错误,具体原因我也不太清楚。

提取信息,一开始的时候我一直想着是横向取数据,然后一直出错。想了很久,换了纵向取数据就好了,但是代码还是有点难看。大伙别介意:

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] + " "; }

先是对每一行的数据以 &$ 进行分割,分割的结果如下:

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

然后就是对里面的数据以 =$ 进行二次分割,分割代码如下:

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

前面的数据不要管,就管最后三行(最后两行是一起的,算作一行),也就是说当前的:

host="test.cn test.cn daphne.dangdang.com test.cn "; xss="b|c b key b|c "; Url="http://test.cn/?a=1&b=2&c=3 http://test.cn/?a=1&b=2&c=3 http://daphne.dangdang.com/list.html?key=hello&inner_cat=all&sort_type=sort_xlowprice_asc http://test.cn/?a=1&b=2&c=3 ";

因为我们在处理时,再后面都加了空格,现在就是把他们分割成数组。这里还有一个需要注意,就是最后面也都有一个空格,转化成数组后,最后面会多出一个空数组,我们只需要把它删除即可:

host = host.split(" "); host.pop(); xss = xss.split(" "); xss.pop(); url = url.split(" "); url.pop();

这个时候我们再输出看下:

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

接下来就是见证奇迹的时刻。现在我们要把提取出来的数据显示到页面里:

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>"; } htmlText += "</tbody></table></div></div>"; $("body").append(htmlText);

第一个 for 呢,是按照当前有多少存在 XSS 漏洞网站的个数循环的。 (i+1) 是序号,让它从 1 开始。 host[i] xss[i] url[i] 是当前数据的域名、参数、原 URL

for 下面的代码是为了闭合上面的 tbody table div 等标签。

最下面的一行是为了把内容添加到 body 里。完整代码如下:

<!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"> </head> <body> <script>  $.ajax({   url: '/xss.log',   type: 'get',   dataType: 'text',  })  .done(function(data) {   var host = "";   var xss = "";   var url = "";   var htmlText = '<div class="panel panel-default"><div>网站存在XSS漏洞结果</div><table><thead><tr><th>序列</th><th>网站域名</th><th>存在漏洞参数</th><th>完整的原URL</th></tr></thead><tbody>';   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>";   }   htmlText += "</tbody></table></div></div>";   $("body").append(htmlText);  }) </script> </body> </html> 

现在让我们看看网站是什么样子吧:

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

0×05 巴拉巴拉小魔仙,变变变:

接下来就是让它成为一个插件,安装之后,我们只需要偶尔打开下 xss.cn 网站,就可以看到哪些网站存在 XSS 漏洞了。不用每打开一个网站就输入一次。

这个 Maxthon 插件需要 4 个文件, 1 个目录。结构如下:

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

Icons 是插件上网 logo

Base.js 是检测代码, 也就是0×02节所说的JavaScript代码,直接复制过去就行了(注意配置服务器,如不想配置,把利用img发包那段代码修改为alert(xss))

Jquery.js jquery 代码,大家都知道

Def.json 是插件的配置代码,代码如下:

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

然后使用 maxthon 官网提供的 MxPacker 软件进行压缩,压缩后打开就可以使用。

MxPacker http://bbs.maxthon.cn/forum.php?mod=viewthread&tid=611580

插件下载地址(没有发送到远程地址,我吧发送数据包的代码修改为了 alert(xss) ): http://pan.baidu.com/s/1jG4EUJ8

因为鄙人只学了 maxthon 的插件开发, chrome 、火狐等浏览器的插件开发,本人不会。如果有会的,可以参考下我的代码。写出来一个,当然 共享了更好。

题外话

这篇文章发布的日期正好是 8 12 号,去年的 8 12 号,正好是我第一次在 freebuf 发文章,那篇文章是 XSS 的原理分析与解剖: http://www.freebuf.com/articles/web/40520.html

一年过去了,我从 xss 开始,从 xss 结束。从最基础的 xss 开始分享经验,到现在的全自动化检测 XSS 并前台显示,大家和我都成长了太多太多,回过头看,曾近的激情和梦想造就了今天的你我,挺美好的一件事。

最后祝 Freebuf 越办越好!

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

正文到此结束
Loading...