Skip to content

Commit

Permalink
Merge pull request from GHSA-8xv7-89vj-q48c
Browse files Browse the repository at this point in the history
* fix information disclosure via `format_map`

* map `format_map` for `unicode` as well

* Add CVE

---------

Co-authored-by: Michael Howitz <mh@gocept.com>
  • Loading branch information
d-maurer and Michael Howitz committed Sep 4, 2023
1 parent 8b57744 commit 6b448f3
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ For changes before version 3.0, see ``HISTORY.rst``.
4.4 (unreleased)
----------------

- Fix information disclosure through ``str.format_map``.
(CVE-2023-41050)

- Provide ``AccessControl.get_safe_globals`` to facilitate safe use.


Expand Down
3 changes: 3 additions & 0 deletions src/AccessControl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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
Expand Down Expand Up @@ -47,13 +48,15 @@
# That one needs special handling to avoid access to attributes.
rules = dict([(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)

if six.PY2:
# Same for unicode instead on Python 2:
rules = dict([(m, True) for m in dir(six.text_type) if
not m.startswith('_')])
rules['format'] = safe_format
rules['format_map'] = safe_format_map
allow_type(six.text_type, rules)

del six
Expand Down
8 changes: 8 additions & 0 deletions src/AccessControl/safe_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,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
22 changes: 22 additions & 0 deletions src/AccessControl/tests/test_safe_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,25 @@ def test_prevents_bad_unicode_formatting_key(self):
self.assertRaises(Unauthorized,
SafeFormatter(u'{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)),
'<Item public1>')
self.assertEqual(SafeFormatter('{foo[2]}')
.safe_format_map(dict(foo=folder)),
'<Item public2>')
# But not the private item:
self.assertRaises(Unauthorized,
SafeFormatter('{foo[1]}').safe_format_map,
dict(foo=folder))

0 comments on commit 6b448f3

Please sign in to comment.