zopefoundation / RestrictedPython

A restricted execution environment for Python to run untrusted code.
http://restrictedpython.readthedocs.io/
Other
470 stars 38 forks source link

Can't restrict open() if open() is used in a function #210

Closed eswxxxa closed 3 years ago

eswxxxa commented 3 years ago

i am learning restrictedPython, and i found open() can't be restricted if it is used in a function. i used the python version is 3.9.2 the below is my test code: a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins)

with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/b.py', 'exec') exec (byte_code, _globals, {}) b.py: open("/flash/c.py", 'w')

test output is normal: Traceback (most recent call last): File "/flash/a.py", line 8, in exec (byte_code, _globals, {}) File "/flash/b.py", line 1, in open("/flash/c.py", 'w') NameError: name 'open' is not defined

then i modify the b.py: def heal_test(): open("/flash/c.py", 'w')

the test result can pass.

what should i do to restrict the open() if it is used in a function? thanks.

d-maurer commented 3 years ago

eswxxxa wrote at 2021-6-8 19:16 -0700:

i am learning restrictedPython, and i found open() can't be restricted if it is used in a function the below is my test code: a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins)

with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/liu.py', 'exec') exec (byte_code, _globals, {}) b.py: open("/flash/c.py", 'w')

test output is normal: Traceback (most recent call last): File "/flash/a.py", line 8, in exec (byte_code, _globals, {}) File "/flash/b.py", line 1, in open("/flash/c.py", 'w') NameError: name 'open' is not defined

then i modify the b.py: def heal_test(): open("/flash/hua.py", 'w')

the test result can pass.

The restriction happens when the function is called (not when it is defined).

RestrictedPython controls access to builtins (such as open) via safe_builtins. When a function is defined names are not accessed but only classified as either global, local or non-local. The actual access happens when the function is called -- and then it will be detected that open is not in safe_builtins.

eswxxxa commented 3 years ago

hi d-maurer, thanks for your clarification. i called this function in another file without restrictedPython, i used a.py to ensure the b.py

eswxxxa wrote at 2021-6-8 19:16 -0700: i am learning restrictedPython, and i found open() can't be restricted if it is used in a function the below is my test code: a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins) with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/liu.py', 'exec') exec (byte_code, _globals, {}) b.py: open("/flash/c.py", 'w') test output is normal: Traceback (most recent call last): File "/flash/a.py", line 8, in exec (byte_code, _globals, {}) File "/flash/b.py", line 1, in open("/flash/c.py", 'w') NameError: name 'open' is not defined then i modify the b.py: def heal_test(): open("/flash/hua.py", 'w') the test result can pass. The restriction happens when the function is called (not when it is defined). RestrictedPython controls access to builtins (such as open) via safe_builtins. When a function is defined names are not accessed but only classified as either global, local or non-local. The actual access happens when the function is called -- and then it will be detected that open is not in safe_builtins.

thanks d-maurer for your clarification. the function is called in another file without restrictedPython, i used a.py to ensure the b.py don't include open(). if exist open(), the another file can not be executived. in this case, what should i do to ensure b.py don't include open()?

icemac commented 3 years ago

what should i do to ensure b.py don't include open()?

Why would you do this? Your test shows that it cannot be used in the RestrictedPython environment.

d-maurer commented 3 years ago

eswxxxa wrote at 2021-6-8 23:14 -0700:

hi d-maurer, thanks for your clarification. i called this function in another file without restrictedPython, i used a.py to ensure the b.py

This is not how RestrictedPython works: What globals (among them builtins) are available to compiled code (whether compiled "normally" or with RestrictedPython) is determined by the globals parameter you pass to exec/eval. If you do not want open to be usable, to not include it in this dict nor its __builtins__.

RestrictedPython is essentially using a specialized compilation which replaces some elementary Python operations (essentially attribute and item access) by function calls such that they can be controlled externally (by putting appropriate definitions for those functions into the globals parameter).

... what should i do to ensure b.py don't include open()?

RestrictedPython by itself does not support this.

Of course, you can (in principle) derive your own version of RestrictedPython which does this. RestrictedPython uses an AST (= "Abstract Syntax Tree", i.e. the abstract representation of a piece of Python code) transformation to modify the code such that its execution can (to some extent) by controlled externally. You could start with this transformation and add more restrictions or control possibilities.

Note however, that the policy you implement should not be based on names (alone): the "open" functionality can easily be provided via a different name: you need a complete and carefull view on the whole context to develop a (quite) safe policy.

eswxxxa commented 3 years ago

ok i know. thanks for your support.

eswxxxa commented 3 years ago

when i call this function, i found open() is still available. i used the python version is 3.9.2, restrictedPython is 5.1

a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins)

with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/b.py', 'exec') exec (byte_code, _globals, {}) from b import heal_test heal_test()

b.py: def heal_test(): open("/flash/c.py", 'w')

d-maurer commented 3 years ago

eswxxxa wrote at 2021-6-30 00:37 -0700:

when i call this function, i found open() is still available.

Your observation is wrong: you do not call the restricted function but the unrestriced one. Details below.

i used the python version is 3.9.2, restrictedPython is 5.1

a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins)

with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/b.py', 'exec') exec (byte_code, _globals, {})

The restricted function would be found in the "locals" passed down to the exec.

from b import heal_test

This imports b and compiles it - unrestricted! From it, you get the unrestricted function heal_test.

heal_test()

... and call it here.

eswxxxa commented 3 years ago

eswxxxa wrote at 2021-6-30 00:37 -0700: when i call this function, i found open() is still available. Your observation is wrong: you do not call the restricted function but the unrestriced one. Details below. i used the python version is 3.9.2, restrictedPython is 5.1 a.py: from RestrictedPython import compile_restricted from RestrictedPython.Guards import safe_builtins _globals = dict(builtins=safe_builtins) with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, '/flash/b.py', 'exec') exec (byte_code, _globals, {}) The restricted function would be found in the "locals" passed down to the exec. from b import heal_test This imports b and compiles it - unrestricted! From it, you get the unrestricted function heal_test. heal_test() ... and call it here.

hi d-maurer, i modify the a.py, but the open() is still available. Could you give me a correct example? thanks very much a.py:

from RestrictedPython import compile_restricted
from RestrictedPython.Guards import safe_builtins

_builtins = dict(__builtins__=safe_builtins)
_builtins['__import__'] = __import__

_globals = globals()
_globals['__builtins__'] = _builtins

with open("/flash/b.py", 'r') as f:
    src = f.read()
    byte_code = compile_restricted(src, "/flash/b.py", 'exec')
    exec (byte_code, _globals)
    heal_test()
d-maurer commented 3 years ago

eswxxxa wrote at 2021-7-1 01:30 -0700:

hi d-maurer,

Try:

... with open("/flash/b.py", 'r') as f: src = f.read() byte_code = compile_restricted(src, "/flash/b.py", 'exec') locals = {} exec (byte_code, _globals, locals) locals["heal_test"]()

To successfully (and safely) use RestrictedPython, you must understand how Python works:

eswxxxa commented 3 years ago

hi d-maurer, i used your code and the open() is still worked and the c.py is created bash-4.3# python3 a.py bash-4.3# bash-4.3# ls a.py b.py c.py bash-4.3#

d-maurer commented 3 years ago

eswxxxa wrote at 2021-7-1 02:52 -0700:

hi d-maurer, i used your code and the open() is still worked and the c.py is created

>>> from RestrictedPython import compile_restricted
>>> from RestrictedPython.Guards import safe_builtins
>>>
>>> G = globals().copy()
>>> G["__builtins__"] = safe_builtins
>>> src = "def f(): return open"
>>>
>>> code = compile_restricted(src, "<string>", "exec")
>>> L = {}
>>> exec(code, G, L)
>>> L["f"]()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in f
NameError: name 'open' is not defined

As it should be.

eswxxxa commented 3 years ago

ok, d-maurer , thanks for your support.