zopefoundation / RestrictedPython

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

RestrictedPython does not catch print statements within function of a Class being imported #250

Closed VikramShenoy97 closed 1 year ago

VikramShenoy97 commented 1 year ago

BUG

What I did:

from RestrictedPython import compile_restricted

code = """
from TestClass import TestClass

obj = TestClass()

obj.set("message")

"""

safe_builtins = {}
safe_builtins["_getattr_"] = getattr
safe_builtins["__import__"] = __import__

restricted_globals = dict(__builtins__= safe_builtins)
restricted_locals = {}
byte_code = compile_restricted(code, "<string>", 'exec')

exec(byte_code, restricted_globals, restricted_locals)
class TestClass:
    def __init__(self):
        self.messages = []

    def set(self, message):
        print("Setting messages")
        self.messages.append(message)

    def view(self):
        return self.messages

What I expect to happen:

NameError: name '_print_' is not defined

What actually happened:

Setting messages

What version of Python and Zope/Addons I am using:

Python 3.9.6

loechel commented 1 year ago

@VikramShenoy97 thanks for your report.

I am not 100 % sure I get your setup, but as far as I understood the class definition of TestClass happens outsite of RestrictedPython Scope. TestClass is actually in Unrestricted Scope so will not be processed via AST modification to ensure a safe environment.

So there is a difference between:

from RestrictedPython import compile_restricted

class TestClass:
    def __init__(self):
        self.messages = []

    def set(self, message):
        print("Setting messages")
        self.messages.append(message)

    def view(self):
        return self.messages

code = """
from TestClass import TestClass

obj = TestClass()

obj.set("message")

"""

safe_builtins = {}
safe_builtins["_getattr_"] = getattr
safe_builtins["__import__"] = __import__

restricted_globals = dict(__builtins__= safe_builtins)
restricted_locals = {}
byte_code = compile_restricted(code, "<string>", 'exec')

exec(byte_code, restricted_globals, restricted_locals)

and a restricted scope of:

from RestrictedPython import compile_restricted

code = """
class TestClass:
    def __init__(self):
        self.messages = []

    def set(self, message):
        print("Setting messages")
        self.messages.append(message)

    def view(self):
        return self.messages

obj = TestClass()

obj.set("message")

"""

safe_builtins = {}
safe_builtins["_getattr_"] = getattr
safe_builtins["__import__"] = __import__

restricted_globals = dict(__builtins__= safe_builtins)
restricted_locals = {}
byte_code = compile_restricted(code, "<string>", 'exec')

exec(byte_code, restricted_globals, restricted_locals)

First will accept TestClass as secure from outside, the second will process it in RestrictedPython and will not print.

icemac commented 1 year ago

I am closing this old issue. Feel free to re-open it if there are any news about the topic.