针对海思 hi3520d DVR/NVR 设备的 PoC 漏洞利用与漏洞披露。演示了通过 Web 界面实现的远程代码执行(RCE)、后门凭据以及缓冲区溢出分析。
= HiSilicon DVR 破解 Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Contents :image_width: 100%
[abstract] 本报告披露了使用海思 hi3520d 及类似片上系统(SoC)的 DVR/NVR 设备中存在的严重漏洞(含概念验证(PoC)代码)。利用这些漏洞仅通过 Web 界面即可实现未授权的远程代码执行(RCE),从而完全控制被利用的设备。由于缺乏升级固件,不建议使用这些设备。 已于 2016 年 12 月之前联系厂商,但仍未收到回复。本披露发布日期为 2017 年 2 月。
== 前言
几年前,我在 eBay 上买了一台廉价的中国 DVR 设备。设备的启动徽标写着:“SECULINK - 安全监控”。作为一名 IT 安全爱好者,我决定仔细看看该设备,看看这个安全监控服务到底有多“安全”。谷歌搜索相关话题时,我找到了一些有趣的资料,但深入挖掘后,发现了关于该设备更有趣、更严重的问题(0-day)。
让我们从头开始看看整个入侵会话。(新的、自主研发的成果会标注出来,旧的、已知的也一样。)
== 探索 DVR
首先,我们应当了解官方用户界面,然后深入挖掘,也许尝试获取固件。找到漏洞的机会随着固件的获取而增加。
=== DVR 初览
用于测试的 DVR 设备品牌为 “Seculink”。
image::./seculink_device.png[Seculink DVR device]
可用的物理接口:
官方用户界面:
直接访问的设置界面受用户认证(用户名、密码)限制。默认超级用户为 'admin',默认密码为空。
设置强密码后,用户可能会觉得自己的摄像头画面不会被他人访问。人们经常将 DVR 设备的 Web 端口(tcp/80)从其安全的 LAN 转发到 WAN 侧,以便从外部访问 DVR 视频流(我们可以通过适当的 Shodan 搜索来验证这一点 ;) )。
=== 获取固件
获取固件的方法可能有很多:
虽然后一种(下载)方法在这里有效,而且是最容易的,但让我们先尝试第一种方法,因为它还能提供有关设备的其他信息。
=== 服务扫描
让我们对 DVR 进行一次全端口扫描。注意,(默认以 root 身份运行时)SYN 扫描非常慢,因为丢包,但完整的 TCP connect 扫描在几分钟内完成。
Nmap scan report for dvr.lan (192.168.88.127) Host is up (0.028s latency). Not shown: 65529 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http uc-httpd 1.0.0 554/tcp open rtsp LuxVision or Vacron DVR rtspd 9527/tcp open unknown 34567/tcp open dhanalakshmi? 34599/tcp open unknown MAC Address: 00:12:12:15:B3:E7 (Plus ) Service Info: Host: LocalHost; Device: webcam
总结和手动测试:
注意,打开 rtsp 流也需要凭据。
这里我们应该指出,该设备可能运行某种类似 Linux 的系统。
连接到 9527/tcp(通过原始 netcat)显示应用程序控制台,其中包含日志消息和一个登录提示符。使用任何已定义的应用程序凭据登录均有效。在提示符后输入 help 会给出控制台命令的简短说明。命令 shell 似乎是最有趣的。是的,它会给设备一个 root shell。;)
注意,这显然是一个严重的安全问题,因为任何(低权限的)应用程序用户都不应该自动获得设备的 root shell。
=== root shell
在 root shell 中探索设备(例如通过 dmesg)可以清楚地看到,DVR 运行的是 Linux 内核(版本 3.0.8),它有一个 ARMv7 CPU,SoC 型号是 hi3520d。
从正在运行的进程列表(ps)可以清楚地看到,DVR 应用程序是 /var/Sofia,除了 nmap 检测到的上述 tcp 端口外,它还在监听 34568/udp 和 34569/udp(netstat -nlup)。
从挂载的磁盘列表(mount 命令)可以清楚地看到,固件镜像位于 /dev/mtdblockX 设备中(其中 X=0,1,2,3,4,5)。
固件很小,因此受限,所以如果我们想向设备复制文件或从设备复制文件,就得发挥创意。幸运的是,支持 NFS,所以在我们的台式机上设置一个 NFS 服务器并从 DVR 挂载它,问题就解决了:
现在获取固件就很简单了:
我们可以得到这些文件(不仅仅是原始镜像):
=== telnet 接口
要通过 telnet 接口(端口 23/tcp)访问设备,我们可能需要一些操作系统凭据。查看 /etc/passwd,我们有 root 用户的密码哈希:
注意,除了 root 之外没有其他用户,所有内容都以完全权限运行。(因此,如果有人以某种方式闯入设备,就没有任何屏障,攻击者会立即获得全部权限。)
假设一个六位字母数字(小写)密码,hashcat 很快就能破解上述弱 DES 哈希:
$ ./hashcat64.bin -a3 -m1500 absxcfbgXtb3o -1 ?l?d ?1?1?1?1?1?1
absxcfbgXtb3o:xc3511
Session..........: hashcat Status...........: Cracked Hash.Type........: descrypt, DES (Unix), Traditional DES Hash.Target......: absxcfbgXtb3o Time.Started.....: Sun Sep 3 03:25:07 2017 (2 mins, 29 secs) Time.Estimated...: Sun Sep 3 03:27:36 2017 (0 secs) Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.Dev.#1.....: 815.9 kH/s (203.13ms) Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 121360384/2176782336 (5.58%) Rejected.........: 0/121360384 (0.00%) Restore.Point....: 93440/1679616 (5.56%) Candidates.#1....: sa8711 -> h86ani HWMon.Dev.#1.....: N/A
因此,使用用户 root 和密码 xc3511 可以通过端口 23/tcp 上的 telnet 接口登录。这个硬编码的 root 账户可通过无法关闭的 telnet 接口访问,显然是一个后门。
这些结果在我们研究之前几乎已由其他人获得,但以下内容完全是新的。
== 固件逆向
探索固件后发现,二进制文件 /var/Sofia 是主应用程序,它实现了除视频处理和其他功能之外的所有接口。所以这个二进制文件对我们来说似乎是最有趣的。
不幸的是,它是(静态链接且)剥离的,这使得静态分析更加困难:
因此,除了静态分析(使用 radare2 或 IDA)之外,动态分析应该非常有帮助。
=== 远程 gdb
对于动态分析,将 GNU 项目调试器(GDB)附加到远程 /var/Sofia 应用程序应该是有利的。推荐的方法是在远程设备上运行(并附加)gdbserver,然后从本地机器将 gdb 连接到它。
当然,我们需要一个为合适的 ARM 架构编译的(最好是静态的)gdbserver。为了构建它,我们可以使用 https://www.uclibc.org/[µClibc],这是嵌入式系统(如我们的 DVR)推荐的 C 库。可用的构建是动态构建,这在我们的 DVR 上会有问题,所以我们应该自己制作自定义静态构建。有一个很好的构建环境叫做 https://buildroot.org/[Buildroot],它使构建工作开箱即用(使用 make menuconfig 选择所需的应用程序(例如 gdb),不要忘记选择静态库,然后运行 make)。
经过短暂的构建时间(约 10-15 分钟),所有必要的工具都应该可用。静态二进制文件可以通过前面提到的 NFS 方法传输到设备。注意,包含 Sofia 二进制文件的 /var 目录是一个 ramfs,因此它不会在重启后持久化。如果我们想(几乎)永久地传输二进制文件,包含配置文件的读写分区 /mnt/mtd 应该是一个合适的目标。如果你也构建了 openssh 包,那么 scp 将可用,这使文件传输更加容易。
现在固件已经准备好进行一些逆向。远程附加 gdbserver 现在可以工作了(通过 ps 很容易获得 Sofia 进程的 PID):
从本地机器连接:
注意,建议使用某些 GDB 扩展(如 http://gef.readthedocs.io/en/master/[GEF])。如果出于某种原因暂停应用程序不起作用(使用 C-c),向 Sofia 进程发送 TRAP 信号(通过 kill -TRAP 610)应该会使其暂停。
=== 检查认证过程
静态分析推荐的工具显然是 Hex-Ray 的 https://www.hex-rays.com/products/ida/[IDA Pro]。不幸的是,它不便宜,但比任何其他工具都好。
初始自动分析后有 15,000+ 个函数,但在 IDA 中找到认证函数只需片刻(使用简单的 Python 脚本)。下面的 https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] 代码片段搜索所有同时引用与 “Users” 和 “Password” 相关内容的函数:
结果只有一个函数:sub_2D857C。快速分析该函数确认这应该是认证函数。
在从配置中获取用户的密码哈希之前,有一个针对硬编码字符串的明文密码的初始检查。如果通过,则授予认证。这是应用程序中的一个丑陋的后门。通用密码是:I0TO5Wv9。
有了这个密码,我们可以以任何用户(例如 admin)的身份访问应用程序中的任何内容。例如,获取视频流:
或者在应用程序控制台(9527/tcp)上获得一个 root shell 也可以:
$ nc 192.168.88.127 9527 nc: using stream socket
认证算法中另一个有趣的结果是:在某些情况下,认证函数不仅接受密码,还接受哈希。打开 rtsp 视频流不仅可以使用密码,还可以使用存储在 /mnt/mtd/Config/Account1 中的哈希。例如,tlJwpbo6 是空密码的哈希(另见下一节),所以```
cvlc 'rtsp://192.168.88.127:554/user=admin&password=&channel=1&stream=0.sdp'
cvlc 'rtsp://192.168.88.127:554/user=admin&password=tlJwpbo6&channel=1&stream=0.sdp'
同样有效。
=== 密码哈希函数
对认证函数进行深入静态分析的另一个结果:密码哈希函数是 `sub_3DD5E4`。它基本上是 MD5 加上一些奇怪的变换。已将其逆向并用 Python 实现:
[source,python]
----
import hashlib
def sofia_hash(msg):
h = ""
m = hashlib.md5()
m.update(msg)
msg_md5 = m.digest()
for i in range(8):
n = (ord(msg_md5[2*i]) + ord(msg_md5[2*i+1])) % 0x3e
if n > 9:
if n > 35:
n += 61
else:
n += 55
else:
n += 0x30
h += chr(n)
return h
----