diff --git a/backport-CVE-2024-56326.patch b/backport-CVE-2024-56326.patch new file mode 100644 index 0000000000000000000000000000000000000000..1404f8754be88d24f803a6ec8460ebd2a10b51f6 --- /dev/null +++ b/backport-CVE-2024-56326.patch @@ -0,0 +1,187 @@ +From 91a972f5808973cd441f4dc06873b2f8378f30c7 Mon Sep 17 00:00:00 2001 +From: Lydxn +Date: Mon, 23 Sep 2024 15:09:10 -0700 +Subject: [PATCH] sandbox indirect calls to str.format +--- + Jinja2-3.1.3/CHANGES.rst | 3 ++ + Jinja2-3.1.3/src/jinja2/sandbox.py | 81 +++++++++++++++-------------- + Jinja2-3.1.3/tests/test_security.py | 18 +++++++ + 3 files changed, 64 insertions(+), 38 deletions(-) + +diff --git a/Jinja2-3.1.3/CHANGES.rst b/Jinja2-3.1.3/CHANGES.rst +index f70cacb..e043649 100644 +--- a/Jinja2-3.1.3/CHANGES.rst ++++ b/Jinja2-3.1.3/CHANGES.rst +@@ -16,6 +16,9 @@ Released 2024-01-10 + - ``xmlattr`` filter does not allow keys with spaces. GHSA-h5c8-rqwp-cp95 + - Make error messages stemming from invalid nesting of ``{% trans %}`` blocks + more helpful. :pr:`1916` ++- The sandboxed environment handles indirect calls to ``str.format``, such as ++ by passing a stored reference to a filter that calls its argument. ++ :ghsa:`q2x7-8rv6-6q7h` + + + Version 3.1.2 +diff --git a/Jinja2-3.1.3/src/jinja2/sandbox.py b/Jinja2-3.1.3/src/jinja2/sandbox.py +index 06d7414..dae5a48 100644 +--- a/Jinja2-3.1.3/src/jinja2/sandbox.py ++++ b/Jinja2-3.1.3/src/jinja2/sandbox.py +@@ -7,6 +7,7 @@ import typing as t + from _string import formatter_field_name_split # type: ignore + from collections import abc + from collections import deque ++from functools import update_wrapper + from string import Formatter + + from markupsafe import EscapeFormatter +@@ -80,20 +81,6 @@ _mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( + ) + + +-def inspect_format_method(callable: t.Callable) -> t.Optional[str]: +- if not isinstance( +- callable, (types.MethodType, types.BuiltinMethodType) +- ) or callable.__name__ not in ("format", "format_map"): +- return None +- +- obj = callable.__self__ +- +- if isinstance(obj, str): +- return obj +- +- return None +- +- + def safe_range(*args: int) -> range: + """A range that can't generate ranges with a length of more than + MAX_RANGE items. +@@ -313,6 +300,9 @@ class SandboxedEnvironment(Environment): + except AttributeError: + pass + else: ++ fmt = self.wrap_str_format(value) ++ if fmt is not None: ++ return fmt + if self.is_safe_attribute(obj, argument, value): + return value + return self.unsafe_undefined(obj, argument) +@@ -330,6 +320,9 @@ class SandboxedEnvironment(Environment): + except (TypeError, LookupError): + pass + else: ++ fmt = self.wrap_str_format(value) ++ if fmt is not None: ++ return fmt + if self.is_safe_attribute(obj, attribute, value): + return value + return self.unsafe_undefined(obj, attribute) +@@ -345,34 +338,49 @@ class SandboxedEnvironment(Environment): + exc=SecurityError, + ) + +- def format_string( +- self, +- s: str, +- args: t.Tuple[t.Any, ...], +- kwargs: t.Dict[str, t.Any], +- format_func: t.Optional[t.Callable] = None, +- ) -> str: +- """If a format call is detected, then this is routed through this +- method so that our safety sandbox can be used for it. ++ def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]: ++ """If the given value is a ``str.format`` or ``str.format_map`` method, ++ return a new function than handles sandboxing. This is done at access ++ rather than in :meth:`call`, so that calls made without ``call`` are ++ also sandboxed. + """ ++ if not isinstance( ++ value, (types.MethodType, types.BuiltinMethodType) ++ ) or value.__name__ not in ("format", "format_map"): ++ return None ++ ++ f_self: t.Any = value.__self__ ++ ++ if not isinstance(f_self, str): ++ return None ++ ++ str_type: t.Type[str] = type(f_self) ++ is_format_map = value.__name__ == "format_map" + formatter: SandboxedFormatter +- if isinstance(s, Markup): +- formatter = SandboxedEscapeFormatter(self, escape=s.escape) ++ ++ if isinstance(f_self, Markup): ++ formatter = SandboxedEscapeFormatter(self, escape=f_self.escape) + else: + formatter = SandboxedFormatter(self) + +- if format_func is not None and format_func.__name__ == "format_map": +- if len(args) != 1 or kwargs: +- raise TypeError( +- "format_map() takes exactly one argument" +- f" {len(args) + (kwargs is not None)} given" +- ) ++ vformat = formatter.vformat ++ ++ def wrapper(*args: t.Any, **kwargs: t.Any) -> str: ++ if is_format_map: ++ if kwargs: ++ raise TypeError("format_map() takes no keyword arguments") ++ ++ if len(args) != 1: ++ raise TypeError( ++ f"format_map() takes exactly one argument ({len(args)} given)" ++ ) ++ ++ kwargs = args[0] ++ args = () + +- kwargs = args[0] +- args = () ++ return str_type(vformat(f_self, args, kwargs)) + +- rv = formatter.vformat(s, args, kwargs) +- return type(s)(rv) ++ return update_wrapper(wrapper, value) + + def call( + __self, # noqa: B902 +@@ -382,9 +390,6 @@ class SandboxedEnvironment(Environment): + **kwargs: t.Any, + ) -> t.Any: + """Call an object from sandboxed code.""" +- fmt = inspect_format_method(__obj) +- if fmt is not None: +- return __self.format_string(fmt, args, kwargs, __obj) + + # the double prefixes are to avoid double keyword argument + # errors when proxying the call. +diff --git a/Jinja2-3.1.3/tests/test_security.py b/Jinja2-3.1.3/tests/test_security.py +index 0e8dc5c..9c8bad6 100644 +--- a/Jinja2-3.1.3/tests/test_security.py ++++ b/Jinja2-3.1.3/tests/test_security.py +@@ -171,3 +171,21 @@ class TestStringFormatMap: + '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":""}) }}' + ) + assert t.render() == "a42b<foo>" ++ ++ ++ def test_indirect_call(self): ++ def run(value, arg): ++ return value.run(arg) ++ ++ env = SandboxedEnvironment() ++ env.filters["run"] = run ++ t = env.from_string( ++ """{% set ++ ns = namespace(run="{0.__call__.__builtins__[__import__]}".format) ++ %} ++ {{ ns | run(not_here) }} ++ """ ++ ) ++ ++ with pytest.raises(SecurityError): ++ t.render() +-- +2.43.0 + diff --git a/python-jinja2.spec b/python-jinja2.spec index 27f4184eae15a51e87202f51a2ba406c6418d845..3f54b2d02eb8e60fedd63da18d960351eb1110b5 100644 --- a/python-jinja2.spec +++ b/python-jinja2.spec @@ -2,7 +2,7 @@ Name: python-jinja2 Version: 3.1.3 -Release: 2 +Release: 3 Summary: A full-featured template engine for Python License: BSD-3-Clause URL: http://jinja.pocoo.org/ @@ -11,6 +11,7 @@ Source0: https://files.pythonhosted.org/packages/source/J/Jinja2/Jinja2-% BuildArch: noarch Patch0001: 0001-disallow-invalid-characters-in-keys-to-xmlattr-filte.patch +Patch0002: backport-CVE-2024-56326.patch %description Jinja2 is one of the most used template engines for Python. It is inspired by Django's @@ -64,6 +65,12 @@ popd %doc Jinja2-%{version}/examples %changelog +* Thu Dec 26 2024 changtao - 3.1.3-3 +- Type: CVE +- CVE: CVE-2024-56326 +- SUG: NA +- DESC: fix CVE-2024-56326 + * Tue May 7 2024 xuchenchen - 3.1.3-2 - Type: CVE - CVE: CVE-2024-34064