在Python中禁止exec和eval访问文件系统

4 投票
5 回答
3435 浏览
提问于 2025-04-16 12:27

我想禁止客户端代码访问文件系统,所以我想重写打开文件的功能。

env = {
   'open': lambda *a: StringIO("you can't use open")
}

exec(open('user_code.py'), env)

但是我遇到了这个问题。

unqualified exec is not allowed in function 'my function' it contains a 
nested function with free variables

我也试过这个。

 def open_exception(*a):
     raise Exception("you can't use open")
 env = {
     'open': open_exception
 }

但还是得到了同样的异常(不是“你不能使用打开”)。

我想要防止:

执行这个:

"""def foo():
     return open('some_file').read()
print foo()"""

并且评估这个。

"open('some_file').write('some text')"

我还使用会话来存储之前评估过的代码,所以我需要防止执行这个:

"""def foo(s):
   return open(s)"""

然后再评估这个。

"foo('some').write('some text')"

我不能使用正则表达式,因为有人可能会在字符串中使用(eval)。

"eval(\"opxx('some file').write('some text')\".replace('xx', 'en')"

有没有办法在执行exec/eval时防止访问文件系统?(我需要这两者)

5 个回答

5

其实这 可以做到的。

也就是说,正如你所描述的那样,在Linux上是可以实现的,这和这里其他的回答不一样。你可以设置一个类似于 exec 的调用,这样可以在一个相对安全的环境中运行不可信的代码,这个环境是比较难被攻破的,并且可以输出结果。不可信的代码根本不能访问文件系统,除了可以读取特定允许的Python虚拟机和标准库的部分。

如果这和你想要的差不多,接着往下看。

我设想的系统是,你的类似 exec 的函数会在一个非常严格的AppArmor配置下启动一个子进程,比如使用 Straitjacket 的那种(可以查看 这里这里)。这将限制所有文件系统的访问,除了那些特别允许读取的文件。这个设置还会限制进程的栈大小、最大数据段大小、最大常驻集大小、CPU时间、可以排队的信号数量以及地址空间大小。进程将完全禁止使用锁定内存、核心、flock/fcntl锁、POSIX消息队列等。如果你想允许在一个临时区域使用大小有限的临时文件,你可以用 mkstemp 创建它,并在特定条件下允许子进程写入(确保绝对禁止硬链接)。你还需要确保清除子进程环境中任何有趣的东西,把它放在一个新的会话和进程组中,并关闭子进程中除了stdin/stdout/stderr以外的所有文件描述符,如果你想允许与这些进行通信的话。

如果你想从不可信的代码中获取一个Python对象,你可以把它包装在一个打印结果的 repr 到标准输出的东西里,检查它的大小后,再用 ast.literal_eval() 进行评估。这会严重限制可以返回的对象类型,但实际上,任何比这些基本类型更复杂的东西都可能带有恶意意图,可能会在你的进程中触发。在任何情况下,你都不应该使用 pickle 作为进程间的通信协议。

5

你无法把 exec()eval() 变成一个安全的沙盒环境。只要 sys 模块可用,你总是能访问到内置模块:

sys.modules[().__class__.__bases__[0].__module__].open

即使 sys 模块不可用,你仍然可以通过类似的方法访问任何导入模块中定义的新式类。这包括 io 模块中的所有输入输出类。

10

在执行代码时,没有办法完全阻止访问文件系统。下面的代码示例展示了用户代码如何调用一些通常被限制的类,这种方法总是有效:

import subprocess
code = """[x for x in ().__class__.__bases__[0].__subclasses__() 
           if x.__name__ == 'Popen'][0](['ls', '-la']).wait()"""
# Executing the `code` will always run `ls`...
exec code in dict(__builtins__=None)

而且,不要想着通过过滤输入来解决问题,尤其是用正则表达式。

你可以考虑一些替代方案:

  1. 如果你只想处理简单的表达式,可以使用 ast.literal_eval
  2. 使用其他语言来运行用户代码。你可以看看 Lua 或 JavaScript,这两种语言有时会在沙箱环境中运行不安全的代码。
  3. 还有一个叫 pysandbox 的项目,不过我不能保证沙箱里的代码真的安全。Python 本身并不是为了沙箱设计的,特别是 CPython 的实现并没有考虑到沙箱的需求。甚至这个项目的作者 似乎也对 安全实现沙箱的可能性表示怀疑。

撰写回答