A Journey into Synology NAS 系列二: findhostd服务分析
前言
上一篇文章主要对群晖NAS
进行了简单介绍,并给出了搭建群晖NAS
环境的方法。在前面的基础上,本篇文章将从局域网的视角出发,对群晖NAS
设备上开放的部分服务进行分析。由于篇幅原因,下面将重点对findhostd
服务进行分析,介绍对应的通信机制和协议格式,并分享在其中发现的部分安全问题。
服务探测
由于NAS
设备是网络可达的,假设我们与其处于同一个局域网中,首先对设备上开放的端口和服务进行探测。简单起见,这里直接通过netstat
命令进行查看,如下。
可以看到,除了一些常见的服务如smbd
、nginx
、minissdpd
和snmpd
等,还有一些自定义的服务如synovncrelayd
、iscsi_snapshot_comm_core
、synosnmpd
和findhostd
等。与常见服务相比,这些自定义的服务可能less tested and more vulnerable
,因此这里主要对这些自定义服务进行分析,包括findhostd
和iscsi_snapshot_comm_core
。
findhostd
服务分析
findhostd
服务主要负责和Synology Assistant
进行通信,而Synology Assistant
则用于在局域网内搜索、配置和管理对应的DiskStation
,比如安装DSM
系统、设置管理员账号/密码、设置设备获取IP
地址的方式,以及映射网络硬盘等。
通过抓包分析可知,Synology Assistant
和findhostd
之间主要通过9999/udp
端口(9998/udp
、9997/udp
)进行通信,一个简单的通信流程如下。具体地,Synology Assistant
首先发送一个广播query
数据包,之后findhostd
会同时发送一个广播包和单播包作为响应。在发现对应的设备后,Synology Assistant
可以进一步发送其他广播包如quickconf
、memory test
等,同样findhostd
会发送一个广播包和单播包作为响应。
抓取的部分数据包如上图右侧所示。可以看到,两者之间通过9999/udp
端口进行通信,且数据似乎以明文方式进行传输,其中包括mac
地址、序列号和型号等信息。
协议格式分析
为了了解具体的协议格式,需要对findhostd
(或Synology Assistant
客户端)进行逆向分析和调试。经过分析可知,消息开头部分是magic
(\x12\x34\x56\x78\x53\x59\x4e\x4f
),然后存在一大段与协议格式相关的数据grgfieldAttribs
,表明消息剩余部分的格式和含义。具体地,下图右侧中的每一行对应结构data_chunk
,其包含6个字段。其中,pkt_id
字段表明对应数据的含义,如数据包类型、用户名、mac
地址等;offset
字段对应将数据放到内部缓冲区的起始偏移;max_length
字段则表示对应数据的最大长度。
根据上述信息,可以将数据包按下图格式进行解析。具体地,消息开头部分为magic
(\x12\x34\x56\x78\x53\x59\x4e\x4f
),后面的部分由一系列的TLV
组成,TLV
分别对应pkt_id
、data_length
和data
。
进一步地,为了更方便地对数据包格式进行分析,编写了一个wireshark
协议解析插件syno_finder,便于在wireshark
中直接对数据包进行解析,效果如下图所示。
需要说明的是,在较新版本的Synology Assistant
和DSM
中,增加了对数据包加密的支持(因为其中可能会包含敏感信息)。对应地,存在两个magic
,分别用于标识明文消息和密文消息。同时,引入了几个新的pkt_id
,用于传递与加解密相关的参数。
1 | // magic |
协议fuzzing
在了解了协议的格式之后,为了测试协议解析代码的健壮性,很自然地会想到采用fuzz
的方式。这里采用Kitty
和Scapy
框架,来快速构建一个基于生成的黑盒fuzzer
。Scapy
是一个强大的交互式数据包处理程序,借助它可以方便快速地定义对应的协议格式,示例如下。
1 | class IDPacket(Packet): |
Kitty是一个开源、模块化且易于扩展的fuzz
框架,灵感来自于Sulley
和Peach Fuzzer
。基于前面定义的协议格式,借助Kitty
框架,可以快速地构建一个基于生成的黑盒fuzzer
。另外,考虑到findhostd
和Synology Assistant
之间的通信机制,可以同时对两端进行fuzz
。
1 | host = '<broadcast>' |
此外,基于前面定义好的协议格式,也可以实现一个简易的Synology Assistant
客户端。
1 | class DSAssistantClient: |
安全问题
密码泄露
前面提到,pkt_id
字段表明对应数据的含义,如数据包类型、用户名、mac
地址等。其中,pkt_id
为0x1
时对应的值表示整个数据包的类型,常见的数据包类型如下。其中,netsetting
、quickconf
和memory test
数据包中包含加密后的管理员密码信息,对应的pkt_id
为0x2a
。
以quickconf
数据包为例,如上图所示。可以看到,pkt_id
为0x1
时对应的值为0x4
,同时pkt_id
为0x2a
时对应的内容为BnvPxUcU5P1nE01UG07BTUen1XPPKPZX
。通过逆向分析可知,函数MatrixDecode()
用于对加密后的密码进行解密。因此,可以很容易地获取到管理员的明文密码。
1 | sudo chroot . ./call_export_func -d BnvPxUcU5P1nE01UG07BTUen1XPPKPZX |
由于Synology Assistant
和findhostd
之间以广播的方式进行通信,且数据包以明文形式进行传输,在某些情形下,通过监听广播数据包,局域网内的用户可以很容易地获取到管理员的明文密码。
密码窃取
在对findhostd
进行fuzz
的过程中,注意到Synology Assistant
中显示的DiskStation
状态变为了"Not configured"
。难道是某些畸形数据包对DiskStation
进行了重置?经过分析后发现,是由于某些数据包欺骗了Synology Assistant
:DiskStation
是正常的,而Synology Assistant
却认为其处于未配置状态。
通常情况下,管理员会选择通过Synology Assistant
对设备进行重新配置,并设置之前用过的用户名和密码。此时,由于Synology Assistant
和findhostd
之间以广播的方式进行通信,且数据包以明文形式进行传输,故密码泄露问题又出现了。因此,在某些情形下,通过发送特定的广播数据包,局域网内的用户可以欺骗管理员对DiskStation
进行”重新配置”,通过监听局域网内的广播数据包,从而窃取管理员的明文密码。另外,即使Synology Assistant
和DSM
版本都支持通信加密,由于向下兼容性,这种方式针对最新的版本仍然适用。
null byte off-by-one
这个问题同样也和Synology Assistant
有关。在fuzz
的过程中,发现Synology Assistant
中显示的一些内容比较奇怪。其中,"%n"
、"%x"
和"%p"
等是针对string
类型预置的一些fuzz
元素。注意到,在"Server name"
中显示的内容除了"%n"
之外,尾部还有一些额外的内容如"00:11:32:8Fxxx"
,这些多余的内容对应的是"MAC address"
。正常情况下,"MAC address"
对应的内容不会显示到"Server name"
中。
通过对6.1-15030
版本的DSAssistant.exe
进行分析和调试,函数sub_1272E10()
负责对string
类型的数据进行处理,将其从接收的数据包中拷贝到对应的内部缓冲区。前面提到过,针对每个pkt_id
项,都有一个对应的offset
字段和max_length
字段。当对应数据长度的大小正好为max_length
时,额外的'\x00'
在(1)
处被追加到缓冲区末尾,而此时该'\x00'
其实是写入了邻近缓冲区的起始处,从而造成null byte off-by-one
。
1 | size_t __cdecl sub_1272E10(int a1, _BYTE *a2, int a3, int a4, size_t a5, int a6, int a7) |
The
_snprintf()
function formats and stores count or fewer characters and values (including a terminating null character that is always appended unless count is zero or the formatted string length is greater than or equal to count characters) in buffer. // WindowsThe functions
snprintf()
andvsnprintf()
write at most size bytes (including the terminating null byte (‘\0’)) to str. // Linux
因此,对于某些在内部缓冲区中处于邻近的pkt_id
(如0x5b
和0x5c
),通过构造特殊的数据包,可以使得前一项内容末尾的'\x00'
被下一项内容覆盖,从而可能会泄露邻近缓冲区中的内容。
1 | pkt_id offset max_len |
小结
本文从局域网的视角出发,对群晖NAS
设备上的findhostd
服务进行了分析,包括Synology Assistant
与findhostd
之间的通信机制、syno_finder
协议格式的解析、协议fuzzing
等。最后,分享了在其中发现的部分问题。
相关链接
- Create Wireshark Dissector in Lua
- syno_finder
- Kitty Fuzzing Framework
- Synology-SA-19:38 Synology Assistant
本文首发于安全客,文章链接:https://www.anquanke.com/post/id/251909