1 前言

基于先前所做的研究工作,需要构建web应用的内路径图谱,本意是想构建精确的渗透路径,目前市面上看到大多数扫描器如果能够扫出来漏洞,好一点的可能会将攻击url和攻击参数展示出来,但是局限性就在于如果这个链接需要层层跳转,当用户不能直接访问这个攻击链接的时候,可能用户就会很迷茫:我该如何访问到这个链接?

那么构建了web内路径图谱后,我们就可以根据爬虫的爬取先后顺序构建一张web应用的网图,那么当出现某个攻击链接的时候,我们就可以使用图查询语句来搜寻从主页到攻击链接的最短路径,从而获得一条精确的渗透路径。

但是在构建过程中发现了一笔意外宝藏,这种检测思想可以对某些不那么常规的漏洞进行检测,比如EAR漏洞。

2 EAR漏洞

EAR漏洞翻译过来应该是执行重定向逻辑漏洞,英文按照理解应该是execution after redirection,其实按照审计的思路这个漏洞不是很复杂,其实就是对于某些关键操作的鉴权没有加上exit等操作,导致程序虽然进行了header跳转或者href跳转,但是页面代码及页面内容仍旧可以执行或返回。

2.1 漏洞原理

下面给出一个具体的案例

这里的代码重点关注第三行,这里可以看到在鉴权上也的确使用了session来确保安全,但是漏洞点就在于只使用了header来进行跳转,而没有加上exit函数,这就会导致第三行往下的代码虽然我们没有username的权限但是依旧可以调用和执行下面的代码。

有人看到这里大概就已经明白了,这个漏洞其实就是逻辑漏洞的一种,大概率出现的业务场景应该是安装功能和鉴权功能。目前笔者所能想到的比较通用的业务场景是这两个,所以检测其实也是针对通用的业务场景。

如果搜下以往的漏洞应该会有大批量的安装EAR漏洞,大致场景就是虽然判断了lock是否存在,但是在判断完之后只是用了header来跳转到提示页面,没有使用exit函数来退出当前页面,导致安装功能可以提交新的数据库参数,从而导致重安装漏洞的存在。

<?php

function check_login()
{
    session_start();
    if (isset($_SESSION['user']) || isset($_SESSION['pass'])) {}
    else{
        echo "<script>";
        echo "window.location.href = '?c=login'";
        echo "</script>";
    }
}

那么EAR漏洞对应到鉴权功能,大致代码就是对后台的权限进行了控制,如果没有user权限,那么也只是使用了header来跳转到登录界面,没有使用exit函数来退出,导致后台的所有的功能其实都是可以调用的。

2.2 漏洞危害

那么在看完漏洞原理后,相信原先不太理解EAR漏洞的应该都有了一定的认识,从后台鉴权这个业务场景来看,危害其实有两点
1、泄露了后台页面源码 2、无限制调用后台功能

这里就拿发现的EAR漏洞为例,利用burp抓包可以看到,在页面上方的确有window.location.href跳转,但是在后台源码里没有加上exit函数,导致在当前回显页面里其实是可以看到后台的登录界面和接口参数的,通常情况下后台的鉴权操作为统一接口,那么如果这个接口函数没有exit函数,那么利用爬虫机制其实可以爬取所有的后台页面。

(感觉这种漏洞应该也算是纵向越权的一种。。)

3 图谱构建

说了这么多,大家可能觉得我们还需要图谱来辅助检测吗?限于笔者对此漏洞的理解能力,如果我们想要去检测此类EAR漏洞,其实有两种办法,第一种就是上面的基于burp抓包然后查看页面回显,第二种就是白盒审计,查看安装功能和鉴权功能是否加上了exit这样的函数,其实这两种检测办法对应了漏洞检测的两种形态:黑盒测试和白盒测试。

那么针对这种漏洞而言,白盒检测相对较为容易,黑盒检测难度其实稍微大些,那么利用我们所构建的图谱,其实就是基于黑盒测试来做的检测。

3.1 爬虫机制

这里爬虫没有什么新意,其实就是使用了scrapy框架来进行页面爬取,但是在爬取代码上其实做了很大的改动,先前的scrapy爬虫其实就是爬取页面所有的url,然后进行递归爬取,虽然是有先后之分,但是在结果里是没有层级之分的,也就是说我们虽然能够获得一个网站的所有链接,比如a、b、c,但是没有形成路径,也就是说我们希望获得的结果应该是a->b->c,或者是a->b,a->c。

那么这里其实我们就需要重写爬虫的底层代码,将他的爬取机制重新改变下。

这里按照先前的思路,我们需要单独抽取每个页面的url,这里我还抽取了表单参数,抽取表单的意义其实跟本文不太相关,主要还是为了构建前言所提到的攻击路径用的。那么这里针对主页,我们其实就已经获取到了主页里所有的url,那么这里在图谱构件上其实已经初现端倪:主页->主页里所有的url。

接着使用yield来递归遍历主页里所有的url,并进行递归抽取,当不再出现新的页面时爬虫终止。在每次递归时都将当前url和爬取url传递给pipeline做处理,这里的处理简单点来讲就是入库。

下面附上爬虫的部分代码,这部分主要是解析用,其实很简单,就是抽取每个页面的url和form参数,构造全局的url列表,然后递归抽取。

def parse(self, response):
        url_list = response.xpath('//a/@href').extract()
        form_list = response.xpath('//form').extract()
        url_result , form_result = [] , []
        for form in form_list:
            form_selector = Selector(text=form)
            action = form_selector.xpath('//form/@action').extract()[0]
            if urlparse.urlparse(action).netloc == '' :#and urlparse.urlparse(action).path == '':
                action = urlparse.urljoin(self.root_url,action) 
            param_type = form_selector.xpath('//form/@method').extract()[0]
            param_list = form_selector.xpath('//input').extract()
            result = {}
            for param in param_list:
                param_selector = Selector(text=param)
                name = param_selector.xpath('//input/@name').extract()
                value = param_selector.xpath('//input/@value').extract()
                if name:
                    name = name[0]
                    if value:
                        #form表单中value可为空
                        value = value[0]
                    else:
                        value = ''
                    result[name] = value
            form_three = [action , param_type , result]
            #表单三元组
            if self.form_include(self.form_set , form_three):
                self.form_set.append(form_three)
                form_result.append(form_three)
        for url in url_list :
            url_add = urlparse.urljoin(response.url,url)
            if '#' in url_add:
                url_add = url_add[:url_add.find('#')]
            if urlparse.urlparse(url_add).netloc == self.status_root.netloc and url_add not in UrlSpider.url_set:
                #signal.signal(signal.SIGALRM, self.myHandler)
                #signal.alarm(10)
                self.url_set.add(url_add) 
                url_result.append(url_add)
                yield scrapy.http.Request(url_add,callback=self.parse,dont_filter=True)

        item = NgItem()
        item['url'] = response.url
        item['form'] = form_result
        item['name'] = url_result
        yield item

3.2 图数据库

这里采用的图数据库为neo4j,目前由于知识图谱概念的兴起,图数据库慢慢的走进人们的视线,这次将EAR漏洞检测和图数据库结合起来,也算是一点小小的创新吧。。。

图数据库的两个构建基本要素:节点-关系-节点 、 节点-属性-值。那么针对这里的应用场景,我们的节点构建就可以以url来作为单位,由于图谱为有向图,所以这个关系在这里其实暂定为“next”关系,表示web内径的上下级关系。这里的节点属性值其实没什么必要应用,我们的重点在于各个url的层级关系。

实际上当前index.php的上一位置的节点就是后台的首页(这里后台的登录页面实际上就在首页页面当中,所以能够遍历到,另外首页的登录接口写的是?c=admin而不是?c=login,为漏洞埋下了直接的伏笔,后来想了下这个点其实非常重要,如果仅仅是登录界面,那么爬虫其实就爬取不到后台界面了,也就不会出现EAR漏洞)。这里可以看到我将当前index.php设置为root_url,另外这里图谱中关系还有param这样关系,这里其实也是为了构造精确的渗透路径用的,主要用于存储form表单中的参数,为了作漏洞验证而加进去的。

4 检测原理

这里检测原理为了作通用性考虑,目前所能想到的检测方法是查询index.php到后台页面的最短路径,如果其中经过了后台首页,那么就可以判定为出现了EAR漏洞。
下面为图查询语句,其实这里已经写好了自动化检测脚本,唯一缺陷可能就是需要配置查询路径的终点,也就是后台的某些链接需要提前配置好,不然如果图谱一旦扩大,通过肉眼去看肯定不太现实。
MATCH (p1:url {name:'http://127.0.0.1/101html/index.php'}),(p2:url{name:'http://127.0.0.1/101html/index.php?c=admin&a=save_content'}), p=shortestpath((p1)-[*..10]-(p2)) RETURN nodes(p)

这是查询后的路径结果,由于url太长了可能看不见,这里单独列一下结果

其中的name序列就是从index.php到后台页面的路径图谱,可以看到经过了后台首页,再从后台首页的回显内容了进行了二次遍历。

附上github地址:
https://github.com/yinhongji/WebCrawl

后记

其实完整的功能还有带登录权限的功能,也就是说其实有两个爬虫的,一个爬虫从首页爬取,另一个爬虫从后台登录页面进行爬取,这样做的目的就是想看交叉结果,比如说带登录权限的爬虫跟不带登录权限的爬虫所爬取的页面如果出现重合,那么是否可以判定出现了EAR漏洞或者是纵向越权,但是在测试wordress站点时,发现从后台爬取也会爬到前台,这就会出现无法判定漏洞的情况出现。。。当然按照前文的方法,可能需要判定路径是否出现某一特定节点。

目前将图数据库和安全结合的应用还比较少,此文就当抛砖引玉,希望跟更多做图数据库和安全的人一起交流交流~

上述如有不当之处,敬请指出~

点击收藏 | 1 关注 | 2
登录 后跟帖