Skip to content

Commit

Permalink
attr filter uses env.getattr
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Mar 5, 2025
1 parent 033c200 commit 065334d
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Version 3.1.6

Unreleased

- The ``|attr`` filter does not bypass the environment's attribute lookup,
allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`


Version 3.1.5
-------------

Expand Down
37 changes: 16 additions & 21 deletions src/jinja2/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typing
import typing as t
from collections import abc
from inspect import getattr_static
from itertools import chain
from itertools import groupby

Expand Down Expand Up @@ -1411,31 +1412,25 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
def do_attr(
environment: "Environment", obj: t.Any, name: str
) -> t.Union[Undefined, t.Any]:
"""Get an attribute of an object. ``foo|attr("bar")`` works like
``foo.bar`` just that always an attribute is returned and items are not
looked up.
"""Get an attribute of an object. ``foo|attr("bar")`` works like
``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
if the attribute doesn't exist.
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
"""
# Environment.getattr will fall back to obj[name] if obj.name doesn't exist.
# But we want to call env.getattr to get behavior such as sandboxing.
# Determine if the attr exists first, so we know the fallback won't trigger.
try:
name = str(name)
except UnicodeError:
pass
else:
try:
value = getattr(obj, name)
except AttributeError:
pass
else:
if environment.sandboxed:
environment = t.cast("SandboxedEnvironment", environment)

if not environment.is_safe_attribute(obj, name, value):
return environment.unsafe_undefined(obj, name)

return value

return environment.undefined(obj=obj, name=name)
# This avoids executing properties/descriptors, but misses __getattr__
# and __getattribute__ dynamic attrs.
getattr_static(obj, name)
except AttributeError:
# This finds dynamic attrs, and we know it's not a descriptor at this point.
if not hasattr(obj, name):
return environment.undefined(obj=obj, name=name)

return environment.getattr(obj, name)


@typing.overload
Expand Down
10 changes: 10 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,13 @@ def run(value, arg):

with pytest.raises(SecurityError):
t.render()

def test_attr_filter(self) -> None:
env = SandboxedEnvironment()
t = env.from_string(
"""{{ "{0.__call__.__builtins__[__import__]}"
| attr("format")(not_here) }}"""
)

with pytest.raises(SecurityError):
t.render()

0 comments on commit 065334d

Please sign in to comment.