zopefoundation/AccessControl

private "special" methods can be (indirectly) accessed by untrusted code

Opened this issue · 1 comments

The code below constructs a class Protected with private keys and __getitem__ methods and shows how to call these methods indirectly from untrusted code:

from AccessControl.tests.testZopeGuards import TestActualPython
t = TestActualPython("testPython")

from AccessControl.class_init import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo
from ExtensionClass import Base

class Protected(Base):
    security = ClassSecurityInfo()
  
    @security.private
    def keys(self):
        return (1, 2, 3)
  
    @security.private
    def __getitem__(self, key):
        return key

InitializeClass(Protected)

protected = Protected()

access_code="""\
d = {}
d.update(protected)
assert d == {1:1, 2:2, 3:3}
"""

code, g = t._compile_str(access_code, __name__)
g["protected"] = protected
exec(code, g)

The same likely applies to many "special" methods called implicitly by Python (such as __bool__, __index__, __iter__, ...) or methods of Python types. The example above uses dict.update; similar unprotected access is likely possible with list.extend (using __iter__ without protection).

Trying to construct examples for a practical security preach I found hints that the problem might have limited practical importance. I concentrated my research on dict.update and list.extend; dict and list are examples of types which can be used in restricted contexts with almost no restrictions. dict.update can call its argument's keys+__getitem__ or __iter__ without the security checks normally applied in restricted contexts; list.extend can do this with __iter__. In both cases, the result of the unprotected calls arrive in a container (dict or list); access to individual elements of such containers is usually protected and typically will fail unless the accessed element is simple (i.e. of an "allowed type") or public.

This means that a security preach could involve an object maintaining sensitive information accessed via "special" methods (e.g. "keys", "getitem", "iter") which essentially only use (potentially recursively) "simple" types/containers.

A constructed example could be a class providing a mapping interface to a user base where each user is described by a dict or public class instance.