使用自定义类方法/函数安全地使用 exec()?



我写了一个程序,孩子们(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% 可用,这表明您不应该费心尝试完成此操作。

这就是我所做的:

  1. 我直接在我的程序编辑器(python 3.8(中测试了ruohola的代码。起初它不起作用,因为在exec()__builtins__似乎已经是对builtins.__dict__的引用,所以在我修改代码以imp = __builtins__[f"__{''.join(map(chr,(105,109,112,111,114,116)))}__"]一切都按预期工作之后,它当然很容易绕过我的弱验证函数。
  2. 我摆脱了我的验证功能,并坚持 ruohola 的建议,不要费心尝试完成基于"字符串验证"的黑名单。除了ruohola的代码示例之外,我还发现了一些更有趣的方法来利用exec()的使用,遵循nneonneo关于如何沙箱python的链接。
  3. 由于学生只需要一小部分 Python 的builtins函数,经过更多的研究,我选择了白名单方法,并将__builtins__设置为空字典,从而禁用其在exec()中的使用。所以现在 ruohola 的代码不再工作,因为无法使用import. 问题是这种方法是否足够(参见4(。

    restricted_env = {
    '__builtins__': {},
    'range': range,
    'kara': spaceship
    }
    exec(userInput, restricted_env)
    
  4. 我想到的另一种解决方案是使用 pyinstaller 打包文件并排除规范文件中的操作系统。然后,我将不得不重写代码中我使用os.pathos.path.join的部分(如果这有意义吗?

如果我理解正确 - 使用exec()的主要威胁是可以使用os模块,从而能够访问和删除(重要(文件(即与操作系统相关(。但是无法访问其他用户的文件还是无法访问?

综上所述,我想到了学校的情况。

  1. 给定一台安装了 python 的计算机 - 如果所有不需要的操作都可以在 Python shell 中进行,那么在我的自定义编辑器中安全使用exec()是否有意义?
  2. 给定一台未安装 python 的计算机 - 我必须使用例如 pyinstaller 来构建可执行文件以在这样的计算机上运行它。因此,除非它能够从pyinstaller文件夹中的文件"提取"os模块并使其以某种方式可访问,否则假设我上面提到的白名单解决方案就足够了,否则我应该很高兴,不是吗?

相关内容

  • 没有找到相关文章

最新更新