首页 > 其他分享 >DDCTF2019-HOMEBREW-EVENT-LOOP-md

DDCTF2019-HOMEBREW-EVENT-LOOP-md

时间:2022-10-02 10:56:39浏览次数:54  
标签:DDCTF2019 items resp request event session action HOMEBREW EVENT


title: DDCTF2019 HOMEBREW EVENT LOOP.md
date: 2022-09-27 11:42:22
tags:

[DDCTF 2019]homebrew event loop

代码审计

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

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:
  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, 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.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=False, host='0.0.0.0')

审计代码把 我一直不擅长python代码审计 这个题其实页折磨了好久

但是有一句话 当你感觉到困难的时候 就是我提升的开始

所以都是在折磨中成长的

废话不多说 审计代码

是个pythonflask框架

对于python的flask框架 在路由下 执行得到函数是 第一个定义的函数

所以我们就开始从第一个函数 entry_point来开始

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()

这里的 urllib.unquote函数就是对参数进行url解码

里面的request.query_string函数就是接受url穿过来的东西

如果我们url传过来的东西为空 或者 我们穿过来的东西没有以action:开头的时候 后者长度大于100 就强制querystring赋值为 action:index;False#False

    if 'num_items' not in session:
        session['num_items'] = 0
        session['points'] = 3
        session['log'] = []

这些东西应该是如果session中没有num_items 的话 就初始化一些东西

request.prev_session = dict(session)

应该就是放如session里面吧 (不过这个应该没什么用)

然后执行

trigger_event(querystring)
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)

这里append是在里面列表里面加入我们刚刚传入的值

应该是个log日志把

然后后来就是把我们传入的东西页加入到request.event_queue

继续回到entry_point():函数

执行 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. <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

看看这个是什么东西 定义了2个变量
valid_event_chars

​ resp = None

 event = request.event_queue[0]

把我们第一个传入的东西赋值为event

  request.event_queue = request.event_queue[1:]

然后 从request.event_queue 去除掉第一个

if not event.startswith(('action:', 'func:')):
    continue

如果我们传入的第一个东西没有以action:或者func:

开头的话 就退出这次循环

一个个检查我们刚才传入的字符不在valid_event_chars中的话 就退出

is_action = event[0] == 'a'

这个等会会说到

action = get_mid_str(event, ':', ';')

继续跟进get_mid_str函数

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

如果 第三个参数为空的话就返回 prefix后面的所有字符

如果第三个参数不为空的话 就返回 第二个参数到第三个参数之间的字符

 args = get_mid_str(event, action+';').split('#')

这里就是通过#把返回值分割赋值为args

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

说一下这个 如果is_action为空的话就返回_handler 为假的话就返回 _function

eval函数可以把字符串当成python代码来执行

返回resp

所有我们就想要直接执行FLAG()函数 但是发现是不行的 因为这里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')

如果这里num_items >=5的话 执行trigger_event函数就会被放入log中

所有看看如何得到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'])

看这个函数

这里就是有漏洞的 他这里会直接加上参数里面的东西

所有就有思路了 我们用eval函数执行buy_handler函数

这里构造payload

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

[红明谷CTF 2021]write_shell

给出源码

<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
    if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
        // if(preg_match("/'| |_|=|php/",$input)){
        die('hacker!!!');
    }else{
        return $input;
    }
}

function waf($input){
  if(is_array($input)){
      foreach($input as $key=>$output){
          $input[$key] = waf($output);
      }
  }else{
      $input = check($input);
  }
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
    mkdir($dir);
}
switch($_GET["action"] ?? "") {
    case 'pwd':
        echo $dir;
        break;
    case 'upload':
        $data = $_GET["data"] ?? "";
        waf($data);
        file_put_contents("$dir" . "index.php", $data);
}
?>

考点 段标签

 <?= ?>

其中=等价为echo

可以利用段标签绕过php

PHP 支持一个执行运算符:反引号(``)。注意这不是单引号!PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符"`"的效果与函数shell_exec() 相同。

可以过滤了空格 可以用%09绕过

这里说一下我的理解 用%09的时候 url会对其进行解码传到浏览器就会变成0x09 十六进制绕过空格

image-20220926215240454

image-20220926215240454

标签:DDCTF2019,items,resp,request,event,session,action,HOMEBREW,EVENT
From: https://www.cnblogs.com/kkkkl/p/16748373.html

相关文章

  • MySQL中EVENT的用法
    一、Event创建语法CREATEEvent[IFNOTEXISTS]event_name--创建使用createeventONSCHEDULEschedule--onschedule什么时候来执行,执行频率[ONCOMPLETION[N......
  • pg_eventserv push pg 变动事件到websocket 服务
    pg_eventserv的实现原理并不难,核心还是利用了pg的特性,只是包装了一个方便的golangserver然后我们可以通过webscoket发送出去参考使用CREATETABLEpeople(......
  • Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
    Netty学习(六):创建NioEventLoopGroup的核心源码说明作者:Grey原文地址:博客园:Netty学习(六):创建NioEventLoopGroup的核心源码说明CSDN:Netty学习(六):创建NioEventLoopG......
  • js event
          ......
  • WPF中Trigger、DataTrigger、EventTrigger区别
    Trigger属性触发器它监视所有者控件上的特定属性,当该属性具有与指定值匹配的值时,属性可以更改。<TriggerProperty="IsMouseOver"Value="True"> DataTrigger数据......
  • Vue中EventBus(事件总线)的基本用法
    vue组件中最常见的数据传递就是父子组件之间的传递,父组件可以通过props向下传数据给子组件,子组件可以通过$emit事件携带数据给父组件。然而当两个页面没有任关系,该如......
  • ts中常用Event 事件对象类型
    常用Event事件对象类型:ClipboardEvent<T=Element>剪贴板事件对象DragEvent<T=Element>拖拽事件对象ChangeEvent<T=Element>Change事件对象KeyboardEven......
  • preventDefault()、stopPropagation()、return false 之间的区别
    “returnfalse”之所以被误用的如此厉害,是因为它看起来像是完成了我们交给它的工作,浏览器不会再将我们重定向到href中的链接,表单也不会被继续提交,但这么做到底有什么不对呢......
  • WPF 解决 ObservableCollection 提示 Cannot change ObservableCollection during a C
    本文告诉大家在使用ObservableCollection时,抛出InvalidOperationException异常,提示CannotchangeObservableCollectionduringaCollectionChangedevent内容,的原......
  • EVENT LOOP 宏任务、微任务
    JS单线程同步程序执行完成之后,执行异步程序js是单线程的,一个任务执行完成之后才执行另一个任务process.nextTick(()=>{}),再node环境下才能运行,这个是在异步前执行的......