buuctf-web-homebrew event loop

源码太多就不贴了

buuctf-web-homebrew event loop_第1张图片
四个按钮
一个是看源码
一个是进商店买
一个是重置
一个是返回index
然后url的格式都是这样的
?action:index;True%23False
?action:view;shop
?action:view;reset
?action:view;index

分析一下流程

首先登陆的时候
http://url_prefix/d5afe1f66147e857/
会先进入这个路由 执行entry_point()

@app.route(url_prefix+'/')
def entry_point():
    querystring = urllib.unquote(request.query_string)
    request.event_queue = []
    if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
        querystring = 'action:index;False#False'
    if 'num_items' not in session:
        session['num_items'] = 0
        session['points'] = 3
        session['log'] = []
    request.prev_session = dict(session)
    trigger_event(querystring)
    return execute_event_loop()

大概就是先url解码得querystring
之后对querystring有个判断
然后设置session
session有三项分别为num_items(购买数量),points(点数),log(访问记录)
之后执行
trigger_event(querystring)
return execute_event_loop()

先看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:
        request.event_queue.append(event)

这里面执行的操作就是将event添加到session[‘log’]中并保留最后的五个
并将这些event添加到request.event_queue中

之后看execute_event_loop()

def execute_event_loop():
    valid_event_chars = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')):
            continue
        for c in event:
            if c not in valid_event_chars:
                break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None:
                    resp = ''
                resp += 'ERROR! All transactions have been cancelled. 
' resp += 'Go back to index.html
' session['num_items'] = request.prev_session['num_items'] session['points'] = request.prev_session['points'] break except Exception, e: if resp is None: resp = '' # resp += str(e) # only for debugging continue if ret_val is not None: if resp is None: resp = ret_val else: resp += ret_val if resp is None or resp == '': resp = ('404 NOT FOUND', 404) session.modified = True return resp

大概操作就是从request.event_queue中逐个取出event检测格式,并执行
重点在else里面

else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)

首先判断event第一个字符是不是a
之后调用了一个函数

def get_mid_str(haystack, prefix, postfix=None):
    haystack = haystack[haystack.find(prefix)+len(prefix):]
    if postfix is not None:
        haystack = haystack[:haystack.find(postfix)]
    return haystack

就是对haystack以prefix, postfix进行分割
但是里面用的是find,也就是说只会查找第一次出现的位置

action是对event分割,取第一个:和;中间的部分
args是先对event分割,取第一个action+:之后的部分,然后再以#分割
之后用了eval执行(如果is_action,添加_handler在后面,否则添加_function在后面)
event_handler = eval( action + (’_ handler’ if is_action else '_ function '))
最后将参数传进去ret_val = event_handler(args)

我们以访问商店event为例:
event=action:view;shop
is_action=true
action=view
args=[‘shop’]
event_handler=eval(view_ handler)
ret_val=view_ handler(shop)

验证一下
buuctf-web-homebrew event loop_第2张图片buuctf-web-homebrew event loop_第3张图片
好了现在我们大概了解了后端执行的流程
那么怎么获得flag呢
发现了这三个函数

def FLAG():
    return '*********************'  # censored
    
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! ;) 
' def get_flag_handler(args): if session['num_items'] >= 5: # show_flag_function has been disabled, no worries trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index')

第一,三个能返回flag
但第一个return了flag却没有输出,只是return回来了
而第三个会调用前两个,触发trigger_event将flag写入log记录项中,而log的内容可以通过破解session获得
但是条件是session[‘num_items’] >= 5:

接下来的目标就是如何让get_flag_handler执行,同时num_items>=5

也就是需要先让num_items>=5,之后让get_flag_handler执行,最后解密session得flag
先看看怎么能让num_items>=5

def buy_handler(args):
    num_items = int(args[0])
    if num_items <= 0:
        return 'invalid number({}) of diamonds to buy
'.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和consume_point_function是分开的
也就是我们先提交自己的购买个数给buy_handler
buy_handler通过trigger_event将事件consume_point_function加入了事件的列表
所以如果要买一个log里应该是这样:
[‘action:buy;1’,[[‘func:consume_point;1’.‘action:view;index’]]]
于是我们买一个
buuctf-web-homebrew event loop_第4张图片
抓包
buuctf-web-homebrew event loop_第5张图片
破解一下session
破解session工具
buuctf-web-homebrew event loop_第6张图片注意一点
'action:buy;1’是我们购买时添加进事件中的
[‘func:consume_point;1’.‘action:view;index’]这个是buy_handler执行到
trigger_event([‘func:consume_point;{}’.format(num_items), ‘action:view;index’])添加进log中的
于是有了漏洞
因为trigger_event() 先将事件添加到log
之后真正执行是execute_event_loop()
也就是说如果只要在将buy_handler添加到记录的同时加入get_flag_handler
执行的log就会变成
[‘action:buy;5’,‘action:get_flag’]
之后buy_handler执行,添加事件consume_point
[‘action:buy;5’,‘action:get_flag’,[[‘func:consume_point;5’.‘action:view;index’]]]
由于get_flag在consume_point前执行
这时候num_items可以等于5
绕过了
那么如何将get_flag添加进log
正好可以参考这里
buuctf-web-homebrew event loop_第7张图片
用trigger_event([‘action:buy;5’,‘action:get_flag;’])

然后就是怎么让trigger_event执行的问题了

整个代码中能根据输入的url调用函数的地方只有刚刚的eval
于是我们回到else

else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)

我们现在已经知道
action是函数
args是函数的参数
所以只要想办法让
args=[‘action:buy;5’,‘action:get_flag;’]
action=trigger_event
就可以了
我们知道#可以分割args
buuctf-web-homebrew event loop_第8张图片
但会这时的action=trigger_event_handler
args也多出了第一个空元素
最后经过尝试发现这样可以
buuctf-web-homebrew event loop_第9张图片
这时的action=trigger_event#_handler,执行时#将后边的注释了
最后的payload是

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

#要编码为%23
buuctf-web-homebrew event loop_第10张图片
访问用bp抓包
buuctf-web-homebrew event loop_第11张图片
解密响应包的session

buuctf-web-homebrew event loop_第12张图片
flag到手

b

你可能感兴趣的:(buuctf)