>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:21:30
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

2.6. 警惕 eval() 的安全漏洞

如果你了解JavaScript或者PHP等,那么你一定对 eval() 所有了解。 如果你并没有接触 过也没关系, eval() 函数的使用非常简单。

>>> eval ("1+1==2")
True
>>> eval("'A'+ 'B'")
'AB'
>>> eval("1+2")
3

Python中 eval() 函数将宇符串 str 当成有效的表达式来求值并返回计算结果。其函数声明如下:

eval(expression(,globals[r,locals]])

其中,参数 globals 为字典形式, locals 为任何映射对象,它们分别表示全局和局部命名空间。 如果传入 globals 参数的字典中缺少 __builtins__ 的时候,当前的全局命名空间将作为 globals 参数输入并且在表达式计算之前被解析。 locals 参数默认与 globals 相同,如果两者都省略的话,表达式将在 eval() 调用的环境中执行。

“eval is evil”(eval 是邪恶的),这是一句广为人知的对eval的评价,它主要针对的是 eval()的安全性。 那么eval存在什么样的安全漏洞呢?来看一个简单的例子:

>>> import sys
>>> from math import *
>>> def ExpCalcBot(string):
>>>     try:
>>>         print( 'Your answer is',eval (user_func))# 计算输入的值
>>>     except NameError:
>>>         print ("The expression you enter is not valid")
>>> print ('Hi/1 am ExpCalcBot. please input your experssion or enter e to end')
>>> inputstr =''
>>> while 1:
>>>     print ('enter A number or operation. Enter c to complete.:' )
>>>     inputstr = input()
>>>     if inputstr ==str('e') :#遇到靖入为e的时候退出
>>>         sys.exit ()
>>>     elif repr(inputstr)!= repr(''):
>>>         ExpCalcBot(inputstr)
>>>         inputstr = ''
Hi/1 am ExpCalcBot. please input your experssion or enter e to end
enter A number or operation. Enter c to complete.:
rrrgg
The expression you enter is not valid
enter A number or operation. Enter c to complete.:
fgsg
The expression you enter is not valid
enter A number or operation. Enter c to complete.:
_import_("os").system("dir")
The expression you enter is not valid
enter A number or operation. Enter c to complete.:
e
An exception has occurred, use %tb to see the full traceback.


SystemExit
/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py:2886: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
  warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)

上面这段代码的主要功能是:根据用户的输入,计算Python表达式的值。 它有什么问题呢?如果用户都是素质良好,没有不良目的的话,那么这段程序也许可以满足基本需求。 比如,输人 1+sin(20) 会输出结果 1.91294525073 。 但如果它是一个Web页面的后台调用(当然,你需要做一定的修改),由于网络环境下运行它的用户并非都是可信任的, 问题就出现了,因为 eval() 可以将任何字符串当做表达式求值,这也就意味着有空子可钻。

于是顿时,有人的“坏心眼”来了,他输入了如下字符串,可悲的事情发生了,当前目录下的所有文件都被删除了,包括test.py,而这一切没有任何提示,悄无声息。

_import_("os").system("dir") !!!不要轻易在你的计算机上尝试 Your answer is 0

试想,在网络环境下这是不是很危险?也许你会辩护,那是因为你没有在globals参数中禁止全局命名空间的访问。好,我们按照你说的来试验一下:将函数ExpCalcBot修改一 下,其中math_flun_list限定为几个常用的数学函数。修改后的函数如下:

>>> def ExpCalcBot(string):
>>>     try:
>>>         math_fun_list = ['acos', 'asin', 'atan',  'cos', 'e', 'log', 'log10','pi', 'pow', 'sin', 'sqrt', 'tan']
>>>         math_fun_diet = dict ([(k, globals().get (k) ) for k in math_fun_list]) #>成$以访问的函數的字與
>>>         print (' Your answer is', eval (string, {"_builtins_": None} ,math_fun_dict) )
>>>     except NameError:
>>>         print ("The expression you enter is not valid")

再次运行程序(请读者自行试验)你会惊喜地发现上面的命令被看着无效表达式,你的辩护是对的,这确实是我们想要的。很好,安全问题不再是个问题!但仔细想想真是这样的吗?试试输入以下字符:

[c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ =='Quitter'[0](0)() # ().__class__._bases__[0]._subclasses_() 用来显示 object 类的所有子类。类 Quitter 与 "quit"功能綁定,因此上面的输入会直接导致程序退出。

注:你可以在Python的安装目录下的Lib:raw-latex:site.py中找到其类的定义。读者也可以自行在 Python 解释器中输人 print().__ctass__.__bases__[0].__subclasses__()看看输出结果是什么。

因此对于有经验的侵人者来说,他可能会有一系列强大的手段,使得eval可以解释和调 用这些方法,从而带来更大的破坏。 此外,evai()函数也给程序的调试带来一定困难,要查 看eval()里面表达式具体的执行过程很难。 因此在实际应用过程中如果使用对象不是信任源, 应该尽量避免使用eval,在需要使用eval的地方可用安全性更好的 ast.literal_eval 替代。 literal_eval 函数具体详情可以参考文档 http://docs.python.org/2/library/ast.html#ast.literaLeval 上面的例子请读濟使用 literal_eval自行试验,你会体会得更加深刻。