CVE-2021-32807: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The module `AccessControl` defines security policies for Python code used in restricted code within Zope applications. Restricted code is any code that resides in Zope's object database, such as the contents of `Script (Python)` objects. The policies defined in `AccessControl` severely restrict access to Python modules and only exempt a few that are deemed safe, such as Python's `string` module. However, full access to the `string` module also allows access to the class `Formatter`, which can be overridden and extended within `Script (Python)` in a way that provides access to other unsafe Python libraries. Those unsafe Python libraries can be used for remote code execution. By default, you need to have the admin-level Zope "Manager" role to add or edit `Script (Python)` objects through the web. Only sites that allow untrusted users to add/edit these scripts through the web - which would be a very unusual configuration to begin with - are at risk. The problem has been fixed in AccessControl 4.3 and 5.2. Only AccessControl versions 4 and 5 are vulnerable, and only on Python 3, not Python 2.7. As a workaround, a site administrator can restrict adding/editing `Script (Python)` objects through the web using the standard Zope user/role permission mechanisms. Untrusted users should not be assigned the Zope Manager role and adding/editing these scripts through the web should be restricted to trusted users only. This is the default configuration in Zope.
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 7.2
Published: 2021-07-30T22:15Z
Repository: https://github.com/zopefoundation/AccessControl
Language: Python
Files changed: CHANGES.rst, src/AccessControl/ZopeGuards.py, src/AccessControl/tests/testZopeSecurityPolicy.py
Commit date: 2021-07-30 09:25:07 +0200
Commit message: Merge pull request from GHSA-qcx9-j53g-ccgf


Diff: diff --git a/CHANGES.rst b/CHANGES.rst
index b6b527e..3fd47fe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,11 +4,12 @@ Changelog
 For changes before version 3.0, see ``HISTORY.rst``.
 
 5.1 (unreleased)
 ----------------
 
-- Nothing changed yet.
+- Fix a remote code execution issue by preventing access to
+  ``string.Formatter`` from restricted code.
 
 
 5.0 (2020-10-07)
 ----------------
 
diff --git a/src/AccessControl/ZopeGuards.py b/src/AccessControl/ZopeGuards.py
index 1ea2ca2..57add77 100644
--- a/src/AccessControl/ZopeGuards.py
+++ b/src/AccessControl/ZopeGuards.py
@@ -28,10 +28,11 @@ from RestrictedPython.Guards import guarded_iter_unpack_sequence
 from RestrictedPython.Guards import guarded_unpack_sequence
 from RestrictedPython.Guards import safe_builtins
 from RestrictedPython.Utilities import utility_builtins
 from zExceptions import Unauthorized
 
+from AccessControl.SecurityInfo import ModuleSecurityInfo
 from AccessControl.SecurityInfo import secureModule
 from AccessControl.SecurityManagement import getSecurityManager
 from AccessControl.SimpleObjectPolicies import ContainerAssertions
 from AccessControl.SimpleObjectPolicies import Containers
 from AccessControl.SimpleObjectPolicies import allow_type
@@ -55,10 +56,17 @@ with warnings.catch_warnings():
 # Allow access to unprotected attributes
 string.__allow_access_to_unprotected_subobjects__ = 1
 math.__allow_access_to_unprotected_subobjects__ = 1
 random.__allow_access_to_unprotected_subobjects__ = 1
 
+# Mark some unprotected module attributes as private, these should not be
+# used in untrusted Python code such as Scripts (Python)
+string_modsec = ModuleSecurityInfo('string')
+for name in ('Formatter', 'Template'):
+    string_modsec.declarePrivate(name)  # NOQA: D001
+secureModule('string')
+
 # AccessControl.Implementation inserts these names into this module as
 # module globals:  aq_validate, guarded_getattr
 
 
 def initialize(impl):
diff --git a/src/AccessControl/tests/testZopeSecurityPolicy.py b/src/AccessControl/tests/testZopeSecurityPolicy.py
index c2a0063..c81face 100644
--- a/src/AccessControl/tests/testZopeSecurityPolicy.py
+++ b/src/AccessControl/tests/testZopeSecurityPolicy.py
@@ -276,10 +276,23 @@ class ZopeSecurityPolicyTestBase(unittest.TestCase):
         self.assertPolicyDenies(SimpleClass(), 'attr')
         c = SimpleClass()
         c.attr = PublicMethod()
         self.assertPolicyAllows(c, 'attr')
 
+    def testAccessToStringModule(self):
+        # The string module is available to restricted code and its members are
+        # explicitly allowed via a
+        # ``__allow_access_to_unprotected_subobjects__`` declaration. However,
+        # a few classes are exempted and declared private, they can indirectly
+        # provide uncontrolled access to system libraries from within
+        # restricted code.
+        import string
+
+        self.assertPolicyAllows(string, 'printable')
+        self.assertPolicyDenies(string, 'Formatter')
+        self.assertPolicyDenies(string, 'Template')
+
     def testUnicodeAttributeLookups(self):
         item = self.item
         r_item = self.a.r_item
         self.assertPolicyAllows(item, u'public_prop')
         self.assertPolicyDenies(r_item, u'private_prop')

Vulnerable code: - Nothing changed yet.
Fixed code: - Fix a remote code execution issue by preventing access to
  ``string.Formatter`` from restricted code.
from AccessControl.SecurityInfo import ModuleSecurityInfo
# Mark some unprotected module attributes as private, these should not be
# used in untrusted Python code such as Scripts (Python)
string_modsec = ModuleSecurityInfo('string')
for name in ('Formatter', 'Template'):
    string_modsec.declarePrivate(name)  # NOQA: D001
secureModule('string')

    def testAccessToStringModule(self):
        # The string module is available to restricted code and its members are
        # explicitly allowed via a
        # ``__allow_access_to_unprotected_subobjects__`` declaration. However,
        # a few classes are exempted and declared private, they can indirectly
        # provide uncontrolled access to system libraries from within
        # restricted code.
        import string

        self.assertPolicyAllows(string, 'printable')
        self.assertPolicyDenies(string, 'Formatter')
        self.assertPolicyDenies(string, 'Template')