From b428dd9593a26e7191708572f294b24ddd1d156f Mon Sep 17 00:00:00 2001 From: maoyanping Date: Fri, 18 Apr 2025 16:58:20 +0800 Subject: [PATCH] backport-CVE-2025-27516 --- Jinja2-3.1.3/CHANGES.rst | 3 +++ Jinja2-3.1.3/src/jinja2/filters.py | 35 +++++++++++++++-------------------- Jinja2-3.1.3/tests/test_security.py | 10 ++++++++++ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/Jinja2-3.1.3/CHANGES.rst b/Jinja2-3.1.3/CHANGES.rst index 2001e3c..af86746 100644 --- a/Jinja2-3.1.3/CHANGES.rst +++ b/Jinja2-3.1.3/CHANGES.rst @@ -1,5 +1,8 @@ .. currentmodule:: jinja2 +- The ``|attr`` filter does not bypass the environment's attribute lookup, + allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7` + - Escape template name before formatting it into error messages, to avoid issues with names that contain f-string syntax. :issue:`1792`, :ghsa:`gmj6-6f8f-6699` diff --git a/Jinja2-3.1.3/src/jinja2/filters.py b/Jinja2-3.1.3/src/jinja2/filters.py index c73dd89..aacb30d 100644 --- a/Jinja2-3.1.3/src/jinja2/filters.py +++ b/Jinja2-3.1.3/src/jinja2/filters.py @@ -5,6 +5,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 @@ -1399,30 +1400,24 @@ 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. + ``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/Jinja2-3.1.3/tests/test_security.py b/Jinja2-3.1.3/tests/test_security.py index 9c8bad6..0a0fddf 100644 --- a/Jinja2-3.1.3/tests/test_security.py +++ b/Jinja2-3.1.3/tests/test_security.py @@ -189,3 +189,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() \ No newline at end of file -- 2.33.0