jsCoder

-----web前端攻城狮进阶中......

   :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言:

还记得刚玩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返回的结构,方便理解:

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

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

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

说的简单一点就是对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()<>"';

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

你以为这样就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域名。

记得重启nginx。

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

127.0.0.1 xss.cn
127.0.0.1 www.xss.cn

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

修改nginx.conf和vhosts.conf文件。

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

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

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

加上这段代码:

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

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

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

来实验下:

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

可以看到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文件:

成功了。

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文件的内容:

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

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

现在再来看看:

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

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

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

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

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();

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

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

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;< data.length;i++){
                     data[i] = data[i].substring(15);
                     data[i] = data[i].substring(0,data[i].length-11);
              }
              for(= 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(= 0;< 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>

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

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

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

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

Icons是插件上网logo

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

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

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

然后使用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越办越好!

posted on 2015-07-02 16:14  jsCoder_洋洋  阅读(1148)  评论(0编辑  收藏  举报