IoT 设备网络协议模糊测试工具boofuzz实战

0x10 物联网设备模糊测试

对于物联网设备,比如家用路由器模块,UPNP、HTTP server、Telnet都是用户经常接触的模块,通常也能够与外界交互,从而提供了入口。如果这些模块或者使用的协议存在漏洞,往往能够直接利用,达到远程攻击的效果。

IoT 固件分析往往涉及到二进制代码审计、静态扫描以及动态调试等,其中模糊测试作为一种重要的方法,能够给安全测试添砖加瓦。针对IoT设备的模糊测试主要有两类

  • 固件的二进制Fuzz测试,如AFL
  • 物联网协议Fuzz测试,如Peach、Sulley
    在这里插入图片描述

本次重点介绍Sulley的衍生项目——boofuzz的使用。对物联网设备的协议fuzz测试,不可丢失的一环是监控器,能够发现bug是监控器的作用所在。一般来说,大多数针对协议的fuzz测试,在每次发送变异报文后,通过ping或者其他协议监测对方是否在线从而判断设备是否出现异常。这种方法较为简单,但是粗粒度的监视器带来的后果是较高的误报率;另一种方式是ssh或者telnet登录目标设备,实时观察当前设备的状态,这种方式优点是随时捕捉异常信号,缺点是,针对不同设备可能要开发不同的监控API。所以当前诸多的协议Fuzz工具,大部分使用第一种,即通过目标设备是否可达来判断崩溃情况。


0x20 boofuzz 框架介绍

0x21 从Sulley说起

项目地址:https://github.com/Bit9/sulley。该项目基于Python2.7,也就是说,如果你要安装此工具,需要该Python环境的支持。

sulley 是一款模糊测试框架,可以使用 sulley 进行协议Fuzzing。Sulley(以作者的观点来看)所具有的能力超过了以前所发布的大多数模糊测试技术,包括商业工具和那些在公开领域中可用的工具。该框架的目标是不仅要简化数据的表示,而且还要简化数据的传输以及对目标的监视。Sulley从Monsters,Inc创建之后就被亲切的命名 ,因为它是模糊化的工具。现在sulley 已经停止更新,不再维护。1

大多数情况下,现代模糊器只专注于数据生成。Sulley不仅具有令人印象深刻的数据生成能力,而且还朝着更进一步的方向迈进了一步,其中还包括现代模糊器应提供的许多其他重要方面。Sulley监视网络并有条不紊地维护记录。Sulley可以检测和监视目标的运行状况,并可以使用多种方法将其恢复到已知的良好状态。Sulley对检测到的故障进行检测,跟踪和分类。Sulley可以并行进行测试,从而显着提高测试速度。Sulley可以自动确定哪些独特的测试用例序列会触发故障。Sulley可以自动完成所有这些工作。
IoT 设备网络协议模糊测试工具boofuzz实战_第1张图片
图 Sulley框架 2

上图是Sulley的整体框架图,来源于BlackHat。Sulley主要包括四大组件

  1. Data Generation 数据生成
  2. Session 会话管理
  3. Agents 代理
  4. Utilities 独立单元工具

其中,数据生成和会话管理是比较重要的两个模块。Sulley数据生成方式是基于generation-based的方式,需要对协议或者文件进行建模。数据生成的特点3

  • 一个数据报文由基元(Primitives)、块(Blocks组成)
  • 多个基元可以组成块,块可以相互嵌套
  • 在基元的基础上我们可以创建自定义的特殊的复杂基元(Legos,数据积木,暂且这么翻译吧),例如Email的地址,IP地址等。
  • 最后还有一些有用的工具,例如算length长度、校验和、加密模块等

会话模块,根据已经构建好的Request,利用 Pgraph 绘制, Pgraph 可以创建、修改和渲染图
IoT 设备网络协议模糊测试工具boofuzz实战_第2张图片
图 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还致力于扩展性。

0x22 boofuzz API

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即可。


0x30 Fuzz HTTP协议

0x31 boofuzz安装

Boofuzz需要Python。建议的安装要求pip。

Ubuntu: sudo apt-get install python-pip

请注意,源码是用python2写成的,高版本ubuntu自带python3,需要自己安装python2,所以应该用pip2

Ubuntu: pip2 install boofuzz

其他系统安装,请参考手册。官方手册中,提供了两个开源的示例 5

  • boofuzz-ftp
  • boofuzz-http

0x32 fuzz 某一路由器

在理解了 boofuzz (Sulley) 的架构之后,现在我们用boofuzz实战操作一波吧。

Created with Raphaël 2.2.0 开始 构造请求 设置会话信息 添加监控 开始fuzz 结束

Step1 根据API接口的数据包构造请求

比如,我们要对路由器的登录接口进行fuzz测试,首先需要使用Burpsuite设置代理,进行抓包。以下为某路由器的登录界面
IoT 设备网络协议模糊测试工具boofuzz实战_第3张图片
Burpsuite抓包结果
IoT 设备网络协议模糊测试工具boofuzz实战_第4张图片
现在需要根据报文,利用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')

Step2 设置会话信息

以下是最简单的一个会话的设置,包括设备的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)

上面的代码中等同于下面的图例
IoT 设备网络协议模糊测试工具boofuzz实战_第5张图片
由于setsysemailsettingssetsyslogsettingssetschedulesettings等请求需要在登录之后才可以正常使用,所以需要在login请求之后发生。而setsysemailsettingssetsyslogsettingssetschedulesettings这几个请求之间则没有明确的先后关系。add_auth_callback为自定义的回调函数,主要用于从login请求中获取用于登录认证的信息如cookie,然后将其设置于setsysemailsettingssetsyslogsettingssetschedulesettings等请求中。


Step3 添加监视器

正如我们在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

Step4 开始fuzz

最后只需要调用session.fuzz() 即可。运行我们编写好的脚本,正如0x22最后所述,每次运行的日志数据将保存到当前工作目录下boofuzz-results目录中的SQLite数据库中,运行boo open ,会在26000端口开启一个Web服务器,控制和查看测试进度(某些版本,运行fuzz脚本,会自动打开26000端口,所以只需要打开如下页面即可)。
IoT 设备网络协议模糊测试工具boofuzz实战_第6张图片
上图表明,第238个用例测试结果出现了异常。tips:在打开数据库监控状态的时候,如果提示26000端口已经占用,可以使用netstat -anp | grep 26000找到进程,并杀死即可

0x33 结果分析

具体的原因,就要靠我们自己进行后续的分析了。可以使用数据库查看软件,Kali自带 DB Browser for SQLite,打开 boofuzz-results 目录下的相关文件可看到fuzz的日志
IoT 设备网络协议模糊测试工具boofuzz实战_第7张图片
从数据库中可以看到,的确是按照我们的代码,对相关字段进行变异,从而达到fuzz的效果。进一步观察238个用例的前一个用例,找到发送的内容,重新发送,观察设备状态,看问题是否能够复现,最终确定漏洞是否存在。


0x40 总结

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()

  1. https://www.jianshu.com/p/5993d5f003d3 ↩︎

  2. Fuzzing Sucks! Introducing the sulley fuzzing framework. Pedram Amini & Aaron Portnoy. Black Hat US 2007 ↩︎ ↩︎

  3. https://blog.csdn.net/u012397189/article/details/79758931 ↩︎

  4. https://boofuzz.readthedocs.io/en/stable/user/quickstart.html# ↩︎

  5. https://boofuzz.readthedocs.io/en/stable/index.html ↩︎

你可能感兴趣的:(模糊测试)