7

我的任务是构建一个应用程序,在该应用程序中,最终用户可以使用自定义规则来评估返回的查询是否会导致警告或警报(基于自己的阈值)。

我为用户构建了一种方法来模板化他们的逻辑。一个示例如下所示:

if (abs(<<21>>) >= abs(<<22>>)):
    retVal = <<21>>
else:
    retVal = <<22>>

<<21>><<22>>参数将替换为程序前面找到的值。一旦所有这些替换发生,我就有一个非常简单的 if/else 块(在本例中),看起来像这样存储在变量 ( execCd) 中:

if (abs(22.0) >= abs(-162.0)):
    retVal = 22.0
else:
    retVal = -162.0

这将exec()正确。现在,我怎样才能确保这一点?我看过这篇文章: http: //lybniz2.sourceforge.net/safeeval.html

我的代码最终看起来像这样:

safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] 
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) 
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)

但是,当我有第二个和第三个参数但此异常时,执行失败 -NameError: name 'retVal' is not defined

最终用户拥有的一些自定义逻辑非常广泛,其中大部分都会定期更改。我不想维护他们的自定义逻辑,最终用户希望能够快速测试各种警告/警报阈值逻辑。

如何从不安全(有意或无意)代码中保护此 exec 语句?

4

3 回答 3

7

eval使用或exec不使用它们的唯一安全方法。

您不需要使用 exec。与其构建要执行的字符串,不如将其解析为对象,然后使用它来驱动代码执行。

在最简单的情况下,您可以将函数存储在 dict 中,并使用字符串来选择要调用的函数。如果你使用 python 语法,python 提供了所有的工具来解析自己,你应该使用它们。

于 2012-03-12T18:44:01.087 回答
5

您的exec声明不是将 retVal 添加到您的本地环境中,而是添加到safe_dict字典中。所以你可以从那里取回它:

execCd = """
if (abs(22.0) >= abs(-162.0)):
    retVal = 22.0
else:
    retVal = -162.0
"""

safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] 
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) 
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)
retVal = safe_dict["retVal"]
于 2012-03-12T18:47:18.737 回答
0

这就是我从我的经验中看到的 - exec 和 eval 有两个主要危险,我的意思是它们都有一个解决方法:

首先,您有源代码在源代码中运行。这意味着这段代码:

eval("mysql_password")

将允许用户访问整个模块中的所有变量和实例。我们不希望那样。如果这是一个连接到 MySQL 的烧瓶服务器,以这种方式执行 eval 就像向公众打开一扇门以查看您的密码。该用户刚刚获得了您的 MySQL 密码,并且成功地不仅登录了那里,还清除了所有数据并更改了密码。恭喜!

相反,您将不得不创建一个字典,在其中只为用户提供基本变量。如果你不想这样,你也可以插入一个空字典。

eval("mysql_password",{},{}) #this will fail as it should in this case.

为什么有两个字典?这是因为第一个用于局部变量,另一个用于全局变量。这意味着字符串中的代码将能够读取和写入本地字典,但只能从全局字典中读取。

现在,当我们解决了这个问题时,就有了第二个问题:

用户可以使用诸如“open()”之类的系统命令或导入诸如 os 之类的模块来查看目录中的所有文件并可能对其进行修改。此外,他们可以导入预构建或外部安装的库,从而控制整个服务器。在这种情况下,有多种方法可以解决这个问题。首先,eval 比 exec 更安全,因为 eval 只有一行并且必须返回一个值,因此无法导入库并使用它来获得未经批准的访问。其次,您可以做的一件事是覆盖范围内的所有系统命令:

scope = {"open":None,"file":None,...} #include ALL dangerous system functions, especially "open".

那么您可以像这样进行“安全”评估:

eval("open('wpadmin/sql.ini').read()",scope,scope) # this will now failed since we defined "open" as None in the scope.

不幸的是,对于如何使其完全防弹并没有保证或完美的答案,但我想说解决问题的方法是定义一个有限的范围并坚持 eval 并尽力避免执行。exec 的问题是那里可能有 import 语句,因此您需要编写一个过滤器来删除所有 import 语句。

第三种方法是(如果您在服务器上运行),是调整 ftp 文件夹中所有文件和文件夹的安全级别,以便主源代码本身无法读取或写入这些文件。这样,用户在尝试使用“打开”之类的东西时会收到“拒绝访问”错误。

并且无论您为此用例做什么,都不要拥有任何可能对正在运行的 PC 造成严重破坏的强大库。我主要考虑像 autopy 和 pyautogui 这样的库,用户突然可以在服务器计算机上导航,而您所看到的只是鼠标自行移动。那些必须卸载,否则,不要使用它们中的任何一个。

于 2021-08-19T18:47:23.213 回答