From 6bc32692e0d4b8d5cf64eae3d19de987c7375bc9 Mon Sep 17 00:00:00 2001 From: Dieter Maurer Date: Mon, 4 Sep 2023 07:28:15 +0200 Subject: [PATCH] Merge pull request from GHSA-8xv7-89vj-q48c * fix information disclosure via `format_map` * Fix CHANGES.rst according to review Co-authored-by: Jens Vagelpohl * Add CVE --------- Co-authored-by: Jens Vagelpohl Co-authored-by: Michael Howitz --- CHANGES.rst | 3 +++ src/AccessControl/__init__.py | 2 ++ src/AccessControl/safe_formatter.py | 8 +++++++ .../tests/test_safe_formatter.py | 22 +++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4514bf7..d6ec6a6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ For changes before version 3.0, see ``HISTORY.rst``. 6.2 (unreleased) ---------------- +- Fix information disclosure through ``str.format_map``. + (CVE-2023-41050) + 6.1 (2023-05-22) ---------------- diff --git a/src/AccessControl/__init__.py b/src/AccessControl/__init__.py index afba2a9..a9597d3 100644 --- a/src/AccessControl/__init__.py +++ b/src/AccessControl/__init__.py @@ -14,6 +14,7 @@ # This has to happen early so things get initialized properly from AccessControl.Implementation import setImplementation from AccessControl.safe_formatter import safe_format +from AccessControl.safe_formatter import safe_format_map from AccessControl.SecurityInfo import ACCESS_NONE from AccessControl.SecurityInfo import ACCESS_PRIVATE from AccessControl.SecurityInfo import ACCESS_PUBLIC @@ -44,6 +45,7 @@ # That one needs special handling to avoid access to attributes. rules = {m: True for m in dir(str) if not m.startswith('_')} rules['format'] = safe_format +rules['format_map'] = safe_format_map allow_type(str, rules) zodbupdate_decode_dict = { diff --git a/src/AccessControl/safe_formatter.py b/src/AccessControl/safe_formatter.py index e777c97..6128368 100644 --- a/src/AccessControl/safe_formatter.py +++ b/src/AccessControl/safe_formatter.py @@ -72,7 +72,15 @@ def safe_format(self, *args, **kwargs): kwargs = _MagicFormatMapping(args, kwargs) return self.vformat(self.value, args, kwargs) + def safe_format_map(self, kw): + kwargs = _MagicFormatMapping((), kw) + return self.vformat(self.value, (), kwargs) + def safe_format(inst, method): """Use our SafeFormatter that uses guarded_getattr for attribute access.""" return SafeFormatter(inst).safe_format + + +def safe_format_map(inst, method): + return SafeFormatter(inst).safe_format_map diff --git a/src/AccessControl/tests/test_safe_formatter.py b/src/AccessControl/tests/test_safe_formatter.py index 68f8408..9eea54b 100644 --- a/src/AccessControl/tests/test_safe_formatter.py +++ b/src/AccessControl/tests/test_safe_formatter.py @@ -202,3 +202,25 @@ def test_prevents_bad_unicode_formatting_key(self): self.assertRaises(Unauthorized, SafeFormatter('{0[1]}').safe_format, folder) + + def test_format_map(self): + from AccessControl.safe_formatter import SafeFormatter + + # Accessing basic Python types in a basic Python list is fine. + foo = list(['bar']) + self.assertEqual(SafeFormatter('{foo[0]}') + .safe_format_map(dict(foo=foo)), + 'bar') + # But for non-basic items or non-basic lists, we want run checks. + folder = self._create_folder_with_mixed_contents() + # We can get the public items just fine: + self.assertEqual(SafeFormatter('{foo[0]}') + .safe_format_map(dict(foo=folder)), + '') + self.assertEqual(SafeFormatter('{foo[2]}') + .safe_format_map(dict(foo=folder)), + '') + # But not the private item: + self.assertRaises(Unauthorized, + SafeFormatter('{foo[1]}').safe_format_map, + dict(foo=folder))