是否将exec()与自定义类方法/函数一起安全使用?

2024-10-02 10:32:25 发布

您现在位置:Python中文网/ 问答频道 /正文

我写了一个程序,孩子们(12-18岁)可以用它学习用Python编写代码。它的灵感来自一个名为PythonKara(https://www.swisseduc.ch/informatik/karatojava/pythonkara/)的程序。虽然PythonKara是基于Jython的,但我完全用Python、pygame和tkinter编写了我的版本

使用一组给定的命令(如move()、turnlift()、…),用户必须操纵宇宙飞船来完成不同的任务,这些任务(越复杂)也需要Python语法。 该程序有两个窗口。一个窗口显示精灵(pygame窗口),另一个窗口是编辑器(tkinter窗口)

用户输入(self.userInput->;self.userOutput)被发送到pygame窗口的游戏循环,以便使用Python的“exec()”函数执行。为了防止高级用户使用Python模块,例如“os”模块或任何其他可能危害系统的命令,我在执行之前解析用户输入

我的问题是,我的“validateUserCode()”函数是否足以确保安全使用“exec()”函数,还是我必须实施进一步的安全措施

代码说明:

  • editor是创建编辑器的editor()类的实例 并解析用户输入
  • validateUserCode(self)是Editor()类的类方法
  • self.userOutput(用户输入的修改版本)被发送到游戏循环(请参阅try-except块)
  • localvariables中的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

Tags: 函数代码用户命令self程序版本tkinter
2条回答

不,这是不安全的,而且永远不会真正安全。请参阅此快速演示:

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)

Output:

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)

Output:

Applications Users        cores        home         sbin         var
Library      Volumes      dev          opt          tmp
System       bin          etc          private      usr

另外:您正试图将'print('列入黑名单,这仍然使print ("foo")100%可用,这一事实表明您不应该费心尝试实现这一点

以下是我所做的:

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

    restricted_env = {
                      '__builtins__': {},
                      'range': range,
                      'kara': spaceship
                     }
    
    exec(userInput, restricted_env)
    
  4. 我考虑的另一个解决方案是使用pyinstaller打包文件,并在spec文件中排除os。然后我必须重写代码中使用os.pathos.path.join的部分(如果有意义的话)

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

总而言之,我考虑了学校的情况

  1. 给定一台安装了python的计算机——如果所有不需要的操作都可以在python shell中执行,那么在我的自定义编辑器中安全使用exec()是否有意义
  2. 如果一台计算机没有安装python,我将不得不使用pyinstaller来构建一个可执行文件,以便在这样的计算机上运行它。因此,除非它能够从pyinstaller文件夹中的文件中“提取”操作系统模块,并以某种方式使其可访问,假设我上面提到的白名单解决方案足够了,否则我应该很乐意去做,不是吗

相关问题 更多 >

    热门问题