30.1. rexec — 受限执行框架 — Python 文档
30.1. 执行 — 受限执行框架
自 2.6 版起已弃用:rexec 模块已在 Python 3 中删除。
在 2.3 版更改:禁用模块。
警告
文档已保留,以帮助阅读使用该模块的旧代码。
该模块包含 RExec 类,该类支持 r_eval()、r_execfile()、r_exec() 和 r_import() 方法,这些方法是标准 Python 函数 eval()、execfile() 和 exec 和 import 语句。 在这个受限环境中执行的代码只能访问被认为安全的模块和功能; 您可以子类化 RExec 以根据需要添加或删除功能。
警告
虽然 rexec 模块的设计如下所述,但它确实有一些已知的漏洞,可以通过精心编写的代码加以利用。 因此,在需要“生产就绪”安全性的情况下不应依赖它。 在这种情况下,可能需要通过子进程执行或非常小心地“清理”要处理的代码和数据。 或者,欢迎帮助修补已知的 rexec 漏洞。
- class rexec.RExec([hooks[, verbose]])
返回 RExec 类的实例。
hooks 是
RHooks类或其子类的实例。 如果省略或None,则实例化默认的RHooks类。 每当 rexec 模块搜索模块(甚至是内置模块)或读取模块代码时,它实际上并没有转到文件系统本身。 相反,它调用传递给其构造函数或由其构造函数创建的RHooks实例的方法。 (实际上, RExec 对象不会进行这些调用——它们是由作为 RExec 对象一部分的模块加载器对象进行的。 这允许另一个级别的灵活性,这在受限环境中更改 import 的机制时非常有用。)通过提供一个替代的
RHooks对象,我们可以控制对导入模块进行的文件系统访问,而无需更改控制这些访问顺序的实际算法。 例如,我们可以替换一个RHooks对象,该对象通过一些 RPC 机制(如 ILU)将所有文件系统请求传递到其他地方的文件服务器。 Grail 的小程序加载器使用它来支持从目录的 URL 导入小程序。如果 verbose 为真,额外的调试输出可能会发送到标准输出。
请务必注意,在受限环境中运行的代码仍然可以调用 sys.exit() 函数。 要禁止受限代码退出解释器,请始终保护导致受限代码运行的调用,并使用捕获 SystemExit 异常的 try/except 语句。 从受限环境中删除 sys.exit() 函数是不够的——受限代码仍然可以使用 raise SystemExit。 删除 SystemExit 不是一个合理的选择; 一些库代码利用了这一点,如果它不可用就会中断。
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.stdin、[ X194X] 和 sys.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 的默认行为是允许打开任何文件进行读取,但禁止任何尝试写入文件。 有关限制较少的 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 的子类并在类定义中为它们分配新值。 然后新类的实例将使用这些新值。 所有这些属性都是字符串元组。
- RExec.nok_builtin_names
- 包含内置函数的名称,这些函数将 不 可用于在受限环境中运行的程序。 RExec 的值为
('open', 'reload', '__import__')。 (这给出了例外情况,因为到目前为止,大多数内置函数都是无害的。 想要覆盖这个变量的子类应该从基类的值开始,并连接额外的禁止函数——当新的危险内置函数被添加到 Python 时,它们也将被添加到这个模块中。)
- RExec.ok_builtin_modules
- 包含可以安全导入的内置模块的名称。 RExec 的值为
('audioop', 'array', 'binascii', 'cmath', 'errno', 'imageop', 'marshal', 'math', 'md5', 'operator', 'parser', 'regex', 'select', 'sha', '_sre', 'strop', 'struct', 'time')。 关于覆盖此变量的类似评论适用 - 使用基类中的值作为起点。
- RExec.ok_posix_names
- 包含 os 模块中函数的名称,这些函数可用于在受限环境中运行的程序。 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_SOURCE、PY_COMPILED和C_EXTENSION。 RExec 的值为(C_EXTENSION, PY_SOURCE)。 不建议在子类中添加PY_COMPILED; 攻击者可以通过将伪造的字节编译文件 (.pyc) 放在文件系统中的任何位置来退出受限执行模式,例如将其写入/tmp或将其上传到/incoming公共 FTP 服务器的目录。
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,这需要拆分文件名并对其执行各种操作。 在安全受到威胁的情况下,最好编写有时过于严格的简单代码,而不是更复杂且可能包含微妙安全漏洞的更通用的代码。