我写了一个程序,孩子们(12-18岁(可以用Python学习编码。它的灵感来自一个名为PythonKara(https://www.swisseduc.ch/informatik/karatojava/pythonkara/(的程序。虽然PythonKara基于Jython,但我完全用Python,pygame和tkinter编写了我的版本。
使用一组给定的命令(如move((,turnLeft((,...(,用户必须操纵宇宙飞船来完成不同的任务(他们越复杂(也需要Python语法。 该程序有两个窗口。一个窗口显示精灵(pygame窗口(,另一个是编辑器(tkinter窗口(。
用户输入(self.userInput -> self.userOutput(被发送到pygame窗口的游戏循环,以便使用Python的">exec(("函数执行。为了防止高级用户使用 Python 模块,例如"os"模块或任何其他可能危及系统的命令,我在执行用户输入之前对其进行解析。
我的问题是我的">validateUserCode(("函数是否足以确保">exec(("函数的安全使用,或者我是否必须实施进一步的安全措施?
代码说明:
编辑器- 是创建编辑器的 Editor(( 类的实例 并解析用户输入
- validateUserCode(self( 是 Editor(( 类的类方法
- self.user输出(用户输入的修改版本(被发送到游戏循环(请参阅尝试 - 除了块(
- 在exec((的局部变量中,只有处理与宇宙飞船相关的所有操作的 Spaceship 类实例被传递给ecec((函数
- 除了:处理各种异常,为了清楚起见,我只写了 pass
def validateUserCode(self):
unsupported_commands = ['import ','print(', 'with ', '.close(', '.read(', '.readline(', 'open(']
for command in unsupported_commands:
if command in self.userOutput:
raise UnsupportedCommandError(command)
主循环代码(pygame窗口(
try:
user_code = editor.validateUserCode()
exec(user_code, local_variables)
except:
pass
不,这并不安全,而且永远不会真正安全。请参阅此快速演示:
def validateUserCode(user_code):
unsupported_commands = ['import ','print(', 'with ', '.close(', '.read(', '.readline(', 'open(']
for command in unsupported_commands:
if command in user_code:
raise ValueError(command)
user_code = """
imp = __builtins__.__dict__[f"__{''.join(map(chr,(105,109,112,111,114,116)))}__"]
os = imp("os")
os.system("ls /")
"""
validateUserCode(user_code)
exec(user_code)
输出:
Applications Users cores home sbin var Library Volumes dev opt tmp System bin etc private usr
或者,如果你愿意,可以更模糊一点:
def validateUserCode(user_code):
unsupported_commands = ['import ','print(', 'with ', '.close(', '.read(', '.readline(', 'open(']
for command in unsupported_commands:
if command in user_code:
raise ValueError(command)
user_code = """
f=lambda*a,l=(95,)*2:''.join(map(chr,(*l,*a,*l)))
imp = getattr(globals()[f(98,117,105,108,116,105,110,115)],f(100,105,99,116))[f(105,109,112,111,114,116)]
os = imp("os")
os.system("ls /")
"""
validateUserCode(user_code)
exec(user_code)
输出:
Applications Users cores home sbin var Library Volumes dev opt tmp System bin etc private usr
另外:您正在尝试将'print('
列入黑名单,这仍然print ("foo")
100% 可用,这表明您不应该费心尝试完成此操作。
这就是我所做的:
- 我直接在我的程序编辑器(python 3.8(中测试了ruohola的代码。起初它不起作用,因为在
exec()
__builtins__
似乎已经是对builtins.__dict__
的引用,所以在我修改代码以imp = __builtins__[f"__{''.join(map(chr,(105,109,112,111,114,116)))}__"]
一切都按预期工作之后,它当然很容易绕过我的弱验证函数。 - 我摆脱了我的验证功能,并坚持 ruohola 的建议,不要费心尝试完成基于"字符串验证"的黑名单。除了ruohola的代码示例之外,我还发现了一些更有趣的方法来利用
exec()
的使用,遵循nneonneo关于如何沙箱python的链接。 -
由于学生只需要一小部分 Python 的
builtins
函数,经过更多的研究,我选择了白名单方法,并将__builtins__
设置为空字典,从而禁用其在exec()
中的使用。所以现在 ruohola 的代码不再工作,因为无法使用import
. 问题是这种方法是否足够(参见4(。restricted_env = { '__builtins__': {}, 'range': range, 'kara': spaceship } exec(userInput, restricted_env)
-
我想到的另一种解决方案是使用 pyinstaller 打包文件并排除规范文件中的操作系统。然后,我将不得不重写代码中我使用
os.path
和os.path.join
的部分(如果这有意义吗?
如果我理解正确 - 使用exec()
的主要威胁是可以使用os模块,从而能够访问和删除(重要(文件(即与操作系统相关(。但是无法访问其他用户的文件还是无法访问?
综上所述,我想到了学校的情况。
- 给定一台安装了 python 的计算机 - 如果所有不需要的操作都可以在 Python shell 中进行,那么在我的自定义编辑器中安全使用
exec()
是否有意义? - 给定一台未安装 python 的计算机 - 我必须使用例如 pyinstaller 来构建可执行文件以在这样的计算机上运行它。因此,除非它能够从pyinstaller文件夹中的文件"提取"os模块并使其以某种方式可访问,否则假设我上面提到的白名单解决方案就足够了,否则我应该很高兴,不是吗?