Skip to content

Commit 90457bb

Browse files
authored
Merge commit from fork
attr filter uses env.getattr
2 parents 033c200 + 065334d commit 90457bb

File tree

3 files changed

+30
-21
lines changed

3 files changed

+30
-21
lines changed

CHANGES.rst

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Version 3.1.6
55

66
Unreleased
77

8+
- The ``|attr`` filter does not bypass the environment's attribute lookup,
9+
allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`
10+
11+
812
Version 3.1.5
913
-------------
1014

src/jinja2/filters.py

+16-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import typing
77
import typing as t
88
from collections import abc
9+
from inspect import getattr_static
910
from itertools import chain
1011
from itertools import groupby
1112

@@ -1411,31 +1412,25 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
14111412
def do_attr(
14121413
environment: "Environment", obj: t.Any, name: str
14131414
) -> t.Union[Undefined, t.Any]:
1414-
"""Get an attribute of an object. ``foo|attr("bar")`` works like
1415-
``foo.bar`` just that always an attribute is returned and items are not
1416-
looked up.
1415+
"""Get an attribute of an object. ``foo|attr("bar")`` works like
1416+
``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
1417+
if the attribute doesn't exist.
14171418
14181419
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
14191420
"""
1421+
# Environment.getattr will fall back to obj[name] if obj.name doesn't exist.
1422+
# But we want to call env.getattr to get behavior such as sandboxing.
1423+
# Determine if the attr exists first, so we know the fallback won't trigger.
14201424
try:
1421-
name = str(name)
1422-
except UnicodeError:
1423-
pass
1424-
else:
1425-
try:
1426-
value = getattr(obj, name)
1427-
except AttributeError:
1428-
pass
1429-
else:
1430-
if environment.sandboxed:
1431-
environment = t.cast("SandboxedEnvironment", environment)
1432-
1433-
if not environment.is_safe_attribute(obj, name, value):
1434-
return environment.unsafe_undefined(obj, name)
1435-
1436-
return value
1437-
1438-
return environment.undefined(obj=obj, name=name)
1425+
# This avoids executing properties/descriptors, but misses __getattr__
1426+
# and __getattribute__ dynamic attrs.
1427+
getattr_static(obj, name)
1428+
except AttributeError:
1429+
# This finds dynamic attrs, and we know it's not a descriptor at this point.
1430+
if not hasattr(obj, name):
1431+
return environment.undefined(obj=obj, name=name)
1432+
1433+
return environment.getattr(obj, name)
14391434

14401435

14411436
@typing.overload

tests/test_security.py

+10
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,13 @@ def run(value, arg):
190190

191191
with pytest.raises(SecurityError):
192192
t.render()
193+
194+
def test_attr_filter(self) -> None:
195+
env = SandboxedEnvironment()
196+
t = env.from_string(
197+
"""{{ "{0.__call__.__builtins__[__import__]}"
198+
| attr("format")(not_here) }}"""
199+
)
200+
201+
with pytest.raises(SecurityError):
202+
t.render()

0 commit comments

Comments
 (0)