【问题标题】:How to safely use exec() in Python?如何在 Python 中安全地使用 exec()?
【发布时间】:2012-03-29 04:34:02
【问题描述】:

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

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

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

&lt;&lt;21&gt;&gt;&lt;&lt;22&gt;&gt; 参数将被替换为程序前面找到的值。一旦发生所有这些替换,我就有一个非常简单的 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 语句免受不安全(有意或无意)代码的影响?

【问题讨论】:

标签: python python-exec


【解决方案1】:

这是我从我的经验中看到的 - 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。 exec 的问题是 import 语句在那里是可能的,所以你需要编写一个过滤器来删除所有的 import 语句。

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

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

【讨论】:

    【解决方案2】:

    使用evalexec 的唯一安全方法是不使用它们。

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

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

    【讨论】:

    • 能否请您在此处包含您所谈论的实用程序?
    • 虽然这是大约 8 年前的事,但我还是会回答。我相信ast 模块就是他们所说的。
    • 你能提供一个如何避免使用 exec 的例子吗?
    【解决方案3】:

    您的 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"]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-03
      • 1970-01-01
      • 1970-01-01
      • 2017-10-19
      • 2021-02-23
      • 2020-04-28
      • 2011-06-29
      • 2016-09-24
      相关资源
      最近更新 更多