[XCTF 抗疫赛] webtmp
python的反序列化漏洞,看opcode手搓pickle来完成,萌新头一次在真正的比赛上打出了输出呜呜呜
源码
import base64
import io
import sys
import pickle
from flask import Flask, Response, render_template, request
import secret
app = Flask(__name__)
class Animal:
def __init__(self, name, category):
self.name = name
self.category = category
def __repr__(self):
return f'Animal(name={self.name!r}, category={self.category!r})'
def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
def read(filename, encoding='utf-8'):
with open(filename, 'r', encoding=encoding) as fin:
return fin.read()
@app.route('/', methods=['GET', 'POST'])
def index():
if request.args.get('source'):
return Response(read(__file__), mimetype='text/plain')
if request.method == 'POST':
try:
pickle_data = request.form.get('data')
if b'R' in base64.b64decode(pickle_data):
return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
else:
result = restricted_loads(base64.b64decode(pickle_data))
if type(result) is not Animal:
return 'Are you sure that is an animal???'
correct = (result == Animal(secret.name, secret.category))
return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)
except Exception as e:
print(repr(e))
return "Something wrong"
sample_obj = Animal('一给我哩giaogiao', 'Giao')
pickle_data = base64.b64encode(pickle.dumps(sample_obj)).decode()
return render_template('unpickle_page.html', sample_obj=sample_obj, pickle_data=pickle_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
函数不多,类定义的都是一些方便使用的魔法方法,强化了pickle反序列化的限制,重写find_class函数,只允许使用main里面的方法,然后也很明显的给了一个read函数,看一眼就知道是利用这个函数读文件读flag。
pickle反序列化时的命令执行都是调用find_class函数返回一个可执行对象,而find_class的本质其实是getattr(module, name),这里重写了find_class把可调用的对象限制在了main中
而通常使用pickle执行命令使用的opcode R被过滤了,但是既然思路是这个样子,就肯定有别的方法去执行命令,然后就去看opcode,看opcode的详细说明,发现创建一个类实例的opcode同样是调用find_class方法实现的,也就意味着创建类实例的opcode同样可以执行命令。