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

时间:2011-02-24 12:00:50

标签: python filesystems exec eval sandbox

我想禁止从客户端代码访问文件系统,所以我想我可以覆盖open function

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')"

我还使用session来存储之前评估过的代码,所以我需要阻止执行它:

"""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 个答案:

答案 0 :(得分:11)

无法阻止访问exec / eval中的文件系统。这是一个示例代码,演示了用户代码调用始终有效的其他受限类的方法:

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实现并不是用沙盒编写的。甚至作者seems to doubt也可以安全地实施这样的沙箱。

答案 1 :(得分:5)

您无法将exec()eval()转变为安全沙箱。只要sys模块可用,您就可以随时访问内置模块::

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

即使sys不可用,您仍然可以通过基本相同的方式访问任何导入模块中定义的任何新样式类。这包括io中的所有IO类。

答案 2 :(得分:5)

实际上可以完成。

也就是说,实际上你所描述的内容可以在Linux上完成,与此处的其他答案相反。也就是说,您可以实现一种设置,在该设置中您可以进行exec类似的调用,该调用在安全性下运行不受信任的代码,这些代码难以穿透,并且允许输出结果。除了阅读Python vm和标准库的特定允许部分外,不允许不受信任的代码访问文件系统。

如果它足够接近你想要的东西,请继续阅读。

我正在构想一个系统,其中类似exec的函数在非常严格的AppArmor配置文件下生成子流程,例如Straitjacket使用的配置文件(请参阅herehere) 。这将限制内核级别的所有文件系统访问,而不是特别允许读取的文件。这还将限制进程的堆栈大小,最大数据段大小,最大驻留集大小,CPU时间,可排队的信号数以及地址空间大小。该进程将锁定内存,内核,flock / fcntl锁,POSIX消息队列等,完全不允许。如果要允许在临时区域中使用大小受限的临时文件,可以mkstemp它并使其可用于子进程,并允许在某些条件下写入(确保绝对不允许使用硬链接)。您希望确保清除子进程环境中的任何有趣内容并将其放入新的会话和进程组中,并关闭除了stdin / stdout / stderr之外的子进程中的所有FD,如果要允许与那些。

如果您希望能够从不受信任的代码中取出Python对象,可以将其包装在将结果repr打印到stdout的内容中,并在检查其大小后,将其评估为ast.literal_eval()。这严重限制了可以返回的对象的可能类型,但实际上,任何比这些基本类型更复杂的东西都可能存在意图在您的进程中触发的sekrit恶意攻击的可能性。在任何情况下都不应将pickle用于进程之间的通信协议。

答案 3 :(得分:3)

由于@Brian建议覆盖开放不起作用:

def raise_exception(*a):
    raise Exception("you can't use open")

open = raise_exception

print eval("open('test.py').read()", {})

这显示文件的内容但是这个(合并@Brian和@lunaryorn答案)

import sys
def raise_exception(*a):
    raise Exception("you can't use open")

__open = sys.modules['__builtin__'].open
sys.modules['__builtin__'].open = raise_exception

print eval("open('test.py').read()", {})

会抛出这个:

Traceback (most recent call last):
  File "./test.py", line 11, in <module>
    print eval("open('test.py').read()", {})
  File "<string>", line 1, in <module>
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open
Error in sys.excepthook:
Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/apport_python_hook.py", line 48, in apport_excepthook
    if not enabled():
  File "/usr/lib/python2.6/dist-packages/apport_python_hook.py", line 23, in enabled
    conf = open(CONFIG).read()
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open

Original exception was:
Traceback (most recent call last):
  File "./test.py", line 11, in <module>
    print eval("open('test.py').read()", {})
  File "<string>", line 1, in <module>
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open

您可以通过open

访问__open外部用户代码

答案 4 :(得分:1)

“嵌套函数”指的是它在另一个函数内声明,而不是它是一个lambda。在模块的顶层声明你的open覆盖,它应该按照你想要的方式工作。

另外,我认为这不是完全安全的。如果你想沙箱Python,预防open只是你需要担心的事情之一。