在本文中,云朵君将和大家一起学习 eval() 如何工作,以及如何在 Python 程序中安全有效地使用它。
本节主要学习 eval() 如何使我们的代码不安全,以及如何规避相关的安全风险。
eval() 函数的安全问题在于它允许你(或你的用户)动态地执行任意的Python代码。
通常情况下,会存在正在读(或写)的代码不是我们要执行的代码的情况。如果我们需要使用eval()来计算来自用户或任何其他外部来源的输入,此时将无法确定哪些代码将被执行,这将是一个非常严重的安全漏洞,极易收到黑客的攻击。
一般情况下,我们并不建议使用 eval()。但如果非要使用该函数,需要记住根据经验法则:永远不要 用 未经信任的输入 来使用该函数。这条规则的重点在于要弄清楚我们可以信任哪些类型的输入。
举个例子说明,随意使用eval()会使我们写的代码漏洞百出。假设你想建立一个在线服务来计算任意的Python数学表达式:用户自定义表达式,然后点击运行按钮。应用程序app获得用户的输入并将其传递给eval()进行计算。
这个应用程序app将在我们的个人服务器上运行,而那些服务器内具有重要文件,如果你在一个Linux 操作系统运行命令,并且该进程有合法权限,那么恶意的用户可以输入危险的字符串而损害服务器,比如下面这个命令。
>>> eval("__import__('math').sqrt(25)", {"__builtins__": {}}, {}) Traceback (most recent call last): File "", line 1, in File "", line 1, in NameError: name '__import__' is not defined
如果我们将一个包含键值对 "__builtins__: {}" 的字典传递给 globals,那么 eval() 就不能直接访问 Python 的内置函数,比如 __import__()。
然而这种方法仍然无法使得 eval() 完全规避风险。
即使可以使用自定义的 globals 和 locals 字典来限制 eval()的执行环境,这个函数仍然会被攻击。例如可以使用像""、"[]"、"{}"或"() "来访问类object以及一些特殊属性。
>>> def eval_expression(input_string): ... # Step 1 ... allowed_names = {"sum": sum} ... # Step 2 ... code = compile(input_string, "", "eval") ... # Step 3 ... for name in code.co_names: ... if name not in allowed_names: ... # Step 4 ... raise NameError(f"Use of {name} not allowed") ... return eval(code, {"__builtins__": {}}, allowed_names)
eval_expression() 函数可以在 eval() 中使用的名字限制为字典 allowed_names 中的那些名字。而该函数使用了 .co_names,它是代码对象的一个属性,返回一个包含代码对象中的名字的元组。
下面的例子显示了eval_expression() 在实践中是如何工作的。
>>> eval(input("Enter a math expression: ")) Enter a math expression: 15 * 2 30 >>> eval(input("Enter a math expression: ")) Enter a math expression: 5 + 8 13
我们可以将函数 eval() 包裹在函数 input() 中,实现自动计算用户的输入的功能。一个常见用例模拟 Python 2.x 中 input() 的行为,input() 将用户的输入作为一个 Python 表达式来计算,并返回结果。
因为它涉及安全问题,因此在 Python 2.x 中的 input() 的这种行为在 Python 3.x 中被改变了。
到目前为止,我们已经了解了函数 eval() 是如何工作的以及如何在实践中使用它。此外还了解到 eval() 具有重要的安全漏洞,尽量在代码中避免使用 eval(),然而在某些情况下,eval() 可以为我们节省大量的时间和精力。因此,学会合理使用 eval() 函数还是蛮重要的。
在本节中,将编写一个应用程序来动态地计算数学表达式。首先不使用eval()来解决这个问题,那么需要通过以下步骤:
考虑到 Python 可以处理和计算的各种表达式非常耗时。其实我们可以使用 eval() 来解决这个问题,而且通过上文我们已经学会了几种技术来规避相关的安全风险。
首先创建一个新的Python脚本,名为mathrepl.py,然后添加以下代码。
注意,在main()中,捕捉了所有已知异常,并相应地打印信息给用户。这将使用户能够审查表达式,修复问题,并再次运行程序。现在已经使用函数 eval() 在大约七十行的代码中建立了一个数学表达式计算器。要运行这个程序,打开我们的系统命令行,输入以下命令。$ python3 mathrepl.py
这个命令将启动数学表达式计算器的命令行界面(CLI),会在屏幕上看到类似这样的东西。
mr>> 5 * (25 + 4 Invalid input expression syntax mr>> sum([1, 2, 3, 4, 5]) The use of 'sum' is not allowed mr>> sqrt(-15) math domain error mr>> factorial(-15) factorial() not defined for negative values
在第一个示例中,漏掉了右括号,因此收到一条消息,告诉我们语法不正确。然后调用 sum() ,这会得到一个解释性的异常消息。最后,使用无效的输入值调用“math”函数,应用程序将生成一条消息来识别输入中的问题。
你可以使用Python的 eval() 从基于字符串或基于代码的输入中计算Python 表达式。当我们动态地计算Python表达式,并希望避免从头创建自己的表达式求值器的麻烦时,这个内置函数可能很有用。
在本文中,我们已经学习了 eval() 是如何工作的,以及如何安全有效地使用它来计算任意Python表达式。