Skip to content

Commit 4134aed

Browse files
d-maurerMichael Howitz
andauthored
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 <[email protected]> * prevent access to `string.Formatter` * fix syntax in `CHANGES.rst` --------- Co-authored-by: Michael Howitz <[email protected]>
1 parent 41a183d commit 4134aed

File tree

6 files changed

+90
-13
lines changed

6 files changed

+90
-13
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Fixes
2222

2323
- Forbid using some attributes providing access to restricted Python internals.
2424

25+
- Fix information disclosure problems through
26+
Python's "format" functionality
27+
(``format`` and ``format_map`` methods on ``str`` and its instances,
28+
``string.Formatter``).
29+
2530

2631
6.0 (2022-11-03)
2732
----------------

src/RestrictedPython/Guards.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,11 @@ def safer_getattr(object, name, default=None, getattr=getattr):
246246
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
247247
248248
"""
249-
if isinstance(object, str) and name == 'format':
249+
if name in ('format', 'format_map') and (
250+
isinstance(object, str) or
251+
(isinstance(object, type) and issubclass(object, str))):
250252
raise NotImplementedError(
251-
'Using format() on a %s is not safe.' % object.__class__.__name__)
253+
'Using the format*() methods of `str` is not safe')
252254
if name.startswith('_'):
253255
raise AttributeError(
254256
'"{name}" is an invalid attribute name because it '

src/RestrictedPython/Utilities.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,21 @@
1818

1919
utility_builtins = {}
2020

21-
utility_builtins['string'] = string
21+
22+
class _AttributeDelegator:
23+
def __init__(self, mod, *excludes):
24+
"""delegate attribute lookups outside *excludes* to module *mod*."""
25+
self.__mod = mod
26+
self.__excludes = excludes
27+
28+
def __getattr__(self, attr):
29+
if attr in self.__excludes:
30+
raise NotImplementedError(
31+
f"{self.__mod.__name__}.{attr} is not safe")
32+
return getattr(self.__mod, attr)
33+
34+
35+
utility_builtins['string'] = _AttributeDelegator(string, "Formatter")
2236
utility_builtins['math'] = math
2337
utility_builtins['random'] = random
2438
utility_builtins['whrandom'] = random

tests/builtins/test_utilities.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
def test_string_in_utility_builtins():
77
from RestrictedPython.Utilities import utility_builtins
8-
assert utility_builtins['string'] is string
8+
9+
# we no longer provide access to ``string`` itself, only to
10+
# a restricted view of it
11+
assert utility_builtins['string'].__name__ == string.__name__
912

1013

1114
def test_math_in_utility_builtins():

tests/test_Guards.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def test_Guards__guarded_unpack_sequence__1(mocker):
160160
"""
161161

162162

163-
def test_Guards__safer_getattr__1():
163+
def test_Guards__safer_getattr__1a():
164164
"""It prevents using the format method of a string.
165165
166166
format() is considered harmful:
@@ -171,17 +171,57 @@ def test_Guards__safer_getattr__1():
171171
}
172172
with pytest.raises(NotImplementedError) as err:
173173
restricted_exec(STRING_DOT_FORMAT_DENIED, glb)
174-
assert 'Using format() on a str is not safe.' == str(err.value)
174+
assert 'Using the format*() methods of `str` is not safe' == str(err.value)
175175

176176

177-
UNICODE_DOT_FORMAT_DENIED = """\
178-
a = u'Hello {}'
179-
b = a.format(u'world')
177+
# contributed by Ward Theunisse
178+
STRING_DOT_FORMAT_MAP_DENIED = """\
179+
a = 'Hello {foo.__dict__}'
180+
b = a.format_map({foo:str})
180181
"""
181182

182183

183-
def test_Guards__safer_getattr__2():
184-
"""It prevents using the format method of a unicode.
184+
def test_Guards__safer_getattr__1b():
185+
"""It prevents using the format method of a string.
186+
187+
format() is considered harmful:
188+
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
189+
"""
190+
glb = {
191+
'__builtins__': safe_builtins,
192+
}
193+
with pytest.raises(NotImplementedError) as err:
194+
restricted_exec(STRING_DOT_FORMAT_MAP_DENIED, glb)
195+
assert 'Using the format*() methods of `str` is not safe' == str(err.value)
196+
197+
198+
# contributed by Abhishek Govindarasu
199+
STR_DOT_FORMAT_DENIED = """\
200+
str.format('{0.__class__.__mro__[1]}', int)
201+
"""
202+
203+
204+
def test_Guards__safer_getattr__1c():
205+
"""It prevents using the format method of a string.
206+
207+
format() is considered harmful:
208+
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
209+
"""
210+
glb = {
211+
'__builtins__': safe_builtins,
212+
}
213+
with pytest.raises(NotImplementedError) as err:
214+
restricted_exec(STR_DOT_FORMAT_DENIED, glb)
215+
assert 'Using the format*() methods of `str` is not safe' == str(err.value)
216+
217+
218+
STR_DOT_FORMAT_MAP_DENIED = """\
219+
str.format_map('Hello {foo.__dict__}', {'foo':str})
220+
"""
221+
222+
223+
def test_Guards__safer_getattr__1d():
224+
"""It prevents using the format method of a string.
185225
186226
format() is considered harmful:
187227
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
@@ -190,8 +230,8 @@ def test_Guards__safer_getattr__2():
190230
'__builtins__': safe_builtins,
191231
}
192232
with pytest.raises(NotImplementedError) as err:
193-
restricted_exec(UNICODE_DOT_FORMAT_DENIED, glb)
194-
assert 'Using format() on a str is not safe.' == str(err.value)
233+
restricted_exec(STR_DOT_FORMAT_MAP_DENIED, glb)
234+
assert 'Using the format*() methods of `str` is not safe' == str(err.value)
195235

196236

197237
SAFER_GETATTR_ALLOWED = """\

tests/test_Utilities.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import pytest
2+
13
from RestrictedPython.Utilities import reorder
24
from RestrictedPython.Utilities import test
5+
from RestrictedPython.Utilities import utility_builtins
36

47

58
def test_Utilities__test_1():
@@ -30,3 +33,13 @@ def test_Utilities__reorder_1():
3033
_with = [('k2', 'v2'), ('k3', 'v3')]
3134
without = [('k2', 'v2'), ('k4', 'v4')]
3235
assert reorder(s, _with, without) == [('k3', 'v3')]
36+
37+
38+
def test_Utilities_string_Formatter():
39+
"""Access to ``string.Formatter`` is denied."""
40+
string = utility_builtins["string"]
41+
# access successful in principle
42+
assert string.ascii_lowercase == 'abcdefghijklmnopqrstuvwxyz'
43+
with pytest.raises(NotImplementedError) as exc:
44+
string.Formatter
45+
assert 'string.Formatter is not safe' == str(exc.value)

0 commit comments

Comments
 (0)