如何在纯 Python 中实现 Python 沙盒化?
- 2024-12-30 08:41:00
- admin 原创
- 43
问题描述:
我正在用纯 Python 开发一款网页游戏,希望使用一些简单的脚本来实现更动态的游戏内容。特权用户可以实时添加游戏内容。
如果脚本语言是 Python 就好了。但是,它无法在访问游戏运行环境的情况下运行,因为恶意用户可能会造成严重破坏,这很糟糕。是否可以在纯 Python 中运行沙盒 Python?
更新:事实上,由于真正的 Python 支持会有些过度,因此具有 Pythonic 语法的简单脚本语言将是完美的。
如果没有 Pythonic 脚本解释器,有没有其他用纯 Python 编写的开源脚本解释器可供我使用?要求支持变量、基本条件和函数调用(不是定义)。
解决方案 1:
这确实不是一件小事。
有两种方法可以对 Python 进行沙盒处理。一种是创建一个受限制的环境(即很少的全局变量等),并将exec
代码放在这个环境中。这就是 Messa 所建议的。这很好,但是有很多方法可以打破沙盒并制造麻烦。大约一年前,Python-dev 上有一个关于这个问题的帖子,人们在其中做了一些事情,从捕获异常和探索内部状态到打破沙盒并进行字节码操作。如果您想要一种完整的语言,这就是要走的路。
另一种方法是解析代码,然后使用ast
模块剔除不需要的构造(例如导入语句、函数调用等),然后编译其余部分。如果您想使用 Python 作为配置语言等,这就是要走的路。
另一种方法(由于您使用的是 GAE,因此可能不适合您)是PyPy 沙盒。虽然我自己没有用过它,但网上说它是唯一真正的沙盒 Python。
根据您对需求的描述(需求是支持变量、基本条件和函数调用(而不是定义)),您可能需要评估方法 2 并从代码中删除其他所有内容。这有点棘手,但可行。
解决方案 2:
在最初的问题提出大约十年后,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()
。Pythonic 模块喜欢numpy
或pickle
最终使用 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 访问:
也许使用直接 OS 调用的假设编译模块无法通过 Cpython 审核——建议将安全的纯 Pythonic 模块列入白名单。
当然,Cpython 解释器仍然存在崩溃或过载的可能性。
也许还存在一些漏洞可以将文件写入硬盘。但我无法使用任何常见的沙盒规避技巧来写入单个字节。我们可以说 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
解决方案 3:
我不知道为什么没有人提到这一点,但是 Zope 2 有一个叫做 Python 脚本的东西,它就是这样 - 在沙箱中执行的受限 Python,无法访问文件系统,但可以访问由 Zope 安全机制控制的其他 Zope 对象,并且导入仅限于安全子集。
Zope 总体来说是相当安全的,因此我认为没有已知或明显的方法可以突破沙箱。
我不确定 Python 脚本是如何实现的,但该功能自 2000 年就已经存在了。
这里是 PythonScripts 背后的魔力,并附有详细的文档:http://pypi.python.org/pypi/RestrictedPython - 它甚至看起来不依赖 Zope,因此可以独立使用。
请注意,这并不是为了安全地运行任意的 Python 代码(大多数随机脚本在第一次导入或文件访问时都会失败),而是为了在 Python 应用程序中使用 Python 进行有限的脚本编写。
这个答案来自我对一个问题的评论,该问题已被关闭,因为它是这个问题的重复:Python 中的 Python:限制功能?
解决方案 4:
更新:此技术不会阻止创建自定义代码对象。请参阅评论。
据我所知,可以在完全隔离的环境中运行代码:
exec somePythonCode in {'__builtins__': {}}, {}
但是在这样的环境中你几乎什么也做不了:)(你甚至不能使用import
一个模块;但恶意用户仍然可以运行无限递归或导致内存耗尽。)可能你想要添加一些模块作为你游戏引擎的接口。
解决方案 5:
我会研究双服务器方法。第一台服务器是您的代码所在的特权 Web 服务器。第二台服务器是受到严格控制的服务器,它仅提供 Web 服务或 RPC 服务并运行不受信任的代码。您为内容创建者提供自定义界面。例如,如果您允许最终用户创建项目,您将查找调用服务器的查询,其中包含要执行的代码和一组参数。
这是治疗药水的抽象例子。
{function_id='healing potion', action='use', target='self', inventory_id='1234'}
响应可能类似于
{hp='+5' action={destroy_inventory_item, inventory_id='1234'}}
解决方案 6:
您可以简单地禁止“dunder”访问并限制内置函数和其他全局函数:
if "__" not in code:
eval(code, {'__builtins__': {}}, {});
所有规避沙箱的机制都需要 dunder 访问权限。此时,您可以小心地重新添加您希望用户有权访问的全局变量(甚至允许的导入)。
例如:
if "__" not in code:
eval(code, {'__builtins__': {'__import__': my_safe_importer}}, {});
净化 + 限制应该足够了。我读过很多关于逃避沙箱的博客和文章,100% 的技术都使用了 dunder 访问。
另外,此模块采用更细致和经过测试的方法为您完成艰苦的工作(防止 dunder 访问):
https://restrictedpython.readthedocs.io/en/latest/
维护得很好:
from RestrictedPython import compile_restricted
source_code = "1+1"
byte_code = compile_restricted(
source_code,
filename='<inline code>',
mode='eval'
)
eval(byte_code, {'__builtins__': {}}, {})
解决方案 7:
嗯。这是一个思想实验,我不知道有人做过:
您可以将compiler
包用于parse
脚本。然后,您可以遍历这棵树,为所有标识符(变量、方法名称等(还有has|get|setattr
调用等))添加唯一的前缀,以便它们不可能引用您的变量。您还可以确保包compiler
本身没有被调用,也许还有其他列入黑名单的东西,例如打开文件。然后,您为此发出 python 代码,然后compiler.compile
它。
文档指出该compiler
包不在 Python 3.0 中,但没有提到 3.0 的替代品是什么。
总的来说,这与论坛软件等尝试将“安全”的 Javascript 或 HTML 等列入白名单的方式类似,而且它们在阻止所有逃逸方面有着不良记录。但使用 Python 可能会更幸运 :)
解决方案 8:
我认为最好的选择是综合迄今为止的所有答复。
您需要解析并清理输入 - 例如删除所有导入语句。
然后,您可以使用 Messa 的exec示例(或类似内容)来允许仅针对您选择的内置变量执行代码 - 很可能是您自己定义的某种 API,可为程序员提供对您认为相关的功能的访问权限。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件