XSS报警机制(前端防火墙:第二篇)

0x00 前言


在第一章结尾的时候我就已经说了,这一章将会更详细的介绍前端防火墙的报警机制及代码。在一章出来后,有人会问为什么不直接防御,而是不防御报警呢。很简单,因为防御的话,攻击者会定位到那一段的JavaScript代码,从而下次攻击的时候绕过代码。如果不防御而报警的话,攻击者会降低警觉,不会在看JavaScript代码(至少我是这样)。回到正题,下面说的代码,是基于thinkphp框架和bootstrap3.3.5框架。如果你的网站没有使用thinkphp3.2.3框架的话,可以参照我的思路重新写一个。这里我强调一下“前端防御XSS是建立在后端忘记做过滤,没有做过滤,疏忽做过滤的基础上的...

0x01 前端要做的事


其实标题应该改成“XSS报警机制”的,因为在这一章里使用了大量的后端代码。但是第一章的标题都出来了,也没法改了。

前端要做的事情在第一章的时候就已经说了,代码如下:

p1

现在我们就是针对第38行进行修改,改成我们后台接受的API URL。就像这样:

p2

对,就这一行。没有其他代码。在实际的线上环境中,也只需要上面5行。可以直接copy到您的线上环境中,记得把倒数第二行的url改成自己的地址就行了。难道就那么简单?不,0x05节还有一部分前端代码。0x01~0x04主要是针对于平台。

0x02 数据库要做的事


一共两个表。fecm_user和fecm_bugdata。

fecm_user的字段信息如下:

p3

  • name:管理员账户名
  • md5name:3次name值的md5
  • password:3次密码的md5
  • email:管理员邮箱
  • create_date:管理员创建时间

为了安全起见(其实就是懒)没有写添加管理员的,自行在数据库里添加

fecm_bugdata的字段信息如下:

p4

  • url:漏洞的url地址
  • category:漏洞类型
  • cookies:攻击者的cookies
  • ua:攻击者的User-Agent
  • hxff_ip:攻击者的HTTP_X_FORWARDED_FOR
  • hci_ip:攻击者的HTTP_CLIENT_IP
  • ra_ip:攻击者的REMOTE_ADDR
  • time:攻击者攻击的时间
  • fixes:漏洞是否修复(0为未修复,1为已修复)

0x03 后端要做的事


因为后端代码太多,所以我就说一些核心的后端处理代码。

在0x01节里,有个核心的代码是new Image().src = 'http://fecm.cn/Api/addVul/';

接下来我们来说说这个Api的处理方式(ThinkPHP代码)

#!php
public function addVul(){
    if(I('get.category','','int') == ""){
        $this->ajaxReturn(array(
            "typeMsg" =>  "error",
            "msgText" =>  "漏洞类型错误",
        ));
    }
    switch (I('get.category','','int')) {
        case '1':
            $vul['category'] = "触发alret函数";
            break;
        case '2':
            $vul['category'] = "发现不在白名单里的第三方JavaScript资源";
            break;
        default:
            $this->ajaxReturn(array(
                "typeMsg" =>  "error",
                "msgText" =>  "漏洞类型错误",
            ));
            break;
    }
    if($_SERVER['HTTP_X_FORWARDED_FOR'] === null){
        $vul['hxff_ip'] = "攻击者没有通过代理服务器访问";
    }else{
        $vul['hxff_ip'] = I('server.HTTP_X_FORWARDED_FOR'); //获取攻击者的HTTP_X_FORWARDED_FOR
    }
    if($_SERVER['HTTP_CLIENT_IP'] === null){
        $vul['hci_ip'] = "攻击者数据包头部没有HTTP_CLIENT_IP";
    }else{
        $vul['hci_ip'] = I('server.HTTP_CLIENT_IP');//获取攻击者的HTTP_CLIENT_IP
    }
    $vul['ra_ip'] = I('server.REMOTE_ADDR');    //获取攻击者的REMOTE_ADDR
    $vulcookie    = I('cookie.');   //获取攻击者的cookies
    for($i = 0;$i<count($vulcookie);$i++){
        $vul['cookies'] .= array_keys($vulcookie)[$i].'='.$vulcookie[array_keys($vulcookie)[$i]].'; ';  //拼接成方便查看的cookies格式
    }
    $vul['url']   = I('server.HTTP_REFERER');   //获取攻击者攻击成功的url
    $vul['ua']    = I('server.HTTP_USER_AGENT');    //获取攻击者的User-Agent
    $vul['time']  = date("Y-m-d");  //获取攻击者攻击的时间
    $vul['fixes'] = 0;  //默认为漏洞未修复
    $bugData = M('bugdata');    //连接fecm_bugdata数据库
    $bugData->data($vul)->add();    //添加到数据库中
}

因为这里是接受攻击信息,不能有管理员验证。

后台有一个数据库可视化的表格,这里我使用的Chart.js,下面是后端代码:

#!php
public function index(){
    $reportForm = M('bugdata'); //连接fecm_bugdata数据库
    $dateTimeLabels = [];
    $dateTimeTotal = [];
    for($i = 0;$i < 7;$i++){    //获取近7天的数据
        $time = date("Y-m-d",strtotime(-$i." day"));
        array_unshift($dateTimeLabels,$time);
        $data['time'] = array('like','%'.$time.'%');
        array_unshift($dateTimeTotal,$reportForm->where($data)->count());
    }
    $reportForm = json_encode(["Labels" => $dateTimeLabels,"Total" => $dateTimeTotal]); //转化成json格式
    $this->assign('reportForm',$reportForm)->assign('total',total());   //交给前端模块
    $this->display();   //前端页面生成
}

前端代码:

#!js
var lineChartData = {
    labels :eval({$reportForm})['Labels'],
    datasets : [
        {
            fillColor : "rgba(151,187,205,0.5)",
            strokeColor : "rgba(151,187,205,1)",
            pointColor : "rgba(151,187,205,1)",
            pointStrokeColor : "#fff",
            data : eval({$reportForm})['Total']
        }
    ]
}
var myLine = new Chart(document.getElementById("Statistics").getContext("2d")).Line(lineChartData);

实际的效果图:

p5

p6

p7

0x04 让我们实际测试一下


代码就用0x01节的代码。我们输入<script>alert(1)</script>。看一下:

p8

我们再去平台看一下:

p9

p10

成功显示了。

0x05 检测第三方js资源是否为xss脚本


这一节需要用到之前长短短分享的代码:

#!js
for(var i=0,tags=document.querySelectorAll('iframe[src],frame[src],script[src],link[rel=stylesheet],object[data],embed[src]'),tag;tag=tags[i];i++){ 
    var a = document.createElement('a'); 
    a.href = tag.src||tag.href||tag.data; 
    if(a.hostname!=location.hostname){ 
        console.warn(location.hostname+' 发现第三方资源['+tag.localName+']:'+a.href); 
    }
}

但是他这里只是在console里显示,没有进一步的操作,而且他这里同时检测了iframe、frame、script、link、object、embed标签,对我们来说只需要script标签就行了,于是我重写了这段代码,首先我们需要一个白名单列表,用于放置网站允许第三方加载的url地址:

#!js
var scriptList = [
    location.hostname,
]

这里只是默认的只允许当前域名加载,打击爱可以根据自己的需要添加。

然后就是获取当前网页的所有script标签:

#!js
var webScript = document.querySelectorAll('script[src]');

在把当前的地址赋值var webHost = location.hostname;至于为什么不放在for循环里,因为根据js优化规则,for循环里避免多次一样的赋值。

接下来就是for循环里的代码了:

#!js
for(var i = 0;i < webScript.length;i++){
    var a = document.createElement('a');    //建立一个新的a标签,方便取值
    a.href = webScript[i].src;  //把script里的src赋值给a标签里的href属性
    if(a.hostname != webHost){  //对比,是否为第三方资源
        for(var j = 0;j < scriptList.length;j++){
            if(a.hostname != scriptList[i]){    //判断当前的第三方资源是否在白名单里
                new Image().src = 'http://fecm.cn/Api/addVul/category/2';   //发送给FECM
            }
        }
    }
}

这里我做了一个测试,加载hi.baidu.com的资源:

p11

刷新后,打开FECM平台,看一下:

p12

p13

0x06 结语


因为穷,没有服务器和域名,也没法添加邮件自动提醒功能了。感兴趣的可以自己添加,如果后来我有钱了,我买个服务器,会添加邮件自动提醒的,第一时间会在乌云社区里发布。本来打算采用ED的on事件拦截代码的,但是发现on事件在程序里也会大量使用,索性就没有添加,如果你有思路

下载地址:http://pan.baidu.com/s/1jGVP7Ps

使用时记得在Application\Home\Conf\config.php改下配置(我已经全部加了注释,即使不会thinkphp的也可以搭建)

个人代码写的没有多好,思路可能也比较烂。如果您有什么意见欢迎提出来,我会进一步修改的。

©乌云知识库版权所有 未经许可 禁止转载


30
vc1 2016-04-18 14:57:21

分享个之前做的js。 http://dwz.cn/394rhg

30
梧桐雨 2016-04-12 17:17:08

有自己的实践和想法,非常好,虽然可能性能会差一点,但是楼主愿意分享出来本身就是非常有价值的,应该得到尊重。

30
爱梅小礼 2016-03-23 17:49:40

https://0kee.360.cn/hxj/

30
撸至深 2016-03-22 15:38:50

你们就是鸡蛋里面挑鱼刺,写的太好了

30
_Evil 2016-03-22 15:33:12

顶lz;lz在freebuf的文章受益匪浅;谢谢lz的共享.

30
Black_Hole 2016-03-22 12:47:34

@小秦 你说的是在理的,关于恶意触发报警机制,我在写的时候就想过了,确实存在,而且因为是前端,所以代码是公开的。但是你不可能把页面内所有的代码都看一遍,即使抓包,我使用的也是img发送get请求,不行的话,我还可以添加一个.png的后缀,总不能每一个图片都要看一遍吧。关于hoosts,同理,我这是开源的,域名是由你们而定,你想找的话,那和代码全部看一遍有什么区别呢?即使你搜索变量,JavaScript是可以混淆加密的,你看到的代码也类似于(function(e){var a=0;})(),你也没有耐心去看吧。除了您提的第一条确实存在问题,而且没有好的解决方案。其他的就有点鸡蛋里挑骨头了。

30
小秦 2016-03-22 12:31:22

都去搞注入了, 会不去查看页面源代码?
一般情况下,我都是开个Fiddler直接看相应网站的请求的,像这种统计或者XSS报警发出来的请求,
我基本上都是屏蔽掉的.

就拿现在的代码来举例, 在 hosts中加上 0.0.0.0 fecm.cn 就什么都解决了.

而且另外还有一个事情,就是 我不停的触发你的这个上报机制(或者就找凌晨3,4点), 那么就会导致你的整个报错的数据上升, 那么你的监控是发邮件还是不发? 发了你查不查问题?
查问题要不要时间? 最后发现其实是一些无聊的人在玩, 这不是自己浪费自己感情吗?
最后搞的都不信任这套机制了, 那它不就是鸡肋了?

30
Black_Hole 2016-03-22 11:20:58

可能有些人没有理解我的意思,我这里说明一下,这里的代码,不会像之前那样防御XSS的时候在每个变量前面加入xx(变量)等自定义过滤函数,而是只需要在base.js文件里加入如上的前端代码就行了,不到20行的代码,我想不会影响到整个网站吧。而且整篇文章的思路是按照后端、前端、nginx都没有做好过滤情况的下,来发现漏洞用于及时修补,而不是防御漏洞。现在基本上网站里都有那么一个js文章是最基础的文件,就像jquery。加20行代码就能及时发现漏洞,何乐而不为呢:)

30
missdiog 2016-03-22 11:19:44

思路很赞,个人觉得工业界还是通过CSP的report-uri实施好,无侵入,已扩展升级。

30
erevus 2016-03-22 11:15:54

modsecurity多好用啊

30
莫里 2016-03-22 10:50:28

文章写的好

其实这玩意没有意义
第一 前端一般是个傻b
第二 前端不是傻b,也是懒人,“后端的自动自觉给我去修复”
第三 前端不是傻b也不是懒人,应该不会再处理这种事情了

30
齐迹 2016-03-22 10:37:55

遍历dom,电脑差的用户怕是卡得不要不要的哦

感谢知乎授权页面模版