美文网首页
homebrew event loop

homebrew event loop

作者: Yix1a | 来源:发表于2019-05-05 15:55 被阅读0次
    • 进入后可以看到代码
    • 进行代码审计
    # -*- encoding: utf-8 -*-
    # written in python 2.7
    __author__ = 'garzon'  #pycharm添加的代码作者
    
    from flask import Flask, session, request, Response
    import urllib
    
    app = Flask(__name__)
    app.secret_key = '*********************' # censored(审查)
    url_prefix = '/d5af31f96147e657'
    
    def FLAG():
        return 'FLAG_is_here_but_i_wont_show_you'  # censored(审查)
    #该函数讲事件即网页事件的循环固定为最后5个,一直循环,以session【log】的值为事件循环的存储和顺序。 
    def trigger_event(event):   
        session['log'].append(event)
        if len(session['log']) > 5: session['log'] = session['log'][-5:]  # 如果session['log']列表的后五个成员。
        if type(event) == type([]):   
            request.event_queue += event
        else:
            request.event_queue.append(event)   #event_queue列表中添加一个'action:trigger_event#;action:buy;10#action:get_flag;#a:show_flag;1'
    #该函数按传入的参数值分割字符串
    def get_mid_str(haystack, prefix, postfix=None):
        haystack = haystack[haystack.find(prefix)+len(prefix):]   #haystack中发现prefix,并获得位置,加上prefix结束的地方,用:截取,得到prefix之后的字符串。
        if postfix is not None:
            haystack = haystack[:haystack.find(postfix)]    #如果postfix不是none 则,去掉结尾的postfix
        return haystack
        
    class RollBackException: pass
    # 该函数用来执行事件循环,持续的按trigger_event函数排好的顺序运行。
    def execute_event_loop():
        valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
        resp = None
        while len(request.event_queue) > 0:     #action:trigger_event#;action:buy;10#action:get_flag;#a:show_flag;1
            event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" #'action:trigger_event#;action:buy;10#action:get_flag;#a:show_flag;1'
            request.event_queue = request.event_queue[1:]  #取第一位之外的所有事件,循环递进的一种方法。
            if not event.startswith(('action:', 'func:')): continue #event字符串里面必须有action或func才能接着执行
            for c in event:
                if c not in valid_event_chars: break   #event字符串必须是26英文字母大小写数字 _ : ; #
            else:
                is_action = event[0] == 'a'   
                action = get_mid_str(event, ':', ';')  #   action = action:trigger_event#
                args = get_mid_str(event, action+';').split('#')  #args= ['action:buy;10', 'action:get_flag;', 'a:show_flag;1']
                try:
                    event_handler = eval(action + ('_handler' if is_action else '_function')) #eval执行相应的函数。: ; 中间的那个决定函数名
                    ret_val = event_handler(args) #向函数中传入值。
                except RollBackException:
                    if resp is None: resp = ''
                    resp += 'ERROR! All transactions have been cancelled. <br />'
                    resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
                    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
    
    @app.route(url_prefix+'/')
    # 该函数是flask route的初始函数,开启route的url网页自动执行的就是这个
    def entry_point():                        # action:trigger_event#;action:buy;10#action:get_flag;#a:show_flag;1
        querystring = urllib.unquote(request.query_string) #获得url?后面的字符串。并把其反url编码化
        request.event_queue = []
        if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: 
            querystring = 'action:index;False#False'  #action:trigger_event#;action:buy;10#action:get_flag;#a:show_flag;1
        if 'num_items' not in session:  
            session['num_items'] = 0
            session['points'] = 3
            session['log'] = []
        request.prev_session = dict(session) #一个字典num_items=0 points=3 log = []
        trigger_event(querystring) #'action:index;False#False'
        return execute_event_loop()
    
    # handlers/functions below --------------------------------------
    #view页面
    def view_handler(args):
        page = args[0]
        html = ''
        html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
        if page == 'index':
            html += '<a href="./?action:index;True%23False">View source code</a><br />'
            html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
            html += '<a href="./?action:view;reset">Reset</a><br />'
        elif page == 'shop':
            html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
        elif page == 'reset':
            del session['num_items']
            html += 'Session reset.<br />'
        html += '<a href="./?action:view;index">Go back to index.html</a><br />'
        return html
    #index页面
    def index_handler(args):
        bool_show_source = str(args[0])
        bool_download_source = str(args[1])
        if bool_show_source == 'True':
        
            source = open('eventLoop.py', 'r')
            html = ''
            if bool_download_source != 'True':
                html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
                html += '<a href="./?action:view;index">Go back to index.html</a><br />'
                
            for line in source:
                if bool_download_source != 'True':
                    html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />')
                else:
                    html += line
            source.close()
            
            if bool_download_source == 'True':
                headers = {}
                headers['Content-Type'] = 'text/plain'
                headers['Content-Disposition'] = 'attachment; filename=serve.py'
                return Response(html, headers=headers)
            else:
                return html
        else:
            trigger_event('action:view;index')
            
    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
        
    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()) # show_flag_function has been disabled, no worries
        trigger_event('action:view;index')
        
    if __name__ == '__main__':
        app.run(debug=False, host='0.0.0.0')
    
    • 通读代码会发现get_flag_handler函数的执行会把flag存储到session['log'],而session会加密存储到浏览器中。
    • get_flag_handler执行的内在条件是 session['num_items']大于5,而程序限制了数码,所以观察代码发现,存在线程逻辑漏洞
    • 只要先执行trigger_event安排顺序,再执行buy_handler让 session['num_items']值增加到5以上,然后下一个函数就是get_flag_handler即可。
    • pyload为action:trigger_event#;action:buy;10#action:get_flag;其中第一个#就用来注释的。
    • 得到session后,用flask seession解密工具解密即可。参考文章https://xz.aliyun.com/t/3569

    相关文章

      网友评论

          本文标题:homebrew event loop

          本文链接:https://www.haomeiwen.com/subject/desnnqtx.html