如何在纯Python中沙盒Python?

时间:2010-06-18 08:28:48

标签: python scripting

我正在开发纯Python的网页游戏,并希望提供一些简单的脚本来实现更加动态的游戏内容。游戏内容可以由特权用户实时添加。

如果脚本语言可能是Python,那就太好了。但是,由于恶意用户可能会造成严重破坏,因此无法在访问游戏运行环境的情况下运行。是否可以在纯Python中运行沙盒Python?

更新:事实上,由于真正的Python支持会有点过分,因此使用Pythonic语法的简单脚本语言将是完美的。

如果没有任何Pythonic脚本解释器,是否有任何其他用纯Python编写的开源脚本解释器,我可以使用?要求是对变量,基本条件和函数调用(不是定义)的支持。

9 个答案:

答案 0 :(得分:43)

这真的非常重要。

沙箱Python有两种方法。一种是在此环境中创建受限制的环境(即极少数全局变量等)和exec您的代码。这就是梅萨所暗示的。这很好但是有很多方法可以打破沙箱并制造麻烦。一年前在Python-dev上有一个关于这个问题的线程,其中人们通过捕获异常并在内部状态中进行操作来突破字节代码操作。如果你想要一个完整的语言,这就是你要走的路。

另一种方法是解析代码,然后使用ast模块踢出你不想要的构造(例如import语句,函数调用等),然后编译其余的。如果你想用Python作为配置语言等,这就是你要走的路。

另一种方式(因为你使用GAE可能对你不起作用)是PyPy sandbox。虽然我自己没有使用它,但是对于intertubes而言,它是唯一的真正的沙盒Python。

根据您对需求的描述(需求是对变量,基本条件和函数调用(不是定义)的支持),您可能想要评估方法2并从中获取其他所有内容码。这有点棘手但可行。

答案 1 :(得分:5)

AFAIK可以在完全隔离的环境中运行代码:

exec somePythonCode in {'__builtins__': {}}, {}

但是在这样的环境中你几乎什么也做不了:)(你甚至不能import一个模块;但是仍然是恶意用户可以运行无限递归或导致内存不足。)可能你想要添加一些模块作为游戏引擎的接口。

答案 2 :(得分:4)

我不确定为什么没有人提到这一点,但是Zope 2有一个名为Python Script的东西,就是那个 - 在沙箱中执行的受限制的Python,没有任何文件系统访问权限,可以访问由Zope控制的其他Zope对象安全机械,进口仅限于安全子集。

Zope一般来说非常安全,所以我认为没有已知或明显的方法可以突破沙箱。

我不确定Python Scripts是如何实现的,但是这个功能就像2000年一样。

这是PythonScripts背后的魔力,详细文档:http://pypi.python.org/pypi/RestrictedPython - 它甚至看起来没有任何Zope依赖,所以可以单独使用。

请注意,这不是为了安全地运行任意python代码(大多数随机脚本在首次导入或文件访问时都会失败),而是在Python应用程序中使用Python进行有限的脚本编写。

这个答案来自我对一个问题的评论,该问题与此问题相同:Python from Python: restricting functionality?

答案 3 :(得分:3)

我会研究一种双服务器方法。第一台服务器是您的代码所在的特权Web服务器。第二个服务器是一个受到严格控制的服务器,它只提供Web服务或RPC服务并运行不受信任的代码。您为内容创建者提供自定义界面。例如,如果您允许最终用户创建项目,那么您将查找调用服务器,其中包含要执行的代码和参数集。

这是一个治疗药水的抽象例子。

{function_id='healing potion', action='use', target='self', inventory_id='1234'}

响应可能类似于

{hp='+5' action={destroy_inventory_item, inventory_id='1234'}}

答案 4 :(得分:3)

最初提出问题大约十年后,Python 3.8.0附带了审核。能帮上忙吗?为了简单起见,我们将讨论范围限于硬盘驱动器的编写,请参见:

from sys import addaudithook
def block_mischief(event,arg):
    if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r') 
            or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']): raise IOError('file write forbidden')

addaudithook(block_mischief)

到目前为止exec可以轻松地写入磁盘:

exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))

但是我们可以随意禁止它,这样任何邪恶的用户都无法通过提供给exec()的代码访问磁盘。像numpypickle这样的Pythonic模块最终会使用Python的文件访问权限,因此它们也被禁止进行磁盘写操作。外部程序调用也已明确禁用。

WRITE_LOCK = True
exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("open('/tmp/FILE','a').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("numpy.savetxt('/tmp/FILE', numpy.eye(3))", dict(locals()))
exec("import subprocess; subprocess.call('echo PWNED >> /tmp/FILE', shell=True)",     dict(locals()))

尝试从exec()内删除锁似乎是徒劳的,因为审计挂钩使用locals的另一副本,而exec运行的代码无法访问该副本。请证明我错了。

exec("print('muhehehe'); del WRITE_LOCK; open('/tmp/FILE','w')", dict(locals()))
...
OSError: file write forbidden

当然,顶层代码可以再次启用文件I / O。

del WRITE_LOCK
exec("open('/tmp/FILE','w')", dict(locals()))

事实证明,Cpython中的沙箱操作非常困难,以前的许多尝试都失败了。这种方法也不是完全安全的,例如。用于公共Web访问:

  1. 也许使用直接OS调用的假设编译模块不能由Cpython审核-建议将安全的纯pythonic模块列入白名单。

  2. 肯定有崩溃或超载Cpython解释器的可能性。

  3. 也许还存在一些漏洞,无法将文件写入硬盘。但是我无法使用任何常见的沙盒规避技巧来写一个字节。我们可以说Python生态系统的“攻击面”减少到(禁止)的事件的范围很窄:https://docs.python.org/3/library/audit_events.html

我将感谢任何指出我这种方法的缺陷的人。


编辑:所以这也不是不安全!我非常感谢@Emu对他使用异常捕获和自省的巧妙技巧:

#!/usr/bin/python3.8
from sys import addaudithook
def block_mischief(event,arg):
    if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r') or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']):
        raise IOError('file write forbidden')

addaudithook(block_mischief)
WRITE_LOCK = True
exec("""
import sys
def r(a, b):
    try:
        raise Exception()
    except:
        del sys.exc_info()[2].tb_frame.f_back.f_globals['WRITE_LOCK']
import sys
w = type('evil',(object,),{'__ne__':r})()
sys.audit('open', None, w)
open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')""", dict(locals()))

我想审核 + 子处理是可行的方法,但不要在生产机器上使用它:

https://bitbucket.org/fdominec/experimental_sandbox_in_cpython38/src/master/sandbox_experiment.py

答案 5 :(得分:2)

嗯。这是一个思想实验,我不知道它是在做什么:

您可以使用compiler包来parse脚本。然后,您可以遍历此树,为所有标识符添加前缀 - 变量,方法名称e.t.c. (还有has|get|setattr调用等) - 使用唯一的前导码,以便它们不可能引用您的变量。您还可以确保未调用compiler包本身,也可能确保其他列入黑名单的内容,例如打开文件。然后,您为此发出python代码,并compiler.compile它。

文档指出compiler包不在Python 3.0中,但没有提到3.0替代方案。

一般来说,这与论坛软件如何尝试将“安全”Javascript或HTML e.t.c列入白名单是平行的。他们在历史上有一个糟糕的记录,踩踏所有的逃脱。但是你可能会对Python有更多的好运:)

答案 6 :(得分:2)

您可能会对libref的Python language services部分感兴趣,因为您可以编写自己的解析器。

答案 7 :(得分:0)

您会在this wiki page中找到一些想法,但看起来并不容易。

答案 8 :(得分:0)

我认为你最好的选择是迄今为止的回复。

您需要解析和清理输入 - 例如删除任何import语句。

然后您可以使用Messa的exec示例(或类似的东西)来允许代码执行仅针对您选择的内置变量 - 很可能是您自己定义的某种API,它为程序员提供了对功能的访问你认为相关。