]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
attr filter uses env.getattr
authorDavid Lord <davidism@gmail.com>
Wed, 5 Mar 2025 18:08:48 +0000 (10:08 -0800)
committerDavid Lord <davidism@gmail.com>
Wed, 5 Mar 2025 18:08:48 +0000 (10:08 -0800)
CHANGES.rst
src/jinja2/filters.py
tests/test_security.py

index f1956ec12f78913cc7df1be37f859fdb5800952d..605a04fd94aaabf53ffc7ab165f0e34f6ea5e8d3 100644 (file)
@@ -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
 -------------
 
index e5b5a00c5e8cb31f69c07c807594db1c492fd116..2bcba4fbd3c660edb1166fcad6ef6fc9a4f6a1e5 100644 (file)
@@ -6,6 +6,7 @@ import re
 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 <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
index 864d5f7f91fb8824f03fc77c75f2a233c7926727..3a13781926e45e598dc8df936b7df7fcbc1a449e 100644 (file)
@@ -190,3 +190,13 @@ class TestStringFormatMap:
 
         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()