From: David Lord Date: Wed, 5 Mar 2025 18:08:48 +0000 (-0800) Subject: attr filter uses env.getattr X-Git-Tag: 3.1.6~1^2 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=065334d1ee5b7210e1a0a93c37238c86858f2af7;p=thirdparty%2Fjinja.git attr filter uses env.getattr --- diff --git a/CHANGES.rst b/CHANGES.rst index f1956ec1..605a04fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ------------- diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py index e5b5a00c..2bcba4fb 100644 --- a/src/jinja2/filters.py +++ b/src/jinja2/filters.py @@ -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 ` 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 864d5f7f..3a137819 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -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()