Python eval(compile(...), 沙箱),globals在沙箱中,除非在def内,为什么?
考虑以下内容:
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 个回答
在Python中,外部执行环境是静态定义的(f.func_globals
是只读的),所以我觉得你想要的事情是不可能实现的。原因是,如果在运行时改变了函数的定义环境,这个函数可能会变得无效。如果语言允许这样做,那就会给恶意代码注入到库调用中提供一个非常简单的途径。
def mycheck(s):
return True
exec priviledged_code in {'check_password':mycheck}
给 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)
在Python中,当你调用一个函数时,它能看到的全局变量总是定义这个函数的模块里的全局变量。如果不是这样,函数可能会出问题,因为它可能需要一些全局值,而你不一定知道那些值是什么。
使用exec
或eval()
指定一个全局变量的字典,只会影响到被exec
或eval()
执行的代码所看到的全局变量。
如果你想让一个函数看到其他的全局变量,你确实需要把函数的定义包含在你传给exec
或eval()
的字符串里。这样做后,函数的“模块”就是它编译时的字符串,并且有自己的全局变量(也就是你提供的那些)。
你可以通过创建一个新的函数来绕过这个限制,这个新函数的代码对象和你正在调用的函数是一样的,但它的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