Hydra
的被动扫描平台,在此也把我们的实践经历做一个分享。Hydra
的待扫描数据主要有以下几大来源:Hydra
插件 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;Hydra
第二大流量来源是自动化接口测试平台,其产生的测试流量经由消息网关传送到 Kafka;t
为消息产生时间缀。对于post body数据,因为可能为流数据,所以我们简单的进行了base64编码后传送。
{
"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
}
/
、 ..
等等,具体可参考URI normalization),当然这里我们不会自己造轮子,前人已经为我们造好,我们只需做一个调包侠就好。之后进行url去重,去重规则利用的传统的hash去重,如拿到的url为:http://site.com/?get_key1=value1&get_key2=value2&get_key3=value3
md5("http://site.com/?get_key1|get_key2|get_key3")
/ path/202cb962ac59075b964b07152d234b70
/ path/c8ffe9a587b126f152ed3d89a146b445
/ 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="}
解析完成后数据结构如下:
{
'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__')
])
}
class BaseMod(object):
name = VulnName.BaseMod
author = "xxx"
vulType = vulType.Web
level = Level.NONE
references = None
desc = None
modType = ModType.PerScheme
def __init__(self):
"""
"""
self._request: Request = None
self._vectors = None
self._response = None
self._vulnParams = ""
def verify(self):
"""
:return: return None if not vulnerable, else return a Output object
"""
raise NotImplementedError("plugin must overwrite this method.")
新增一个扫描插件时,新增一个类继承自 BaseMod
, 填写必要的信息,并重写 verify
方法实现扫描逻辑,发现漏洞时,返回 Output的实例对象,由扫描引擎持久化漏洞数据。
插件类型 | 简要说明 |
PerFile | 扫描某个文件是否存在漏洞 |
PerFolder | 扫描某个目录是否存在漏洞 |
PerScheme | 扫描某个参数是否存在漏洞 |
PerServer | 扫描某个服务器是否存在漏洞 |
Disable | 处于开发测试或者禁用的插件,引擎不会加载此目录下的插件 |
flushSession
参数,如下简单的例子:
sqliopts =
{
"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
}
对于sqlmap ,因其会发送大量的payload,所以不可避免的在会在测试环境插入大量脏数据,影响测试过程。在银行大量资金交易应用的场景下,影响较大,在项目初期,因未做好控制,被投诉好几次。对于黑盒扫描,脏数据一定是无法避免的,我们能做的就是尽量少产生脏数据。对于完全不能扫描的业务系统, 使用黑名单屏蔽;对于其它的业务系统则采用授权扫描方式,并将扫描嵌入到sdlc中提高扫描覆盖率。业务在冒烟测试通过后的某个节点时间须手动触发扫描,授权完成后拉取记录的流量执行扫描,扫描平台自动生成扫描报告,如果没有扫描报告,安全卡点系统则拒绝其上线; 对于其它无版本的系统,则只扫描get请求,尽可能少产生脏数据。