简体   繁体   中英

Python eval(compile(…), sandbox), globals go in sandbox unless in def, why?

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

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 (ie, 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

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}

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM