开启邀请码注册中,禁止灌水!发现一次永久禁言

墨客安全网

 找回密码
 立即注册

墨客安全网-新手入门指南 常见问题及帮助 | 做任务赚墨币币 新人报道 | 悬赏问答| 墨币充值| 帖子举报

墨客安全网-论坛精华合集 墨客安全网 -精华合集 之 速成之路 原创精品 | 加入墨客Vip | Vip工具| Vip教程

墨客安全网-进阶技术学习区 软件/工具| 社工专区 | 入侵检测| 技术文章 动画教程 | 编程交流| 免杀更新 | 程序源码

[悬赏公告] - 严查灌水,打造一个无水论坛,从即日起.请大家互相监督,发现恶意灌水的,请发贴举报,核实后会给予5-10的墨币奖励。
[官方公告] 从今日起所有会员发布工具必须到审核板块进行审核,如有违反永久禁言处理!
 [招聘招聘]-招聘各方面给力版主,要求每日发帖不少于3贴,每天在线时间6个小时以上,具体福利和待遇联系TG客服或者在线管理员
[官方公告]1.发现网盘下载地址失效!可以发贴举报 审核证实后给予奖励10-20墨币。2.即日起!不管是谁发贴!都不能带QQ群 已及个人QQ。个人网站,发现后严格处理。[官方业务]-加入墨客安全网Vip,圆你日抓千鸡梦,各种精品教程,免杀远控,压力测试等你拿,期待各位会员的加入,即可享受众多福利!
【官方公告】论坛所有广告均为商业行为,需要交易的请尽量走担保程序,所有因广告产生的任何纠纷请私下解决【站外广告】大量收色刷!刷单肉鸡!带飞机肉鸡!寻内网横向技术!懂的来,小白勿扰!长期包养色刷,刷单,带飞机海外盘国内盘灰产肉鸡稳定鸡商
联系飞机:@seeok91
【官方业务】精品广告位招租,需要请联系官方TG客服【官方业务】精品广告位招租,需要请联系官方TG客服【官方业务】精品广告位招租,需要请联系官方TG客服
查看: 554|回复: 0

[其它知识] 打造一个自动检测页面是否存在XSS的插件(完结篇)

[复制链接]
  • TA的每日心情
    烦躁
    2018-3-18 00:30
  • 签到天数: 5 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    31

    主题

    242

    回帖

    1187

    积分

    UID
    1325
    威望
    271 (点)
    主题
    31 (帖)
    精华
    0 (帖)
    贡献
    61  (次)
    墨币
    311  (枚)
    活跃
    288  (点)
    担保币
    0  (枚)
    注册时间
    2015-10-26
    最后登录
    2018-3-18

    优秀版主

    QQ
    发表于 2015-11-3 22:53:01 | 显示全部楼层 |阅读模式
    0×01: 当form存在多个img时,会获取第一个,无法遍历下面的img

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

    我们来看下代码:

    A1.png

    问题就出现这一行formImg = $(index).find("img").attr("src");
    因为在jquery里,attr会自动获取第一个DOM的src属性,即使前面是多个DOM。

    在这里我重构了tureForm变量。

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

    A2.png

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

    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,让我们看下现在的返回值吧。

    A3.png



    A4.png

    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.indexOf("?") != "-1"){
        imgArray = imgArray.slice(0,imgArray.indexOf("?"));
    }
    formImg = formImg.substr(formImg.lastIndexOf("."),formImg.length);
    这段代码和之前的代码除了变量名不一样,其他地方都是一样的。把formImg变量改为imgArray就行了。

    接下来就是判断后缀了。

    if((imgArray != ".png")&&(imgArray != ".jpg")&&(imgArray != ".jpeg")&&(imgArray != ".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.indexOf("?") != "-1"){
                    imgArray = imgArray.slice(0,imgArray.indexOf("?"));
                }
                imgArray = imgArray.substr(imgArray.lastIndexOf("."),imgArray.length);
                if((imgArray != ".png")&&(imgArray != ".jpg")&&(imgArray != ".jpeg")&&(imgArray != ".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");
    我们来看下。实际的输出:

    AA.png

    我们可以看到返回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...
    }
    我们先来看看现在的输出:

    A5.png

    A6.png

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

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

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

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

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

    return (inputName.getAttribute("name"));
    效果是一样的。完整的代码如下:

    tureForm = $(tureForm).filter(function(item,index){
        var inputName = $(index).find(":input:not(:submit)");
        for(i = 0;i < inputName.length;i++){
                return (inputName.getAttribute("name"));
        }
    })
    0×03:添加白名单

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

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

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

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

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

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

    A7.png

    先新建一个变量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.indexOf(host) != "-1"){
        return false;
    }
    这里为什么要使用indexOf,而不使用if(urlWhiteList == host)呢。

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

    if(urlWhiteList == host)替换掉if(urlWhiteList.indexOf(host) != "-1"),就行了。完整代码如下:
    var urlWhiteList = ['baidu.com','360.cn','google.com','xss.cn'];
    for(var i = 0;i < urlWhiteList.length;i++){
        if(urlWhiteList.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).find("input").eq(xss - 1).css("border"," 3px solid red");
    但是还是不怎么直观,于是我就添加了一段代码,使value值改变。代码如下:

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

    下面是实际页面的反馈:

    A9.png

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

    其他问题与解答:

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

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

    <img src="testtesttest" alt="" />
    在0×01节的重构代码中,有这样一段:代码

    imgArray = imgArray.substr(imgArray.lastIndexOf("."),imgArray.length);
    如果src等于testtesttest,那imgArray就等于testtesttest字符串了。我们现在再来看下,进一步的代码:

    "testtesttest".substr("testtesttest".lastIndexOf("."),"testtesttest".length);
    反馈如下:

    AB.png

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

    if((imgArray != ".png")&&(imgArray != ".jpg")&&(imgArray != ".jpeg")&&(imgArray != ".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.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;
                parameter = parameter.split("=")[0] + "=" + parameter.split("=")[1] + onlyString;
                $.ajax({
                    url: url + parameter.join("&"),
                    type: 'get',
                    dataType: 'text',
                    async:false,
                })
                .done(function(data) {
                    if(data.indexOf(parameter.split("=")[1]) != "-1"){
                        alert("当前URL参数" +  parameter.split("=")[0] + "存在XSS漏洞");
                        // $("body").append("<img src='http://xss.cn/getXSS.html?host=$" + host + "&$xss=$" + parameter.split("=")[0] + "&$url=$" + window.location.href + "&$rand=$" + Date.parse(new Date()) + "' style='display:none;'>");
                    }
                })
                parameter = 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 += onlyString;
                url = protocol + "//" + host + "/" + urlPath.join("/") + "/" + fileURL;
                $.ajax({
                    url: url,
                    type: 'post',
                    dataType: 'text',
                    async:false,
                })
                .done(function(data){
                    if(data.indexOf(urlPath) != "-1"){
                        xss += urlPath.substring(0,urlPath.length-11) + "|";
                    }
                })
                urlPath = urlPath.substring(0,urlPath.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.indexOf("?") != "-1"){
                            imgArray = imgArray.slice(0,imgArray.indexOf("?"));
                        }
                        imgArray = imgArray.substr(imgArray.lastIndexOf("."),imgArray.length);
                        if((imgArray != ".png")&&(imgArray != ".jpg")&&(imgArray != ".jpeg")&&(imgArray != ".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.getAttribute("name"));
                }
            })
            if(tureForm.length <= 0){
                return false;
            }
            for(i = 0;i < tureForm.length;i++){
                actionUrl = $(tureForm).attr("action");
                methodType = $(tureForm).attr("method");
                if(actionUrl == undefined || actionUrl == "#" || actionUrl == ""){
                    actionUrl = href;
                }
                if(methodType == undefined || methodType == "#"){
                    methodType = "get";
                }
                tureInput = $(tureForm).find("input:not(:submit)").length
                for(j = 0;j < tureInput;j++){
                    sendData += $(tureForm).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).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

    结语:
    新人必看帖,如何快速赚取墨币,了解墨客安全网论坛版规,等等...( 点我查看

    如果你在论坛悬赏问答求助问题,并且已经从坛友或者管理的回复中解决了问题,请在帖子内点击(已解决)

    发帖求助前要善用 论坛搜索 功能,如果搜不到可以试试,论坛顶上的 百度站内搜索 - 纵横站内搜索 那里可能会有你要找的答案;

    如果发现论坛有灌水帖、下载地址失效帖、后门帖、广告帖、工具不能正常使用、都可以去 举报版块 发帖举报,核实给予退回墨币+额外的墨币奖励哦;
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    1、请认真发帖,禁止回复纯表情,纯数字等无意义的内容!帖子内容不要太简单!
    2、提倡文明上网,净化网络环境!抵制低俗不良违法有害信息。
    3、每个贴内连续回复请勿多余3贴,每个版面回复请勿多余10贴!
    4、如果你对主帖作者的帖子不屑一顾的话,请勿回帖。谢谢合作!

    关闭

    站长推荐上一条 /1 下一条