identYwaf
identYwaf 介绍
identYwaf 是一款开源的 WAF 类型识别工具,通过发送给目标站点一些恶意的 payload,并且分析目标站点的响应信息来识别站点使用的 WAF 类型,支持识别超过 80 个的知名 WAF 产品,相关的链接如下:
- https://github.com/stamparm/identYwaf
- https://www.slideshare.net/stamparm/blind-waf-identification
笔者分析该项目的起因是因为在分析 sqlmap 时发现 sqlmap 使用的第三方插件正是该项目,并且 sqlmap 并未全部使用该项目中的指纹信息,其中data.json中的 signatures 字段并未被 sqlmap 使用,并且 identYwaf 对于该字段的使用也比较有意思,因此想要记录一下。
流程分析
identYwaf 识别 WAF 产品主要分为两种检测方式,下文主要分析的是利用 signatures 检测方式:
- 利用 regex 字段识别,简单正则内容匹配 WAF 指纹。
- 利用 signatures 字段识别。
接下来描述下 identYwaf 的主要检测流程:
- 首先是判断是否存在 WAF,identYwaf 参考了 sqlmap的启发式sql注入 ,发送一个包含多种攻击类型的 payload 给目标站点,匹配一些拦截关键字(例如
protect,rejected等)或者将响应页面同正常页面比较,计算页面相似度,如果差异过大则认为存在 WAF。 - 当判断存在 WAF 时 ,identYwaf 会依次发送 data.json 中的 payload 给目标站点,记录响应内容,然后通过上述两种方式来检测识别 WAF 类型。
identYwaf 对于 WAF 的识别均位于 data.json 中,因此两种方式都使用了其中的数据,所以以其中一个数据为例进行展示,格式的简单。每个WAF 有对应的指纹。
"wafs": {
"360": {
"company": "360",
"name": "360",
"regex": "<title>493</title>|/wzws-waf-cgi/",
"signatures": [
"9778:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC",
"9ccc:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A4chW1XaTC"
]
}
}
regex 识别
通过 regex 字段识别逻辑很简单。取对应 WAF 的 regex 字段,data.json 中内置了不同种类攻击的测试 payload,依法发送给目标站点,然后将响应内容与 regex 字段进行匹配,如果有命中即判定存在该类型的 WAF。
def non_blind_check(raw, silent=False):
retval = False
match = re.search(WAF_RECOGNITION_REGEX, raw or "")
if match:
retval = True
for _ in match.groupdict():
if match.group(_):
waf = re.sub(r"\Awaf_", "", _)
non_blind.add(waf)
if not silent:
single_print(colorize("[+] non-blind match: '%s'%s" % (format_name(waf), 20 * ' ')))
return retval
signatures 识别
首先说明下笔者理解的 identYwaf 通过 signatures 识别指纹的思路:当涉及到不同的 WAF 产品时,它们的拦截效果会有所不同。理论上,如果我们有足够多的攻击样本,并记录每种 WAF 对每个 payload 的拦截结果,那么这些汇总后的数据可以作为该时间段内(随着时间的推移,WAF 产品也会进行改进)特定 WAF 的指纹。如果在其他站点也发送了大量的 payload,并取是否被拦截作为结果,然后将这些结果合并计算成指纹,就可以与先前记录的指纹进行比较,如果相同,就可以确定该站点正在使用某种特定的 WAF 产品进行防护。
当然这是理想的效果,实际上我们并不能发送大量的 payload,并且除了 WAF 会进行改进外,不同的站点对于相同 WAF 的配置可能也会有所差异。因此可以尝试进行一些优化:
- 可以将大量的 payload 改成不同攻击类型的 payload,并且 payload 应该是那种属于那种弱特征的 payload。因为面对该种类型 payload,不同类型的 WAF 的反应类型是不同的,并且受后续产品改进的影响也比较小。
- 对比结果不需要完全一致,可以比较相似度,相似度高的也可能认为是对应类型的 WAF。
了解了思路之后,然后开始分析 identYwaf 的检测流程:
data.json中记录了 40 多中不同类型的 payload,identYwaf 会记录站点对于每个 payload 的拦截情况。连同 hash 后的 payload 和拦截结果一起作为 sign 的一部分。直到将所有的 payload 发送完毕。# ---------------------signature-------------------| # hex(crc32(sign)) | base64(sign) | # ---------------------signature-------------------| # # -----------------------sign----------------------| # hex((crc32(payload_i)<<1 | last)&0xffff)|.i+1.|..| # -----------------------sign----------------------| for item in DATA_JSON["payloads"]: # ... # 判断是否是被拦截了 last = check_payload(payload, protection_regex) # 匹配data.json中的指纹判断来识别对应的waf,命中则加入到non_blind数组中 non_blind_check(intrusive[RAW]) # 将payload做hash | 匹配结果last 转成字符串 signature += struct.pack(">H", ((calc_hash(payload, binary=False) << 1) | last) & 0xffff) # last结果是则以X,否则以.,将每个结果进行拼接 # results用于后续判断WAF对攻击数据包的拦截程度,输出waf严格程度 results += 'x' if last else '.'`- 接着是对 signatures 的匹配:
- 如果得到的 signatures 同
data.json中某个 WAF 记录的 signatures 相同那就直接判断为对应的 WAF。if signature in SIGNATURES: waf = SIGNATURES[signature] print(colorize("[+] blind match: '%s' (100%%)" % format_name(waf))) - 如果不同则会开始计算相似度,将
data.json中记录的每个 signatures 进行解码,提取出 WAF 对于对应位置 payload 的拦截结果,同当前站点的拦截结果进行比较,计算相似度。最后以相似度进行排序,最高的则认为是最有可能的 WAF。for candidate in SIGNATURES: counter_y, counter_n = 0, 0 decoded = base64.b64decode(candidate.split(':')[-1]) for i in xrange(0, len(decoded), 2): part = struct.unpack(">H", decoded[i: i + 2])[0] if part in markers: # 对应位置payload被拦截的数量 counter_y += 1 elif any(_ in markers for _ in (part & ~1, part | 1)): # 对应位置payload未被拦截的数量 counter_n += 1 # 计算结果,最高的则 认为是最可能的 result = int(round(100.0 * counter_y / (counter_y + counter_n)))
- 如果得到的 signatures 同
思考
signatures 这种检测 WAF 的思路比较有意思。笔者觉得同样的思路用在扫描器的精准识别或者是空间测绘上也可以的,之前笔者就尝试用类似的思路去主动探测 Cobaltstrike 的 HTTP 服务器。