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

2015-08-12 780148人围观 ,发现 22 个不明物体 工具极客

前言:

还记得刚玩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就是我们构造好的urltype是选择发送数据时采用的方法。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的参数以|为分割(这里后面会多处一个"|"字符,后面会处理。)假设网站的bc参数存在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配置远程服务器:

因为我已经一年没有碰PHPMySql了,所以这里就是用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.confvhosts.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存在XSSURL如下:

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文件

在开头我们需要远程调用jquerybootstrap

<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、储蓄完整的原URLurl、储蓄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下面的代码是为了闭合上面的tbodytablediv等标签。

最下面的一行是为了把内容添加到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>

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

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

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

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

Icons是插件上网logo

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

Jquery.jsjquery代码,大家都知道

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

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

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

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

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

题外话

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

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

最后祝Freebuf越办越好!

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

更多精彩
相关推荐

这些评论亮了

  • lucky0001 (5级) 已经买了电脑,网费已经续到了2020年。 回复
    思路不错, 不过有的xss并不一定是会直接在页面上显示消息的 ,可以增加一些新的检测方式和类型。 如果是渗透测试中的一环, 还要考虑防火墙问题。 所以工具检测不管是代码审计还是漏洞扫描, 都不能被视为看是否存在问题的唯一途径。
    )7( 亮了
发表评论

已有 22 条评论

取消
Loading...

文章目录

    特别推荐

    推荐关注

    官方公众号

    聚焦企业安全

    填写个人信息

    姓名
    电话
    邮箱
    公司
    行业
    职位
    css.php