[DDCTF 2019]homebrew event loop (2)

https://blog.cindemor.com/post/ctf-web-16.html
分析一下:

# flag获取函数def FLAG() # 以下三个函数负责对参数进行解析。 # 1. 添加log,并将参数加入队列def trigger_event(event) # 2. 工具函数,获取prefix与postfix之间的值 def get_mid_str(haystack, prefix, postfix=None): # 3. 从队列中取出函数,并分析后,进行执行。(稍后进行详细分析) def execute_event_loop() # 网站入口点 def entry_point() # 页面渲染,三个页面: index/shop/resetdef view_handler() # 下载源码 def index_handler(args) # 增加钻石 def buy_handler(args) # 计算价钱,进行减钱 def consume_point_function(args) # 输出flagdef show_flag_function(args) def get_flag_handler(args)

有这么两个跟 flag 有关的函数:

def show_flag_function(args): flag = args[0] #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. return \'You naughty boy! ;) <br />\' def get_flag_handler(args): if session[\'num_items\'] >= 5: trigger_event(\'func:show_flag;\' + FLAG()) trigger_event(\'action:view;index\')

可以看到show_flag_function()无法直接展示出 flag,先看看get_flag_handler()中用到的trigger_event()函数:

def trigger_event(event): session[\'log\'].append(event) if len(session[\'log\']) > 5: session[\'log\'] = session[\'log\'][-5:] if type(event) == type([]): request.event_queue += event else:

这个函数往 session 里写了日志,而这个日志里就有 flag,并且 flask 的 session 是可以被解密的。只要后台成功设置了这个 session 我们就有机会获得 flag。

但若想正确调用show_flag_function(),必须满足session[\'num_items\'] >= 5。

购买num_items需要花费points,而我们只有 3 个points,如何获得 5 个num_items?

先看看购买的机制:

def buy_handler(args): num_items = int(args[0]) if num_items <= 0: return \'invalid number({}) of diamonds to buy<br />\'.format(args[0]) session[\'num_items\'] += num_items trigger_event([\'func:consume_point;{}\'.format(num_items), \'action:view;index\']) def consume_point_function(args): point_to_consume = int(args[0]) if session[\'points\'] < point_to_consume: raise RollBackException() session[\'points\'] -= point_to_consume

buy_handler()这个函数会先把num_items的数目给你加上去,然后再执行consume_point_function(),若points不够consume_point_function()会把num_items的数目再扣回去。
其实就是先给了货后,无法扣款,然后货被拿跑了

那么我们只要赶在货被抢回来之前,先执行get_flag_handler()即可。
函数trigger_event()维护了一个命令执行的队列,只要让get_flag_handler()赶在consume_point_function()之前进入队列即可。看看最关键的执行函数:

[DDCTF 2019]homebrew event loop

仔细分析execute_event_loop,会发现里面有一个eval函数,而且是可控的!

利用eval()可以导致任意命令执行,使用注释符可以 bypass 掉后面的拼接部分。

若让eval()去执行trigger_event(),并且在后面跟两个命令作为参数,分别是buy和get_flag,那么buy和get_flag便先后进入队列。

根据顺序会先执行buy_handler(),此时consume_point进入队列,排在get_flag之后,我们的目标达成。

所以最终 Payload 如下:

action:trigger_event%23;action:buy;5%23action:get_flag;

要注意执行buy_handler函数后事件列表末尾会加入consume_point_function函数,在最后执行此函数时校验会失败,抛出RollBackException()异常,但是不会影响session的返回

[DDCTF 2019]homebrew event loop

[DDCTF 2019]homebrew event loop

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzfspj.html