0x00 知识点

逻辑漏洞:

异步处理导致可以先调用增加钻石,再调用计算价钱的。也就是先货后款。

eval函数存在注入,可以通过#注释,我们可以传入路由action:eval#;arg1#arg2#arg3这样注释后面语句并可以调用任意函数,分号后面的#为传入参数,参数通过#被分割为参数列表.

flask session解密

网上有脚本

0x01解题

题目给了我们源码了

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

先贴上师傅博客:

https://blog.cindemor.com/post/ctf-web-16.html

分析一下:

  1. # flag获取函数def FLAG()
  2. # 以下三个函数负责对参数进行解析。
  3. # 1. 添加log,并将参数加入队列def trigger_event(event)
  4. # 2. 工具函数,获取prefix与postfix之间的值
  5. def get_mid_str(haystack, prefix, postfix=None):
  6. # 3. 从队列中取出函数,并分析后,进行执行。(稍后进行详细分析)
  7. def execute_event_loop()
  8. # 网站入口点
  9. def entry_point()
  10. # 页面渲染,三个页面:
  11. index/shop/resetdef view_handler()
  12. # 下载源码
  13. def index_handler(args)
  14. # 增加钻石
  15. def buy_handler(args)
  16. # 计算价钱,进行减钱
  17. def consume_point_function(args)
  18. # 输出flagdef show_flag_function(args)
  19. def get_flag_handler(args)

有这么两个跟 flag 有关的函数:

  1. def show_flag_function(args):
  2. flag = args[0]
  3. #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
  4. return 'You naughty boy! ;) <br />'
  5. def get_flag_handler(args):
  6. if session['num_items'] >= 5:
  7. trigger_event('func:show_flag;' + FLAG())
  8. trigger_event('action:view;index')

可以看到show_flag_function()无法直接展示出 flag,先看看get_flag_handler()中用到的trigger_event()函数:

  1. def trigger_event(event):
  2. session['log'].append(event)
  3. if len(session['log']) > 5: session['log'] = session['log'][-5:]
  4. if type(event) == type([]):
  5. request.event_queue += event
  6. else:

这个函数往 session 里写了日志,而这个日志里就有 flag,并且 flask 的 session 是可以被解密的。只要后台成功设置了这个 session 我们就有机会获得 flag。

但若想正确调用show_flag_function(),必须满足session['num_items'] >= 5。

购买num_items需要花费points,而我们只有 3 个points,如何获得 5 个num_items?

先看看购买的机制:

  1. def buy_handler(args):
  2. num_items = int(args[0])
  3. if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
  4. session['num_items'] += num_items
  5. trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])
  6. def consume_point_function(args):
  7. point_to_consume = int(args[0])
  8. if session['points'] < point_to_consume: raise RollBackException()
  9. session['points'] -= point_to_consume

buy_handler()这个函数会先把num_items的数目给你加上去,然后再执行consume_point_function(),若points不够consume_point_function()会把num_items的数目再扣回去。

其实就是先给了货后,无法扣款,然后货被拿跑了

那么我们只要赶在货被抢回来之前,先执行get_flag_handler()即可。

函数trigger_event()维护了一个命令执行的队列,只要让get_flag_handler()赶在consume_point_function()之前进入队列即可。看看最关键的执行函数:

仔细分析execute_event_loop,会发现里面有一个eval函数,而且是可控的!

利用eval()可以导致任意命令执行,使用注释符可以 bypass 掉后面的拼接部分。

若让eval()去执行trigger_event(),并且在后面跟两个命令作为参数,分别是buy和get_flag,那么buy和get_flag便先后进入队列。

根据顺序会先执行buy_handler(),此时consume_point进入队列,排在get_flag之后,我们的目标达成。

所以最终 Payload 如下:

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

要注意执行buy_handler函数后事件列表末尾会加入consume_point_function函数,在最后执行此函数时校验会失败,抛出RollBackException()异常,但是不会影响session的返回

参考链接:

https://blog.cindemor.com/post/ctf-web-16.html

[DDCTF 2019]homebrew event loop的更多相关文章

  1. 刷题记录:[DDCTF 2019]homebrew event loop

    目录 刷题记录:[DDCTF 2019]homebrew event loop 知识点 1.逻辑漏洞 2.flask session解密 总结 刷题记录:[DDCTF 2019]homebrew ev ...

  2. DDCTF 2019 部分WP

    WEB 滴~ http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09 观察链接可发现jpg的值是文件名转hex再bas ...

  3. 浏览器与Node的事件循环(Event Loop)有何区别?

    前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的. 一.线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里 ...

  4. Atitit 解决Unhandled event loop exception错误的办法

    Atitit 解决Unhandled event loop exception错误的办法 查看workspace/.metadata/.log org.eclipse.swt.SWTError: No ...

  5. javascript运行模式:并发模型 与Event Loop

    看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...

  6. [译]Node.js - Event Loop

    介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...

  7. Eclipse经常报Unhandled event loop exception的原因

    在公司的电脑上,Eclipse经常报Unhandled event loop exception 错误,非常频繁,通过搜索发现是因为电脑上安装了百度杀毒导致的.... 无语 另外 teamviewer ...

  8. Node.js 事件循环(Event Loop)介绍

    Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...

  9. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

随机推荐

  1. C++面试常见问题——16函数模板的使用

    函数模板的使用 函数模板在使用之前必须在外部对函数模板进行初始化. 函数模板的实例化包含两中 1.隐式实例化: template <class T> //没有: T Fun(T a,T b ...

  2. 使用JS写一个计算器

    先上效果图: 简单的加减乘除功能还是有的,所以我们就考虑怎么来实现这个功能. 根据预期效果,可以确定页面中的布局要用到table tr td. 所以先放上页面布局,table的边框宽度border,c ...

  3. jenkins#安装jenkins

    1. 访问官网下载地址https://jenkins.io/zh/download/ 2. 选择自己的平台,然后按照文档进行操作: 主要按照文档来,下面是我按照文档按照的一个记录 #访问 https: ...

  4. 深入理解JVM Note

    第2章 Java内存区域与内存溢出异常 运行时数据区域 在虚拟机有栈.堆和方法区. 线程共享的:堆.方法区 不共享的:栈.程序计数器(代码执行的行号) 程序计数器(Program Counter Re ...

  5. Day6 - I - Sticks Problem POJ - 2452

    Xuanxuan has n sticks of different length. One day, she puts all her sticks in a line, represented b ...

  6. Java 定时循环运行程序

    Timer 和 ScheduledExecutorSeruvce 都能执行定时的循环任务,有函数 scheduleAtFixedRate.但是,如果任务运行时间较长,超过了一个周期时长,下一个任务就会 ...

  7. canon 打印机 连接不上 netgear 路由器

    解决方法很简单,只要把信道设置到 10以内即可.

  8. [YOLO]《YOLOv3: An Incremental Improvement》笔记

    相比较于前两篇论文,个人感觉YOLO3作者有点来搞笑的!!!虽然加了一些新的点子进来,但是,论文的开头是这样的: 简单理解就是作者花了很多时间玩Twitter去了,所以没有做啥研究!!!! 然后: 你 ...

  9. Spark 资源调度包 stage 类解析

    spark 资源调度包 Stage(阶段) 类解析 Stage 概念 Spark 任务会根据 RDD 之间的依赖关系, 形成一个DAG有向无环图, DAG会被提交给DAGScheduler, DAGS ...

  10. 小程序Promise

    /** 异步函数回调简化处理 const promisify = require('./promisify') const getSystemInfo = promisify(wx.getSystem ...