在Python中禁止exec和eval访问文件系统
我想禁止客户端代码访问文件系统,所以我想重写打开文件的功能。
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 个回答
其实这 是 可以做到的。
也就是说,正如你所描述的那样,在Linux上是可以实现的,这和这里其他的回答不一样。你可以设置一个类似于 exec
的调用,这样可以在一个相对安全的环境中运行不可信的代码,这个环境是比较难被攻破的,并且可以输出结果。不可信的代码根本不能访问文件系统,除了可以读取特定允许的Python虚拟机和标准库的部分。
如果这和你想要的差不多,接着往下看。
我设想的系统是,你的类似 exec
的函数会在一个非常严格的AppArmor配置下启动一个子进程,比如使用 Straitjacket 的那种(可以查看 这里 和 这里)。这将限制所有文件系统的访问,除了那些特别允许读取的文件。这个设置还会限制进程的栈大小、最大数据段大小、最大常驻集大小、CPU时间、可以排队的信号数量以及地址空间大小。进程将完全禁止使用锁定内存、核心、flock/fcntl锁、POSIX消息队列等。如果你想允许在一个临时区域使用大小有限的临时文件,你可以用 mkstemp
创建它,并在特定条件下允许子进程写入(确保绝对禁止硬链接)。你还需要确保清除子进程环境中任何有趣的东西,把它放在一个新的会话和进程组中,并关闭子进程中除了stdin/stdout/stderr以外的所有文件描述符,如果你想允许与这些进行通信的话。
如果你想从不可信的代码中获取一个Python对象,你可以把它包装在一个打印结果的 repr
到标准输出的东西里,检查它的大小后,再用 ast.literal_eval()
进行评估。这会严重限制可以返回的对象类型,但实际上,任何比这些基本类型更复杂的东西都可能带有恶意意图,可能会在你的进程中触发。在任何情况下,你都不应该使用 pickle
作为进程间的通信协议。
你无法把 exec()
和 eval()
变成一个安全的沙盒环境。只要 sys
模块可用,你总是能访问到内置模块:
sys.modules[().__class__.__bases__[0].__module__].open
即使 sys
模块不可用,你仍然可以通过类似的方法访问任何导入模块中定义的新式类。这包括 io
模块中的所有输入输出类。
在执行代码时,没有办法完全阻止访问文件系统。下面的代码示例展示了用户代码如何调用一些通常被限制的类,这种方法总是有效:
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)
而且,不要想着通过过滤输入来解决问题,尤其是用正则表达式。
你可以考虑一些替代方案: