被动式漏洞扫描平台建设之路
2020-5-13 14:15:0 Author: mp.weixin.qq.com(查看原文) 阅读量:9 收藏

Author:平安银行应用安全团队 - 王小林
0x00.前言
业务上线之前,通常需要进行一系列的安全扫描及工具检查,随着敏捷开发流程的推进,版本迭代的周期越来越短,传统的主动web扫描器(包括开源的、商用扫描器等)基于站点维度,扫描时进行大量的链接爬取,无法覆盖接口、孤岛链接,也无法深度融入内部研发流程,以及会有扫描配合成本耗费巨大等问题,这使得主动web扫描器在面对大型企业成千上万站点的扫描需求时越来越显得捉襟见肘。相对于传统的主动扫描,被动扫描则刚好弥补了这些缺点,各个大厂也纷纷走上了扫描器的自研之路。
为解决上述的需求,实现全行应用上线的自动化安全扫描,我们团队内部也研发了命名为 Hydra的被动扫描平台,在此也把我们的实践经历做一个分享。

0x01.架构设计

整个项目分为 6个模块,分别为流量消息网关、流量解析、扫描模块、日志模块、漏洞管理,资产收集,系统架构如下图:
如上图,从交换机获取到的流量会经过流量解析引擎,然后发送到消息队列等待扫描引擎的消费;其它来源的流量则经由消息网关转发到消息队列。扫描引擎从消息队列取到消息后先进行去静态,去重去脏后,再封装成任务对象,调用各插件进行扫描,扫描完成后若发现漏洞则持久化到 MySQL 数据库,漏洞数据库与漏洞管理系统对接,方便跟踪处理漏洞,形成漏洞管理闭环,同时扫描日志发送到ELK日志中心,方便后期排查。资产中心则是记录全行所有的主机,接口等信息,为资产元数据的建设提供数据。

0x02.数据源

Hydra的待扫描数据主要有以下几大来源:
  • 测试区核心交换机
  • 自动化测试平台(接口自动化测试平台)
  • Hydra插件 
  • 代理服务器
  1. 从交换机接过来的流量为全协议的流量,需要先将三层协议的流量解析到七层,这里我们使用的是Zeek , Zeek是一个开源的网络流量分析工具,支持解析多种协议(包括DNS,FTP,HTTP,IRC,SMTP,SSH,SSL等)。详细介绍可参考官网文档Zeek Doc; (也可围观团队另外一位小伙伴写的Zeek食用指南《流量分析的瑞士军刀Zeek》,文章记录我们使用Zeek中一些经验与踩过的坑。Zeek 解析流量后生成http.log 日志文件,然后使用 Filebeat读取 Zeek日志并发送到Logstash,使用 LogStash 初步过滤一些静态文件,例如img、png、docx 等。然后再组装成约定的 json格式发送到 Kafka;
  2. Hydra第二大流量来源是自动化接口测试平台,其产生的测试流量经由消息网关传送到 Kafka;
  3. 针对其它场景,我们开发 chrome插件和 Burp插件,插件支持一些简单的文件,域名过滤。
    Burp插件:
    Chrome插件:
    Burp插件主要是针对一些chrome插件不能使用场景的一个补充,如部分业务系统只能运行在IE浏览器中、app等,但这个插件对一般的功能测试人员门槛有点高。此插件也可作为内部安全同事测试时的一个补充。
  4. 代理服务器基于 MITMProxy 开发,客户端需要安装服务器证书以解密流量。
为了方便数据处理,所有传入到消息队列的数据需要约定统一格式,我们约定如下简要格式,其中 t为消息产生时间缀。对于post body数据,因为可能为流数据,所以我们简单的进行了base64编码后传送。
  1. {

    "url": "http://testasp.vulnweb.com/showthread.asp?id=0",

    "headers": {

    "Host": "testasp.vulnweb.com",

    "Connection": "keep-alive",

    "Cache-Control": "max-age=0",

    "DNT": "1",

    "Upgrade-Insecure-Requests": "1",

    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",

    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",

    "Accept-Encoding": "gzip, deflate",

    "Accept-Language": "zh-CN,zh;q=0.9",

    "Cookie": "ASPSESSIONIDQQBRQABB=FDKAKDNCHPAKFGGIMLNLFBLB"

    },

    "host": "testasp.vulnweb.com",

    "method": "GET",

    "source": "xxxx",

    "postdata": "",

    "t": 1565079259

    }

为了不对生产环境产生影响,我们只扫描测试区的系统,利用域名/IP黑名单对在测试区不能扫描的服务器进行排除。

0x03. 数据解析

扫描引擎从消息队列取到一条消息之后,会先对其再次去静态(前期zeek已经去过一次),然后进行url归一化(如将协议名、主机名进行小写、去掉默认的 80 端口、消除多余的 /..等等,具体可参考URI normalization),当然这里我们不会自己造轮子,前人已经为我们造好,我们只需做一个调包侠就好。之后进行url去重,去重规则利用的传统的hash去重,如拿到的url为:http://site.com/?get_key1=value1&get_key2=value2&get_key3=value3
取出其中的key值并排序后计算:md5("http://site.com/?get_key1|get_key2|get_key3")
计算出的md5去redis中查询是否存在,如果存在则认为是重复的报文,否则发往扫描引擎进行扫描。在这里需要考虑一些特殊的场景:伪静态,如下两个url:      

/ path/202cb962ac59075b964b07152d234b70

/ path/c8ffe9a587b126f152ed3d89a146b445

如果利用hash去重机制则会认为是不同的url,如果存在大量这样的站点,则需要扫描的报文急剧增加,常见的做法是替换为同一等长字符,如约定用A替换,刚处理后的url为:

/ path/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

/ path/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

去重之后便需要对报文去脏,对于一些带有明显扫描攻击的payload的报文,我们需要将其剔除,这一类流量主要来自内网的其它扫描器等。

能进入到此步骤的便是我们需要扫描的报文了,为了方便各个插件的编写,我们需要将字符串的报文解析为对象,对于 post body ,因为格式太多,有json、key value、xml、multipart等难以满足一个统一的数据结构,当初一直在研究如何设计数据结构方便处理,后来看了下sqlmap 的源码,觉得其设计得较合理,便借鉴过来了。对于url中的参数处理则简单许多,直接将key与value拆分存放到 dict即可。如我们拿到一个报文:{"url":"http://site.com/?get_key1=value1&get_key2=value2&get_key3=value3","postdata":"a2V5MT12YWx1ZTEma2V5Mj12YWx1ZTIma2V5Mz12YWx1ZTM="}解析完成后数据结构如下:

  1. {

    'GET': OrderedDict([('get_key1', 'value1'), ('get_key2', 'value2'), ('get_key3', 'value3')]),

    'POST': OrderedDict([

    ('key1', 'key1=value1__PAYLOAD_MARKER_PAYLOAD__&key2=value2&key3=value3'),

    ('key2', 'key1=value1&key2=value2__PAYLOAD_MARKER_PAYLOAD__&key3=value3'),

    ('key3', 'key1=value1&key2=value2&key3=value3__PAYLOAD_MARKER_PAYLOAD__')

    ])

    }

为了简化插件开发,屏蔽报文操作细节内容,我们在其上封装了一个代理类,插件只需要告诉代理类poc是什么便可快速组装一个请求,对于插件编写人员,不需要了解具体的报文格式。这样就降低插件的开发难度。解析完成之后,引擎便会调用扫描插件进行扫描。

0x04. 插件

插件作为扫描器的核心有着举足轻重的地位,能不能"准确无误"的发现漏洞全靠扫描插件。在这里,非常感谢团队内的各个小伙伴一起为扫描器建设出谋划策和参与到插件的开发工作之中。对于插件编写,我们简单地约定了一套编写规则,每一个插件需要正确的覆写父类方法。引擎才能正确调用,如下简要父类模板:
  1. class BaseMod(object):

    name = VulnName.BaseMod

    author = "xxx"

    vulType = vulType.Web

    level = Level.NONE

    references = None

    desc = None

    modType = ModType.PerScheme

  2. def __init__(self):

    """

    """

    self._request: Request = None

    self._vectors = None

    self._response = None

    self._vulnParams = ""

  3. def verify(self):

  4. """

  5. :return: return None if not vulnerable, else return a Output object

  6. """

  7. raise NotImplementedError("plugin must overwrite this method.")

新增一个扫描插件时,新增一个类继承自 BaseMod, 填写必要的信息,并重写 verify方法实现扫描逻辑,发现漏洞时,返回 Output的实例对象,由扫描引擎持久化漏洞数据。

为了提高扫描效率需要对插件进行正确分类。这里我们参考了awvs 插件分类规则,将插件分为如下五大类:

插件类型

简要说明

PerFile

扫描某个文件是否存在漏洞

PerFolder

扫描某个目录是否存在漏洞

PerScheme

扫描某个参数是否存在漏洞

PerServer

扫描某个服务器是否存在漏洞

Disable

处于开发测试或者禁用的插件,引擎不会加载此目录下的插件

每一个插件需要根据自己的扫描目标特点正确放置于相应的目录下,引擎会自动加载各目录下面的插件并为插件设置对应的类型值。目前,我们会周期性的对历史接口进行扫描。对于发现的漏洞利用邮件通过对应的开发负责人和对应的安全工程师,以方便及时跟进处理。
对于sql注入的检测,我们直接调用了sqlmap的api进行扫描。基于我行使用的数据库,删除了sqlmap中部分poc,这里需要注意的是,sqlmap会自动加载上次扫描的结果,对于已经修复过的漏洞如果再次扫描到,sqlmap 会直接提示存在漏洞,所以需要传入 flushSession参数,如下简单的例子:
  1. sqliopts =

  2. {

    "url": url,

    "data": postdata,

    "method": method,

    "answers": "crack=N,dict=N",

    "cookie": cookie,

    "headers": header,

    "risk": 1,

    "level": 1,

    "agent": UA,

    "threads": 3,

    "timeout": TIMEOUT,

    "forceSSL": force_https,

    "flushSession": True,

    "proxy": proxy,

    "batch": True

  3. }

对于sqlmap ,因其会发送大量的payload,所以不可避免的在会在测试环境插入大量脏数据,影响测试过程。在银行大量资金交易应用的场景下,影响较大,在项目初期,因未做好控制,被投诉好几次。对于黑盒扫描,脏数据一定是无法避免的,我们能做的就是尽量少产生脏数据。对于完全不能扫描的业务系统, 使用黑名单屏蔽;对于其它的业务系统则采用授权扫描方式,并将扫描嵌入到sdlc中提高扫描覆盖率。业务在冒烟测试通过后的某个节点时间须手动触发扫描,授权完成后拉取记录的流量执行扫描,扫描平台自动生成扫描报告,如果没有扫描报告,安全卡点系统则拒绝其上线; 对于其它无版本的系统,则只扫描get请求,尽可能少产生脏数据。

另一条路则是不使用流量中的身份凭证,通过sso替换请求中的身份,这样对业务的影响则相对可控,但随之而来的问题,则是通过sso登录的安全扫描账号在对应的业务系统里面无数据,会产生漏报,所以需要我们权衡取舍。对于sql注入的扫描,我们这边有一些比较好的思路,基于我行已经大量部署的应用监控平台 cat,抓取服务运行时的堆栈数据,如执行的 sql语句等,然后进行离线分析。(可参考我们之前发的【企业快速实践部署IAST/RASP的一种新思路】)目前我们已经取得初步成就,正考虑逐步淘汰 sqlmap。
为了应对行内大量的流量处理,我们使用的集群方式,部署多台扫描引擎加快处理速度,但有可能qps过高,导致对方系统宕机,所以需要进行并发控制,我们使用的方式比较简单即控制线程数。

0x05. 应用

自19年中旬进行被动扫描器规划并进入开发,同年10月左右扫描器稳定运行,截至目前累计处理流量 1.5PiB,累计扫描报文40亿+,发现大量安全漏洞。
扫描发现的漏洞落地到漏洞管理系统,由漏洞管理系统跟进处理流程。通过对接行内的资产cms、cmdb数据,我们可以由漏洞的ip信息直接定位到开发负责人,并快速通知负责人进行漏洞修复。
另一方面,我们能做的不仅仅只是扫描。当我们拿到全行的测试流量时,我们可以对流量进行分析,如资产分析,记录全行所有的主机资产信息;接口信息,分析各业务系统使用的组件等。在应急响应时,我们可以快速进行全行的资产扫描,定位风险资产。基于流量的检测手段(DAST)是我们检查工具集中重要的一部分,能做到的不仅仅是应用安全,还有各类安全合规层面的问题,例如是否接入sso,敏感信息是否掩码等等,之后自动将检测结果同步到发版卡点系统,即可做到各应用上线前的安全管控。

0x06. 总结

1、目前,被动扫描器已进入稳定运行状态,我们需要做的事就是优化各插件扫描效率,提高插件的准确率。对于行业难度检测的越权漏洞,我们团队目前也有一些不错的点子,同上面 sql 注入的检测原理,基于 cat利用iast技术,记录请求与服务端的堆栈信息进行分析对比,目前正在着手开发。
2、对于被动扫描器发现的漏洞,如果都需要人工去复测也是一件工程量比较大的量,我们也在考虑如何做到自动复测,在这一点上,唯一的困难就是会话问题,如果大部分网站都支持sso 的话就比较简单,直接内置两套账号密码便可以刷新会话,实现自动复测,但如果 sso 覆盖面比较少,做起来则比较繁琐。同样缺点前面也提到过,有可能安全扫描的账号无业务数据,造成精准度不高。这一点我们也在摸索中,有兴趣的伙伴也可以交流。


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg3NjI1MzU4Mg==&mid=2247483950&idx=1&sn=65a355d82a4a12f82ecc67882464add7&chksm=cf345acff843d3d9d722fd1282d78f7771b6fdb7d221313d4252307a35c31322774b7ac0f522&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh