[DDCTF 2019]homebrew event loop
0x00 知识点
逻辑漏洞:
异步处理导致可以先调用增加钻石,再调用计算价钱的。也就是先货后款。
eval函数存在注入,可以通过#注释,我们可以传入路由action:eval#;arg1#arg2#arg3这样注释后面语句并可以调用任意函数,分号后面的#为传入参数,参数通过#被分割为参数列表.
flask session解密
网上有脚本
0x01解题
题目给了我们源码了
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('&', '&').replace('\t', ' '*4).replace(
' ', ' ').replace('<', '<').replace('>', '>').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')
先贴上师傅博客:
https://blog.cindemor.com/post/ctf-web-16.html
分析一下:
# flag获取函数def FLAG()
# 以下三个函数负责对参数进行解析。
# 1. 添加log,并将参数加入队列def trigger_event(event)
# 2. 工具函数,获取prefix与postfix之间的值
def get_mid_str(haystack, prefix, postfix=None):
# 3. 从队列中取出函数,并分析后,进行执行。(稍后进行详细分析)
def execute_event_loop()
# 网站入口点
def entry_point()
# 页面渲染,三个页面:
index/shop/resetdef view_handler()
# 下载源码
def index_handler(args)
# 增加钻石
def buy_handler(args)
# 计算价钱,进行减钱
def consume_point_function(args)
# 输出flagdef show_flag_function(args)
def get_flag_handler(args)
有这么两个跟 flag 有关的函数:
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())
trigger_event('action:view;index')
可以看到show_flag_function()无法直接展示出 flag,先看看get_flag_handler()中用到的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:
这个函数往 session 里写了日志,而这个日志里就有 flag,并且 flask 的 session 是可以被解密的。只要后台成功设置了这个 session 我们就有机会获得 flag。
但若想正确调用show_flag_function(),必须满足session['num_items'] >= 5。
购买num_items需要花费points,而我们只有 3 个points,如何获得 5 个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'])
def consume_point_function(args):
point_to_consume = int(args[0])
if session['points'] < point_to_consume: raise RollBackException()
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 如下:
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的更多相关文章
- 刷题记录:[DDCTF 2019]homebrew event loop
目录 刷题记录:[DDCTF 2019]homebrew event loop 知识点 1.逻辑漏洞 2.flask session解密 总结 刷题记录:[DDCTF 2019]homebrew ev ...
- DDCTF 2019 部分WP
WEB 滴~ http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09 观察链接可发现jpg的值是文件名转hex再bas ...
- 浏览器与Node的事件循环(Event Loop)有何区别?
前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的. 一.线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里 ...
- Atitit 解决Unhandled event loop exception错误的办法
Atitit 解决Unhandled event loop exception错误的办法 查看workspace/.metadata/.log org.eclipse.swt.SWTError: No ...
- javascript运行模式:并发模型 与Event Loop
看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...
- [译]Node.js - Event Loop
介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...
- Eclipse经常报Unhandled event loop exception的原因
在公司的电脑上,Eclipse经常报Unhandled event loop exception 错误,非常频繁,通过搜索发现是因为电脑上安装了百度杀毒导致的.... 无语 另外 teamviewer ...
- Node.js 事件循环(Event Loop)介绍
Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
随机推荐
- wx.previewimage预览返回会触发onshow的处理方法
最近做详情页,添加图片预览后竟然触发onshow的处理方法.就显得很尴尬.框架用的uni-app 解决方法 1.page外全局定义开关变量 var a; export default { } 2 .o ...
- 最简单-转换MBR为GPT
Windows Server 2016可能没有这个 mbr2gpt 这个软件,可以从Windows 10 的C:\Windows\System32 目录下面复制到 Windows Server 201 ...
- [Codeforces #608 div2]1272B Blocks
Description There are nnn blocks arranged in a row and numbered from left to right, starting from on ...
- python爬虫破解带有RSA.js的RSA加密数据的反爬机制
前言 同上一篇的aes加密一样,也是偶然发现这个rsa加密的,目标网站我就不说了,保密. 当我发现这个网站是ajax加载时: 我已经习以为常,正在进行爬取时,发现返回为空,我开始用findler抓包, ...
- 003.Oracle数据库 , 查询日期格式格式化
/*日期格式转换*/ SELECT TO_CHAR( OCCUR_DATE, 'yyyy/mm/dd hh24:mi:ss' ) FROM LM_FAULT WHERE ( ( OCCUR_DATE ...
- ListView的DrawSubItem时间添加边框,字体变粗问题
procedure TFrmrdp.ListView1AdvancedCustomDrawSubItem(Sender: TCustomListView; Item: TListItem; SubIt ...
- 在windows7 64位上安装selenium2library问题解决
今天发现了windows7 64位上安装selenium2Libaray的问题,下载exe文件安装不行.就切换成了半自动模式. 方案如下: 1.首先下载pip安装.(因为要用pip来安装 selen ...
- Codeforces Round #611 (Div. 3)
原题面:https://codeforces.com/contest/1283 A.Minutes Before the New Year 题目大意:给定时间,问距离零点零分还有多久? 分析:注意一下 ...
- Linux: 桥接 NET HOST-only
桥接 虚拟机会利用真实的网卡和真实计算机之间通信 还能和同一局域网之间的计算机之间通信 缺点:会耗费一个ip地址 NET:虚拟机会通过VMnet8 虚拟的网卡与真实计算机之间通信 如果真实计可 ...
- mybatis插入数据后将其ID返回
背景 mybatis没有关联保存的功能,所以主从表需要分开保存,这就涉及到主表保存后要再次获取主表ID的环节,以下介绍mybatis插入数据后返回其自增ID的两种方式 方案 1.sql获取 <i ...