30.1. rexec-受限执行框架

自 2.6 版起弃用:rexec模块已在 Python 3 中删除。

在版本 2.3 中更改:禁用模块。

Warning

该文档已保留在原处,以帮助您阅读使用该模块的旧代码。

该模块包含RExec类,该类支持r_eval()r_execfile()r_exec()r_import()方法,这是标准 Python 函数eval()execfile()以及execimport语句的受限版本。在此受限环境中执行的代码只能访问被认为安全的模块和Function;您可以将RExec子类化,以根据需要添加或删除Function。

Warning

尽管rexec模块旨在按如下所述执行,但确实存在一些已知的漏洞,可以pass精心编写的代码来利用这些漏洞。因此,在需要“生产就绪”安全性的情况下,不应依赖它。在这种情况下,可能需要pass子流程执行或非常仔细地“清理”要处理的代码和数据。另外,也欢迎提供帮助修复已知的rexec漏洞。

Note

RExec类可以防止代码执行不安全的操作,例如读取或写入磁盘文件或使用 TCP/IP 套接字。但是,它不能防止使用大量内存或处理器时间的代码。

    • class * rexec. RExec([* hooks * [,* verbose *]])
    • 返回RExec类的实例。
  • hooks *是RHooks类或其子类的实例。如果Ellipsis或None,则实例化默认的RHooks类。每当rexec模块搜索模块(甚至是内置模块)或读取模块的代码时,它实际上都不会访问文件系统本身。相反,它调用传递给其构造函数或由其构造函数创建的RHooks实例的方法。 (实际上,RExec对象不会进行这些调用-它们是由RExec对象的一部分的模块加载器对象进行的。这提供了另一种灵 Active,这在受限环境中更改import的机制时很有用。 )

pass提供备用的RHooks对象,我们可以控制对导入模块的文件系统的访问,而无需更改控制访问 Sequences 的实际算法。例如,我们可以替换RHooks对象,该对象pass某种 RPC 机制(例如 ILU)将所有文件系统请求传递到其他位置的文件服务器。 Grail 的 applet 加载器使用它来支持从目录的 URL 导入 applet。

如果* verbose *为 true,则可能会将其他调试输出发送到标准输出。

请务必注意,在受限环境中运行的代码仍可以调用sys.exit()函数。要禁止受限代码退出解释器,请始终使用导致SystemExit异常的try/except语句来保护导致受限代码运行的调用。从受限环境中删除sys.exit()函数是不够的-受限代码仍可以使用raise SystemExit。删除SystemExit不是一个合理的选择;一些库代码利用了这一点,如果无法使用,则会break。

See also

  • 圣杯首页

  • Grail 是完全用 Python 编写的 Web 浏览器。它使用rexec模块作为支持 Python applet 的基础,并且可以用作该模块的示例用法。

30.1.1. RExec 对象

RExec个实例支持以下方法:

  • RExec. r_eval(* code *)

      • code *必须是包含 Python 表达式的字符串或已编译的代码对象,将在受限环境的main模块中对其进行评估。表达式或代码对象的值将被返回。
  • RExec. r_exec(* code *)

      • code *必须是包含一行或多行 Python 代码的字符串,或者是将在受限环境的main模块中执行的已编译代码对象。
  • RExec. r_execfile(* filename *)

    • 执行受限环境的main模块中文件* filename *中包含的 Python 代码。

名称以s_开头的方法类似于以r_开头的函数,但是将授予该代码访问标准 I/O 流sys.stdinsys.stderrsys.stdout的受限版本的权限。

  • RExec. s_eval(* code *)

      • code *必须是包含 Python 表达式的字符串,它将在受限环境中进行评估。
  • RExec. s_exec(* code *)

      • code *必须是包含一行或多行 Python 代码的字符串,该代码将在受限环境中执行。
  • RExec. s_execfile(* code *)

    • 在受限环境中执行文件* filename *中包含的 Python 代码。

RExec对象还必须支持各种方法,这些方法将在受限环境中执行的代码隐式调用。在子类中重写这些方法用于更改由受限环境强制实施的策略。

  • RExec. r_import(* modulename * [,* globals * [,* locals * [,* fromlist *]]])

    • 导入模块* modulename *,如果认为模块不安全,则引发ImportError异常。
  • RExec. r_open(* filename * [,* mode * [,* bufsize *]])

    • 在受限环境中调用open()时调用的方法。参数与open()相同,并且应返回文件对象(或与文件对象兼容的类实例)。 RExec的默认行为是允许打开任何文件进行读取,但禁止任何写入文件的try。参见下面的示例,以限制r_open()的实现。
  • RExec. r_reload(* module *)

    • 重新加载模块对象* module *,重新解析并重新初始化它。
  • RExec. r_unload(* module *)

    • 卸载模块对象* module *(将其从受限环境的sys.modules字典中删除)。

及其等效项,可以访问受限制的标准 I/O 流:

  • RExec. s_import(* modulename * [,* globals * [,* locals * [,* fromlist *]]])

    • 导入模块* modulename *,如果认为模块不安全,则引发ImportError异常。
  • RExec. s_reload(* module *)

    • 重新加载模块对象* module *,重新解析并重新初始化它。
  • RExec. s_unload(* module *)

    • 卸载模块对象* module *。

30.1.2. 定义受限环境

RExec类具有以下类属性,这些属性由init()方法使用。在现有实例上更改它们不会有任何效果;而是创建RExec的子类,并在类定义中为其分配新值。然后,新类的实例将使用这些新值。所有这些属性都是字符串的 Tuples。

  • RExec. nok_builtin_names

    • 包含内置函数的名称,这些名称对于在受限环境中运行的程序将不可用。 RExec的值为('open', 'reload', '__import__')。 (这是个 exception,因为到目前为止大多数内置函数都是无害的.要覆盖此变量的子类可能应该以 Base Class 中的值开头,并连接其他禁止的函数-当新的危险内置函数时被添加到 Python,它们也将被添加到此模块.)
  • RExec. ok_builtin_modules

    • 包含可以安全导入的内置模块的名称。 RExec的值为('audioop', 'array', 'binascii', 'cmath', 'errno', 'imageop', 'marshal', 'math', 'md5', 'operator', 'parser', 'regex', 'select', 'sha', '_sre', 'strop', 'struct', 'time')。关于覆盖此变量的类似说明也适用-使用 Base Class 中的值作为起点。
  • RExec. ok_path

    • 包含在受限环境中执行import时将搜索的目录。对于不受限制的代码,RExec的值与sys.path相同(加载模块时)。
  • RExec. ok_posix_names

    • 包含os模块中的Function名称,这些名称将在受限环境中运行的程序使用。 RExec的值为('error', 'fstat', 'listdir', 'lstat', 'readlink', 'stat', 'times', 'uname', 'getpid', 'getppid', 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
  • RExec. ok_sys_names

    • 包含sys模块中的函数和变量的名称,可用于在受限环境中运行的程序。 RExec的值为('ps1', 'ps2', 'copyright', 'version', 'platform', 'exit', 'maxint')
  • RExec. ok_file_types

    • 包含允许从中加载模块的文件类型。每种文件类型都是在imp模块中定义的整数常量。有意义的值为PY_SOURCEPY_COMPILEDC_EXTENSIONRExec的值为(C_EXTENSION, PY_SOURCE)。不推荐在子类中添加PY_COMPILED。攻击者可以pass将伪造的字节编译文件(.pyc)放在文件系统中的任何位置来退出受限执行模式,例如,将其写入/tmp或将其上传到公共 FTP 服务器的/incoming目录中。

30.1.3. 一个例子

让我们说,我们想要一个比标准RExec类稍微宽松一些的策略。例如,如果我们愿意写入/tmp中的文件,则可以将RExec类作为子类:

class TmpWriterRExec(rexec.RExec):
    def r_open(self, file, mode='r', buf=-1):
        if mode in ('r', 'rb'):
            pass
        elif mode in ('w', 'wb', 'a', 'ab'):
            # check filename: must begin with /tmp/
            if file[:5]!='/tmp/':
                raise IOError("can't write outside /tmp")
            elif (string.find(file, '/../') >= 0 or
                 file[:3] == '../' or file[-3:] == '/..'):
                raise IOError("'..' in filename forbidden")
        else: raise IOError("Illegal open() mode")
        return open(file, mode, buf)

请注意,以上代码有时会禁止使用完全有效的文件名;例如,受限环境中的代码将无法打开名为/tmp/foo/../bar的文件。要解决此问题,r_open()方法将必须将文件名简化为/tmp/bar,这将需要拆分文件名并对其执行各种操作。在安全受到威胁的情况下,最好写一些有时过于严格的简单代码,而不是写一些更复杂并且可能带有细微安全漏洞的通用代码。