From 065334d1ee5b7210e1a0a93c37238c86858f2af7 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 5 Mar 2025 10:08:48 -0800 Subject: [PATCH] attr filter uses env.getattr --- src/jinja2/filters.py | 37 ++++++++++++++++--------------------- tests/test_security.py | 10 ++++++++++ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py index e5b5a00c5..2bcba4fbd 100644 --- a/src/jinja2/filters.py +++ b/src/jinja2/filters.py @@ -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 @@ -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 ` 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 diff --git a/tests/test_security.py b/tests/test_security.py index 864d5f7f9..3a1378192 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -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()