Python eval(compile(...), 沙箱),globals在沙箱中,除非在def内,为什么?

4 投票
3 回答
3970 浏览
提问于 2025-04-16 09:11

考虑以下内容:

def test(s):
    globals()['a'] = s

sandbox = {'test': test}
py_str = 'test("Setting A")\nglobals()["b"] = "Setting B"'
eval(compile(py_str, '<string>', 'exec'), sandbox)

'a' in sandbox # returns False, !What I dont want!
'b' in sandbox # returns True, What I want
'a' in globals() # returns True, !What I dont want!
'b' in globals() # returns False, What I want

我不知道该怎么问,但我希望一个函数的全局作用域是我打算运行它的环境,而不需要在执行时编译这个函数。这可能吗?

谢谢大家的建议

解决方案

def test(s):
    globals()['a'] = s

sandbox = {}

# create a new version of test() that uses the sandbox for its globals
newtest = type(test)(test.func_code, sandbox, test.func_name, test.func_defaults,
                     test.func_closure)

# add the sandboxed version of test() to the sandbox
sandbox["test"] = newtest

py_str = 'test("Setting A")\nglobals()["b"] = "Setting B"'
eval(compile(py_str, '<string>', 'exec'), sandbox)

'a' in sandbox # returns True
'b' in sandbox # returns True
'a' in globals() # returns False
'b' in globals() # returns False

3 个回答

2

在Python中,外部执行环境是静态定义的(f.func_globals是只读的),所以我觉得你想要的事情是不可能实现的。原因是,如果在运行时改变了函数的定义环境,这个函数可能会变得无效。如果语言允许这样做,那就会给恶意代码注入到库调用中提供一个非常简单的途径。

def mycheck(s): 
    return True

exec priviledged_code in {'check_password':mycheck}
3

exec 代码设置沙盒环境,提供替代的全局和局部变量,有很多需要注意的地方:

  • 这些替代的全局和局部变量只对沙盒里的代码有效。它们不会影响沙盒外的任何东西,也无法影响外面的东西。如果可以影响,那就没什么意义了。

    换句话说,你所谓的“沙盒”是把对象 test 传给了 exec 执行的代码。如果想要改变 test 看到的全局变量,也必须修改这个对象,而不是直接传递它。这样做几乎是不可能的,更别提让这个对象继续正常工作了。

  • 即使使用了替代的全局变量,沙盒里的代码仍然可以看到内置函数。如果你想要隐藏一些或所有的内置函数,需要在你的字典里添加一个 "__builtins__" 键,指向 None(这样会禁用所有内置函数)或者你自己定义的版本。这也会限制某些对象的属性,比如访问函数的 func_globals 属性将会被禁用。

  • 即使你移除了内置函数,沙盒仍然安全。沙盒只适合你本来就信任的代码。

下面是一个简单的概念验证:

import subprocess
code = """[x for x in ().__class__.__bases__[0].__subclasses__() 
           if x.__name__ == 'Popen'][0](['ls', '-la']).wait()"""
# The following runs "ls"...
exec code in dict(__builtins__=None)
# ...even though the following raises
exec "(lambda:None).func_globals" in dict(__builtins__=None)
6

在Python中,当你调用一个函数时,它能看到的全局变量总是定义这个函数的模块里的全局变量。如果不是这样,函数可能会出问题,因为它可能需要一些全局值,而你不一定知道那些值是什么。

使用execeval()指定一个全局变量的字典,只会影响到被execeval()执行的代码所看到的全局变量。

如果你想让一个函数看到其他的全局变量,你确实需要把函数的定义包含在你传给execeval()的字符串里。这样做后,函数的“模块”就是它编译时的字符串,并且有自己的全局变量(也就是你提供的那些)。

你可以通过创建一个新的函数来绕过这个限制,这个新函数的代码对象和你正在调用的函数是一样的,但它的func_globals属性指向你的全局变量字典。不过,这种做法比较复杂,可能不太值得。尽管如此,下面是你可以这样做的方法:

# create a sandbox globals dict
sandbox = {}

# create a new version of test() that uses the sandbox for its globals
newtest = type(test)(test.func_code, sandbox, test.func_name, test.func_defaults,
                     test.func_closure)

# add the sandboxed version of test() to the sandbox
sandbox["test"] = newtest

撰写回答