# app.py from flask import Flask, request, jsonify from CONFIG import Config
app = Flask(__name__)
@app.route('/update_config', methods=['POST']) def update_config(): data = request.json for key, value in data.items(): Config.set_config(key, value) return jsonify({"status": "success", "config": data})
@app.route('/login',methods=['GET','POST']) def login(): if request.method == 'POST': username=request.form.get('username') password=request.form.get('password') user = next((user for user in registered_users if user.username == username and user.password == password), None) if user: session['username'] = user.username session['password'] = user.password return redirect(url_for('play')) else: return "Invalid login" return redirect(url_for('play')) return render_template("login.html")
@app.route('/register',methods=['GET','POST']) def register(): if request.method == 'POST': try: if waf(request.data): return "fuck payload!Hacker!!!" data=json.loads(request.data) if "username" not in data or "password" not in data: return "连用户名密码都没有你注册啥呢" user=hhh() merge(data,user) registered_users.append(user) except Exception as e: return "泰酷辣,没有注册成功捏" return redirect(url_for('login')) else: return render_template("register.html")
@app.route('/flag',methods=['GET']) def flag(): user = next((user for user in registered_users if user.username ==session['username'] and user.password == session['password']), None) if user: if user.isvip: data=request.args.get('num') if data: if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10: flag = os.environ.get('geek_flag') return render_template('flag.html',flag=flag) else: return "你的数字不对哦!" else: return "I need a num!!!" else: return render_template_string('这种神功你不充VIP也想学?<p><img src="{{url_for(\'static\',filename=\'weixin.png\')}}">要不v我50,我送你一个VIP吧,嘻嘻</p>') else: return "先登录去"
def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)
if __name__ == '__main__': app.run(host="0.0.0.0",port="8888")
审计一下,很明显有 merge函数,这就是上面一直在学习的递归合并函数。 我们通过 /register路由能够注册账户,/login登录,关键点在 /flag路由:当 user.isvip判断为true时,再经过一层 int绕过,即可在页面返回 flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
@app.route('/flag',methods=['GET']) def flag(): user = next((user for user in registered_users if user.username ==session['username'] and user.password == session['password']), None) if user: if user.isvip: data=request.args.get('num') if data: if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10: flag = os.environ.get('geek_flag') return render_template('flag.html',flag=flag) else: return "你的数字不对哦!" else: return "I need a num!!!" else: return render_template_string('这种神功你不充VIP也想学?<p><img src="{{url_for(\'static\',filename=\'weixin.png\')}}">要不v我50,我送你一个VIP吧,嘻嘻</p>') else: return "先登录去"
class User(): def __init__(self): self.username="" self.password="" self.isvip=False
class hhh(User): def __init__(self): self.username="" self.password=""
对程序进行断点调试,user的所属类的确是 hhh(),所以污染并不需要找父类
攻击链构造 理清楚逻辑,可以大胆构造 Payload 了,不过这里还需要绕过 WAF
1 2 3 4
def waf(data): data=str(data) if "isvip" in data or "_static_folder" in data or "os" in data or "loader" in data or "defaults" in data or "kwdefaults" in data: return True
@app.route("/admin", methods=['GET', 'POST']) async def admin(request): if request.ctx.session.get('admin') == True: key = request.json['key'] value = request.json['value'] if key and value and type(key) is str and '_.' not in key: pollute = Pollute() pydash.set_(pollute, key, value) return text("success") else: return text("forbidden") return text("forbidden")
if __name__ == '__main__': app.run(host='0.0.0.0')
版本 5.1.2,padash.set_允许通过路径的方式修改嵌套对象或类属性
1 2 3 4 5 6 7 8 9 10 11 12
# pydash==5.1.2 async def admin(request): if request.ctx.session.get('admin') == True: key = request.json['key'] value = request.json['value'] if key and value and type(key) is str and '_.' not in key: pollute = Pollute() pydash.set_(pollute, key, value) return text("success") else: return text("forbidden") return text("forbidden")
def setval(name:str, path:str, value:str)-> Optional[bool]: if name.find("__")>=0: return False for word in __forbidden_name__: if name==word: return False for word in __forbidden_path__: if path.find(word)>=0: return False obj=globals()[name] try: pydash.set_(obj,path,value) except: return False return True
@bottle.post('/setValue') def set_value(): name = bottle.request.query.get('name') path=bottle.request.json.get('path') if not isinstance(path,str): return "no" if len(name)>6 or len(path)>32: return "no" value=bottle.request.json.get('value') return "yes" if setval(name, path, value) else "no"
@bottle.get('/render') def render_template(): path=bottle.request.query.get('path') if len(path)>10: return "hacker" blacklist=["{","}",".","%","<",">","_"] for c in path: if c in blacklist: return "hacker" return bottle.template(path) bottle.run(host='0.0.0.0', port=8000)
def template(*args, **kwargs): """ Get a rendered template as a string iterator. You can use a name, a filename or a template string as first parameter. Template rendering arguments can be passed as dictionaries or directly (as keyword arguments). """ tpl = args[0] if args else None for dictarg in args[1:]: kwargs.update(dictarg) adapter = kwargs.pop('template_adapter', SimpleTemplate) lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) tplid = (id(lookup), tpl) if tplid not in TEMPLATES or DEBUG: settings = kwargs.pop('template_settings', {}) if isinstance(tpl, adapter): TEMPLATES[tplid] = tpl if settings: TEMPLATES[tplid].prepare(**settings) elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) else: TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) if not TEMPLATES[tplid]: abort(500, 'Template (%s) not found' % tpl) return TEMPLATES[tplid].render(kwargs)
adapter = kwargs.pop('template_adapter', SimpleTemplate) ... elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) else: TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
class BaseTemplate(object): def __init__(self, source=None, name=None, lookup=None, encoding='utf8', **settings): self.name = name self.source = source.read() if hasattr(source, 'read') else source self.filename = source.filename if hasattr(source, 'filename') else None self.lookup = [os.path.abspath(x) for x in lookup] if lookup else [] self.encoding = encoding self.settings = self.settings.copy() # Copy from class variable self.settings.update(settings) # Apply if not self.source and self.name: self.filename = self.search(self.name, self.lookup) if not self.filename: raise TemplateError('Template %s not found.' % repr(name)) if not self.source and not self.filename: raise TemplateError('No template specified.') self.prepare(**self.settings)