对于物联网设备,比如家用路由器模块,UPNP、HTTP server、Telnet都是用户经常接触的模块,通常也能够与外界交互,从而提供了入口。如果这些模块或者使用的协议存在漏洞,往往能够直接利用,达到远程攻击的效果。
IoT 固件分析往往涉及到二进制代码审计、静态扫描以及动态调试等,其中模糊测试作为一种重要的方法,能够给安全测试添砖加瓦。针对IoT设备的模糊测试主要有两类
本次重点介绍Sulley的衍生项目——boofuzz的使用。对物联网设备的协议fuzz测试,不可丢失的一环是监控器,能够发现bug是监控器的作用所在。一般来说,大多数针对协议的fuzz测试,在每次发送变异报文后,通过ping或者其他协议监测对方是否在线从而判断设备是否出现异常。这种方法较为简单,但是粗粒度的监视器带来的后果是较高的误报率;另一种方式是ssh或者telnet登录目标设备,实时观察当前设备的状态,这种方式优点是随时捕捉异常信号,缺点是,针对不同设备可能要开发不同的监控API。所以当前诸多的协议Fuzz工具,大部分使用第一种,即通过目标设备是否可达来判断崩溃情况。
项目地址:https://github.com/Bit9/sulley。该项目基于Python2.7,也就是说,如果你要安装此工具,需要该Python环境的支持。
sulley 是一款模糊测试框架,可以使用 sulley 进行协议Fuzzing。Sulley(以作者的观点来看)所具有的能力超过了以前所发布的大多数模糊测试技术,包括商业工具和那些在公开领域中可用的工具。该框架的目标是不仅要简化数据的表示,而且还要简化数据的传输以及对目标的监视。Sulley从Monsters,Inc创建之后就被亲切的命名 ,因为它是模糊化的工具。现在sulley 已经停止更新,不再维护。1
大多数情况下,现代模糊器只专注于数据生成。Sulley不仅具有令人印象深刻的数据生成能力,而且还朝着更进一步的方向迈进了一步,其中还包括现代模糊器应提供的许多其他重要方面。Sulley监视网络并有条不紊地维护记录。Sulley可以检测和监视目标的运行状况,并可以使用多种方法将其恢复到已知的良好状态。Sulley对检测到的故障进行检测,跟踪和分类。Sulley可以并行进行测试,从而显着提高测试速度。Sulley可以自动确定哪些独特的测试用例序列会触发故障。Sulley可以自动完成所有这些工作。
图 Sulley框架 2
上图是Sulley的整体框架图,来源于BlackHat。Sulley主要包括四大组件
其中,数据生成和会话管理是比较重要的两个模块。Sulley数据生成方式是基于generation-based的方式,需要对协议或者文件进行建模。数据生成的特点3
会话模块,根据已经构建好的Request,利用 Pgraph
绘制, Pgraph
可以创建、修改和渲染图
图 Session2
在上图中,节点ehlo、helo、mail from、rcpt to、data表示5个请求,路径’ehlo’->‘mail form’->‘rcpt to’->‘data’和’helo’->‘mail from’->‘rcpt to’->data’体现了请求之间的先后顺序关系。callback_one()和callback_two()表示回调函数,当从节点echo移动到节点mail from时会触发该回调函数,利用这一机制,节点mail from可以获取节点ehlo中的一些信息。而pre_send()和post_send()则负责测试前的一些预处理工作和测试后的一些清理工作。每个节点连接起来,组成状态图,可以对图中的每个节点进行操作,也可以自定义callback回调函数。
由于该项目不再更新,为此,本次将使用另外一个版本boofuzz,该工具基于Sulley,并且作者积极更新。Boofuzz是古老的Sulley模糊测试框架的分支和后续版本。除了大量的错误修复之外,boofuzz还致力于扩展性。
Session对象是Fuzz测试的核心,当你创建Session的时候,你会传递一个Target对象,该对象本身接收一个Connection对象
session = Session(
target=Target(
connection=SocketConnection("127.0.0.1", 8021, proto='tcp')))
连接对象实现ITargetConnection。可用的选项包括 SocketConnection和SerialConnection。
准备好会话对象后,接下来需要在协议中定义消息。细节请参照 静态协议定义功能定义协议 。每一条消息均以一个s_initialize
函数开头,比如这是fuzz FTP协议中的几个消息定义:
s_initialize("user")
s_string("USER")
s_delim(" ")
s_string("anonymous")
s_static("\r\n")
s_initialize("pass")
s_string("PASS")
s_delim(" ")
s_string("james")
s_static("\r\n")
s_initialize("stor")
s_string("STOR")
s_delim(" ")
s_string("AAAA")
s_static("\r\n")
s_initialize("retr")
s_string("RETR")
s_delim(" ")
s_string("AAAA")
s_static("\r\n")
定义消息后,将使用刚刚创建的Session对象将它们连接到图中:
session.connect(s_get("user"))
session.connect(s_get("user"), s_get("pass"))
session.connect(s_get("pass"), s_get("stor"))
session.connect(s_get("pass"), s_get("retr"))
之后,就可以进行fuzz测试了
session.fuzz()
每次运行的日志数据将保存到当前工作目录下boofuzz-results目录中的SQLite数据库中。可以随时通过以下任一方式在任何这些数据库上重新打开Web界面
boo open <run-*.db>
该段译自:https://boofuzz.readthedocs.io/en/stable/user/quickstart.html# API接口的详细说明,参照手册4即可。
Boofuzz需要Python。建议的安装要求pip。
Ubuntu: sudo apt-get install python-pip
请注意,源码是用python2写成的,高版本ubuntu自带python3,需要自己安装python2,所以应该用pip2
Ubuntu: pip2 install boofuzz
其他系统安装,请参考手册。官方手册中,提供了两个开源的示例 5
在理解了 boofuzz (Sulley) 的架构之后,现在我们用boofuzz实战操作一波吧。
比如,我们要对路由器的登录接口进行fuzz测试,首先需要使用Burpsuite设置代理,进行抓包。以下为某路由器的登录界面
Burpsuite抓包结果
现在需要根据报文,利用boofuzz框架提供的原语对http请求进行定义
s_initialize(name="Request")
with s_block("Request-Line"):
# LINE 1
s_static("POST", name="Method")
s_delim(" ", name='space-1')
s_string("/fromLogin", name='Request-URI') # 需要变异
s_delim(" ", name='space-2')
s_static('HTTP/1.1', name='HTTP-Version')
s_static("\r\n")
# LINE 2
s_static("Host", name="Host")
s_static(": ")
s_static("192.168.10.1", name="ip")
s_static("\r\n")
# LINE 3 对应 Content-Length: 400
s_static('Content-Length')
s_static(': ')
s_size('data', output_format='ascii', fuzzable=True) # size的值根据data部分的长度自动进行计算,同时对该字段进行fuzz
s_static('\r\n')
# ...
s_static('\r\n')
# 对应http请求数据
with s_block('data'):
s_static('login_name=&curTime=1581845487827&setLang=&setNoAutoLang=&login_n=admin&login_pass=')
s_string('123456', max_len=1024) # 需要变异,且最大长度为1024
s_static('&languageSel=1')
以下是最简单的一个会话的设置,包括设备的IP地址以及端口
session = Session(
target=Target(
connection=SocketConnection("192.168.10.1", 80, proto='tcp')
netmon=Remote_NetworkMonitor(host, port, proto='tcp')) # 服务可用性监控 非必须代码
),
)
如果需要fuzz多个请求,比如说,还要继续fuzz登录后的一些接口,还需要将之前定义的请求按照一定的先后顺序连接,比如说
session.connect(s_get('login')) # 默认前置节点为root
session.connect(s_get('login'), s_get('setsysemailsettings'), callback=add_auth_callback)
session.connect(s_get('login'),s_get('setsyslogsettings'), callback=add_auth_callback)
session.connect(s_get('login'),s_get('setschedulesettings'), callback=add_auth_callback)
上面的代码中等同于下面的图例
由于setsysemailsettings
、setsyslogsettings
、setschedulesettings
等请求需要在登录之后才可以正常使用,所以需要在login请求之后发生。而setsysemailsettings
、setsyslogsettings
和setschedulesettings
这几个请求之间则没有明确的先后关系。add_auth_callback
为自定义的回调函数,主要用于从login
请求中获取用于登录认证的信息如cookie
,然后将其设置于setsysemailsettings
、setsyslogsettings
、setschedulesettings
等请求中。
正如我们在0x10
节所说,通过当前设备是否在线,就是判断目标设备是否崩溃的方法之一。结合Step 2
中,我们已经引用的函数Remote_NetworkMonitor()
,其核心代码如下,此函数并非官方API,也可不添加
# 通过TCP全连接来判断目标端口是否在监听
if self.proto == "tcp" or self.proto == "ssl":
try:
self._sock.connect((self.host, self.port))
self.alive_flag = 1
except socket.error as e:
self.alive_flag = 0
最后只需要调用session.fuzz()
即可。运行我们编写好的脚本,正如0x22
最后所述,每次运行的日志数据将保存到当前工作目录下boofuzz-results目录中的SQLite数据库中,运行boo open
,会在26000端口开启一个Web服务器,控制和查看测试进度(某些版本,运行fuzz脚本,会自动打开26000端口,所以只需要打开如下页面即可)。
上图表明,第238个用例测试结果出现了异常。tips:在打开数据库监控状态的时候,如果提示26000端口已经占用,可以使用netstat -anp | grep 26000
找到进程,并杀死即可
具体的原因,就要靠我们自己进行后续的分析了。可以使用数据库查看软件,Kali自带 DB Browser for SQLite
,打开 boofuzz-results 目录下的相关文件可看到fuzz的日志
从数据库中可以看到,的确是按照我们的代码,对相关字段进行变异,从而达到fuzz的效果。进一步观察238个用例的前一个用例,找到发送的内容,重新发送,观察设备状态,看问题是否能够复现,最终确定漏洞是否存在。
IoT 设备在移动互联网时代,是一个兴起并且正在壮大的群体,其安全性不容忽视。对 IoT 设备的协议进行模糊测试,是一种有效的漏洞挖掘手段。boofuzz就是这样一个优秀的针对协议fuzz的工具,笔者深入浅出,从原理出发,介绍其架构组成,并最终进行实战演练,更多的细节说明,请参考相关用户手册,这个工具更多的功能等你发现!
附:fuzz脚本
#!/usr/bin/env python
# Designed for use with boofuzz v0.0.9
# coding=utf-8
from boofuzz import *
def main():
session = Session(
target=Target(
connection=SocketConnection("192.168.10.1", 80, proto='tcp')
),
)
s_initialize(name="Request")
with s_block("Request-Line"):
# LINE 1
s_static("POST", name="Method")
s_delim(" ", name='space-1')
s_string("/fromLogin", name='Request-URI') # variation
s_delim(" ", name='space-2')
s_static('HTTP/1.1', name='HTTP-Version')
s_static("\r\n")
# LINE 2
s_static("Host", name="Host")
s_static(": ")
s_static("192.168.10.1", name="ip")
s_static("\r\n")
# LINE 3 对应 Content-Length: 400
s_static('Content-Length')
s_static(': ')
s_size('data', output_format='ascii', fuzzable=True) # size的值根据data部分的长度自动进行计算,同时对该字段进行fuzz
s_static('\r\n')
# ...
s_static('\r\n')
# 对应http请求数据
with s_block('data'):
s_static('login_name=&curTime=1581845487827&setLang=&setNoAutoLang=&login_n=admin&login_pass=')
s_string('123456', max_len=1024)
s_static('&languageSel=1')
session.connect(s_get("Request"))
session.fuzz()
if __name__ == "__main__":
main()
https://www.jianshu.com/p/5993d5f003d3 ↩︎
Fuzzing Sucks! Introducing the sulley fuzzing framework. Pedram Amini & Aaron Portnoy. Black Hat US 2007 ↩︎ ↩︎
https://blog.csdn.net/u012397189/article/details/79758931 ↩︎
https://boofuzz.readthedocs.io/en/stable/user/quickstart.html# ↩︎
https://boofuzz.readthedocs.io/en/stable/index.html ↩︎