CVE-2023-41039 (published 2023-08-30T18:15Z)

RestrictedPython is a restricted execution environment for Python to run untrusted code. Python\'s "format" functionality allows someone controlling the format string to "read" all objects accessible through recursive attribute lookup and subscription from objects he can access. This can lead to critical information disclosure. With `RestrictedPython`, the format functionality is available via the `format` and `format_map` methods of `str` (and `unicode`) (accessed either via the class or its instances) and via `string.Formatter`. All known versions of `RestrictedPython` are vulnerable. This issue has been addressed in commit `4134aedcff1` which has been included in the 5.4 and 6.2 releases. Users are advised to upgrade. There are no known workarounds for this vulnerability.\n\n

CVSS v3 base score: 7.7, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-74
cwe_name: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
extended_description: Software or other automated logic has certain assumptions about what constitutes data and control respectively. It is the lack of verification of these assumptions for user-controlled input that leads to injection problems. Injection problems encompass a wide variety of issues -- all mitigated in very different ways and usually attempted in order to alter the control flow of the process. For this reason, the most effective way to discuss these weaknesses is to note the distinct features that classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed.
         

Fix information:
repo_url: https://github.com/zopefoundation/RestrictedPython
commit_details: repo_url: https://github.com/zopefoundation/RestrictedPython
author: Dieter Maurer
author_date: 2023-08-30 08:28:28+02:00
msg: Merge pull request from GHSA-xjw2-6jm9-rf67

* fix information disclosure problems through the `format*` methods of the `str` class and its instances

* Suggestion from @icemac

Co-authored-by: Michael Howitz <mh@gocept.com>

* prevent access to `string.Formatter`

* fix syntax in `CHANGES.rst`

---------

Co-authored-by: Michael Howitz <mh@gocept.com>
repository: repo_url: https://github.com/zopefoundation/RestrictedPython
repo_name: zopefoundation/RestrictedPython
description: A restricted execution environment for Python to run untrusted code.
repo_language: Python
owner: zopefoundation
file_changes: filename: CHANGES.rst
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,11 @@ Fixes
 
 - Forbid using some attributes providing access to restricted Python internals.
 
+- Fix information disclosure problems through
+  Python's "format" functionality
+  (``format`` and ``format_map`` methods on ``str`` and its instances,
+  ``string.Formatter``).
+
 
 6.0 (2022-11-03)
 ----------------

programming_language: Markdown
filename: Guards.py
change_type: ModificationType.MODIFY
diff: @@ -246,9 +246,11 @@ def safer_getattr(object, name, default=None, getattr=getattr):
     http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
 
     """
-    if isinstance(object, str) and name == 'format':
+    if name in ('format', 'format_map') and (
+            isinstance(object, str) or
+            (isinstance(object, type) and issubclass(object, str))):
         raise NotImplementedError(
-            'Using format() on a %s is not safe.' % object.__class__.__name__)
+            'Using the format*() methods of `str` is not safe')
     if name.startswith('_'):
         raise AttributeError(
             '"{name}" is an invalid attribute name because it '

programming_language: Python
filename: Utilities.py
change_type: ModificationType.MODIFY
diff: @@ -18,7 +18,21 @@ import string
 
 utility_builtins = {}
 
-utility_builtins['string'] = string
+
+class _AttributeDelegator:
+    def __init__(self, mod, *excludes):
+        """delegate attribute lookups outside *excludes* to module *mod*."""
+        self.__mod = mod
+        self.__excludes = excludes
+
+    def __getattr__(self, attr):
+        if attr in self.__excludes:
+            raise NotImplementedError(
+                f"{self.__mod.__name__}.{attr} is not safe")
+        return getattr(self.__mod, attr)
+
+
+utility_builtins['string'] = _AttributeDelegator(string, "Formatter")
 utility_builtins['math'] = math
 utility_builtins['random'] = random
 utility_builtins['whrandom'] = random

programming_language: Python
filename: test_utilities.py
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,10 @@ import string
 
 def test_string_in_utility_builtins():
     from RestrictedPython.Utilities import utility_builtins
-    assert utility_builtins['string'] is string
+
+    # we no longer provide access to ``string`` itself, only to
+    # a restricted view of it
+    assert utility_builtins['string'].__name__ == string.__name__
 
 
 def test_math_in_utility_builtins():

programming_language: Python
filename: test_Guards.py
change_type: ModificationType.MODIFY
diff: @@ -160,7 +160,7 @@ b = a.format('world')
 """
 
 
-def test_Guards__safer_getattr__1():
+def test_Guards__safer_getattr__1a():
     """It prevents using the format method of a string.
 
     format() is considered harmful:
@@ -171,17 +171,57 @@ def test_Guards__safer_getattr__1():
     }
     with pytest.raises(NotImplementedError) as err:
         restricted_exec(STRING_DOT_FORMAT_DENIED, glb)
-    assert 'Using format() on a str is not safe.' == str(err.value)
+    assert 'Using the format*() methods of `str` is not safe' == str(err.value)
 
 
-UNICODE_DOT_FORMAT_DENIED = """\
-a = u'Hello {}'
-b = a.format(u'world')
+# contributed by Ward Theunisse
+STRING_DOT_FORMAT_MAP_DENIED = """\
+a = 'Hello {foo.__dict__}'
+b = a.format_map({foo:str})
 """
 
 
-def test_Guards__safer_getattr__2():
-    """It prevents using the format method of a unicode.
+def test_Guards__safer_getattr__1b():
+    """It prevents using the format method of a string.
+
+    format() is considered harmful:
+    http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
+    """
+    glb = {
+        '__builtins__': safe_builtins,
+    }
+    with pytest.raises(NotImplementedError) as err:
+        restricted_exec(STRING_DOT_FORMAT_MAP_DENIED, glb)
+    assert 'Using the format*() methods of `str` is not safe' == str(err.value)
+
+
+# contributed by Abhishek Govindarasu
+STR_DOT_FORMAT_DENIED = """\
+str.format('{0.__class__.__mro__[1]}', int)
+"""
+
+
+def test_Guards__safer_getattr__1c():
+    """It prevents using the format method of a string.
+
+    format() is considered harmful:
+    http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
+    """
+    glb = {
+        '__builtins__': safe_builtins,
+    }
+    with pytest.raises(NotImplementedError) as err:
+        restricted_exec(STR_DOT_FORMAT_DENIED, glb)
+    assert 'Using the format*() methods of `str` is not safe' == str(err.value)
+
+
+STR_DOT_FORMAT_MAP_DENIED = """\
+str.format_map('Hello {foo.__dict__}', {'foo':str})
+"""
+
+
+def test_Guards__safer_getattr__1d():
+    """It prevents using the format method of a string.
 
     format() is considered harmful:
     http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
@@ -190,8 +230,8 @@ def test_Guards__safer_getattr__2():
         '__builtins__': safe_builtins,
     }
     with pytest.raises(NotImplementedError) as err:
-        restricted_exec(UNICODE_DOT_FORMAT_DENIED, glb)
-    assert 'Using format() on a str is not safe.' == str(err.value)
+        restricted_exec(STR_DOT_FORMAT_MAP_DENIED, glb)
+    assert 'Using the format*() methods of `str` is not safe' == str(err.value)
 
 
 SAFER_GETATTR_ALLOWED = """\

programming_language: Python
filename: test_Utilities.py
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,8 @@
+import pytest
+
 from RestrictedPython.Utilities import reorder
 from RestrictedPython.Utilities import test
+from RestrictedPython.Utilities import utility_builtins
 
 
 def test_Utilities__test_1():
@@ -30,3 +33,13 @@ def test_Utilities__reorder_1():
     _with = [('k2', 'v2'), ('k3', 'v3')]
     without = [('k2', 'v2'), ('k4', 'v4')]
     assert reorder(s, _with, without) == [('k3', 'v3')]
+
+
+def test_Utilities_string_Formatter():
+    """Access to ``string.Formatter`` is denied."""
+    string = utility_builtins["string"]
+    # access successful in principle
+    assert string.ascii_lowercase == 'abcdefghijklmnopqrstuvwxyz'
+    with pytest.raises(NotImplementedError) as exc:
+        string.Formatter
+    assert 'string.Formatter is not safe' == str(exc.value)

programming_language: Python