Python eval(compile(...), sandbox),全局变量进入沙箱,除非在 def 中,为什么?

发布于 2024-10-09 19:15:30 字数 1114 浏览 0 评论 0原文

考虑以下事项:

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

Consider the following:

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

I'm not even sure how to ask, but I want the global scope for a function to be the environment I intend to run it in without having to compile the function during the eval. Is this possible?

Thanks for any input

Solution

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

肩上的翅膀 2024-10-16 19:15:30

当您在 Python 中调用函数时,它看到的全局变量始终是定义它的模块的全局变量。(如果这不是真的,该函数可能无法工作 - 它实际上可能需要 一些全局值,而您不一定知道它们是哪些。)使用 execeval() 指定全局变量字典只会影响代码所在的全局变量exec'd 或 eval()'d 看到。

如果您希望函数查看其他全局变量,那么您确实必须在传递给 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

When you call a function in Python, the global variables it sees are always the globals of the module it was defined in. (If this wasn't true, the function might not work -- it might actually need some global values, and you don't necessarily know which those are.) Specifying a dictionary of globals with exec or eval() only affects the globals that the code being exec'd or eval()'d sees.

If you want a function to see other globals, then, you do indeed have to include the function definition in the string you pass to exec or eval(). When you do, the function's "module" is the string it was compiled from, with its own globals (i.e., those you supplied).

You could get around this by creating a new function with the same code object as the one you're calling but a different func_globals attribute that points to your globals dict, but this is fairly advanced hackery and probably not worth it. Still, here's how you'd do it:

# 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
浅忆流年 2024-10-16 19:15:30

通过提供替代全局变量/局部变量来对 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)

Sandboxing code for exec by providing alternative globals/locals has lots of caveats:

  • The alternative globals/locals only apply for the code in the sandbox. They do not affect anything outside of it, they can't affect anything outside of it, and it wouldn't make sense if they could.

    To put it another way, your so-called "sandbox" passes the object test to the code ran by exec. To change the globals that test sees it would also have to modify the object, not pass it as it is. That's not really possible in any way that would keep it working, much less in a way that the object would continue to do something meaningful.

  • By using the alternative globals, anything in the sandbox would still see the builtins. If you want to hide some or all builtins from the code inside the sandbox you need to add a "__builtins__" key to your dictionary that points to either None (disables all the builtins) or to your version of them. This also restricts certain attributes of the objects, for example accessing func_globals attribute of a function will be disabled.

  • Even if you remove the builtins, the sandbox will still not be safe. Sandbox only code that you trust in the first place.

Here's a simple proof of concept:

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)
心安伴我暖 2024-10-16 19:15:30

外部执行上下文是在 Python 中静态定义的(f.func_globals 是只读的),所以我想说你想要的东西是不可能的。原因是如果 Python 的定义上下文在运行时发生更改,该函数可能会变得无效。如果语言允许,这将是向库调用注入恶意代码的极其简单的途径。

def mycheck(s): 
    return True

exec priviledged_code in {'check_password':mycheck}

External execution contexts are defined statically in Python (f.func_globals is read-only), so I would say that what you want is not possible. The reason is because the function could become invalid Python it its definition context is changed at runtime. If the language allowed it, it would be an extremely easy route for injection of malicious code into library calls.

def mycheck(s): 
    return True

exec priviledged_code in {'check_password':mycheck}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文