首页 > 其他分享 >BUUCTF刷题:[DDCTF 2019]homebrew event loop

BUUCTF刷题:[DDCTF 2019]homebrew event loop

时间:2024-07-02 10:10:36浏览次数:16  
标签:BUUCTF num items args DDCTF session 2019 action event

[DDCTF 2019]homebrew event loop

代码审计

from flask import Flask, session, request, Response
import urllib
import urllib.parse

app = Flask(__name__)
app.secret_key = '*********************'  # censored
url_prefix = '/d5afe1f66147e857'


def FLAG():
    return '*********************'  # censored


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)


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


class RollBackException(Exception):
    pass


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. <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 as 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+'/')
def entry_point():
    querystring = urllib.parse.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()

# handlers/functions below --------------------------------------


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


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:
        # show_flag_function has been disabled, no worries
        trigger_event('func:show_flag;' + FLAG())
    trigger_event('action:view;index')


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

代码实在太长了,最好是先从flag开始分析

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需要num_items>=5

然后在buy_handler可以增加数字

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'])

可以看到先+=num_items

然后执行consume操作

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

这种购买类的一般都存在购买的逻辑漏洞

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)

可以直接传数组进去,这样不就直接就能多次执行了吗?我们执行一次买钻石,然后执行get_flag_handler就好了

再看看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)

调用函数的逻辑,每次取队列中的第一个元素event = request.event_queue[0]

action:的话自动加_handler;func:的话自动加_function

想法很好但是现实很骨感,我们并不能传入数组给trigger_event

后端没有代码实现让我们传入数组

trigger_event​函数只能执行一次且我们无法传入数组

那如果我们调用trigger_event ​自己呢,不就可以多次调用函数了吗

image-20240701185155-d7fu43e

所以这里其实就是给我们的提示

我们传入?action:trigger_event:%23;action:buy;5%23action:get_flag;

使用#​号来绕过后缀拼接

image-20240701193154-tkklo42

然后用工具解密cookie就拿到flag了,这里用在线网站解不了

标签:BUUCTF,num,items,args,DDCTF,session,2019,action,event
From: https://www.cnblogs.com/m1xian/p/18279348

相关文章

  • BUUCTF---rsa_output
    题目点击查看代码{21058339337354287847534107544613605305015441090508924094198816691219103399526800112802416383088995253908857460266726925615826895303377801614829364034624475195859997943146305588315939130777450485196290766249612340054354622516207681542973756......
  • Windows Server 2019 OVF, updated Jun 2024 (sysin) - VMware 虚拟机模板
    WindowsServer2019OVF,updatedJun2024(sysin)-VMware虚拟机模板2024年6月版本更新,现在自动运行sysprep,支持ESXiHostClient部署请访问原文链接:https://sysin.org/blog/windows-server-2019-ovf/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgWindo......
  • NOI2019 Day1
    就准备这样面对你的NOI吗?问题:对拍,极限数据,构造数据。不要老觉得过了大洋里就可以万事大吉跑路了。自己觉得写不完的东西,一定不要上来就写。读题。读题。读题。实在改不了就每题都先写个暴力验证题意。学会放题。一个题实在想不明白就退而求其次。保持冷静。尽量一遍写对......
  • BUUCTF 53.SoulLike
    跟进到关键函数,这里有三千行异或操作最后的比较很简单有两种思路,正向和逆向第一种:正向解决,把三千行代码原封不动抄下来,爆破十一个可能的字母,数字和下划线。第二种:逆向解决,因为异或是可逆的操作,只要把全部操作倒过来,加变减即可。这里利用excel表格逆向这些操作语句:1.添......
  • P6261 [ICPC2019 WF] Traffic Blights 题解
    思路考虑题目要求的是什么。假设\(p_i\)代表通过前\(i\)个红绿灯的概率。那么我们的答案即为\(p_i-p_{i-1}\)。不妨设\(w_i=r_i+g_i\)。我们的限制条件类似:\[t\not\equiva_i\pmodw_i\]那么所有红绿灯会形成周期\(lcm(w_1,w_2,\cdots,w_n)\)。由于\(2019!\)肯......
  • BUUCTF PWN
    ripchecksec分析一下,发现没有开NX,PIE。栈段可执行,还有RWX的段看了一下main函数存在栈溢出,然后有一个fun函数很奇怪跟进看一下,发现是后门,很简单的ret2text,但是做64位题的时候要注意堆栈平衡frompwnimport*#io=process('./pwn1')io=remote("node5.buuoj.cn",25429)......
  • BUUCTF-WEB(86-90)
    [NPUCTF2020]ezinclude参考:php7segmentfault特性(CVE-2018-14884)-Eddie_Murphy-博客园(cnblogs.com)[BUUCTF题解][NPUCTF2020]ezinclude1-Article_kelp-博客园(cnblogs.com)查看源码发现然后抓包发现一个hash值然后我直接传参数,让pass等于这一个Hash值?pass=f......
  • [安洵杯 2019]easy_serialize_php1
    知识点:1.php代码审计       2.序列化与反序列化       3.键值对逃逸进入之后开始代码审计~<?php$function=@$_GET['f'];functionfilter($img){$filter_arr=array('php','flag','php5','php4','fl1g'......
  • BUUCTF-Misc(151-160)
    [DDCTF2018]第四扩展FSbinwalk提取一下然后提取出来一个加密压缩包,密码就在图片的备注里Pactera提取出来是一个文本字频统计得到flagflag{huanwe1sik4o!}Beautiful_Side010editor打开,发现一个png文件,我们提取出来发现是半张二维码然后打开QRazyBox-QRCodeAnal......
  • BUUCTF 48~59 wp
    48用户名是welcomebeijing主函数401090函数处理用户名,401830参数为用户名和密码,比较关键401830的前面部分是处理密码,转化为十进制后半部分包含一段反调试代码,401470函数比较是否正确使用动态调试可以忽视401090的处理,直接在xor处得到处理后的每个数据48树和其他主函......