On 27/02/2025 23:06, Lee Garrett wrote:
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: jin...@packages.debian.org, deb...@rocketjump.eu
Control: affects -1 + src:jinja2
User: release.debian....@packages.debian.org
Usertags: pu


[ Reason ]
Fix CVE-2024-56201
Fix CVE-2024-56326


[ Impact ]
Two security vulnerabilities will stay unfixed.

[ Tests ]
The patches are taken from upstream, and include test coverage. Both patches
applied with minimal changes.

[ Risks ]
Low, are nearly direct patches from upstream.

[ Checklist ]
   [x] *all* changes are documented in the d/changelog
   [x] I reviewed all changes and I approve them
   [x] attach debdiff against the package in (old)stable
   [x] the issue is verified as fixed in unstable

[ Changes ]
Fix CVE-2024-56201
Fix CVE-2024-56326

[ Other info ]
%


Forgot the debdiff. Is attached now.
diff -Nru jinja2-3.1.2/debian/changelog jinja2-3.1.2/debian/changelog
--- jinja2-3.1.2/debian/changelog	2024-12-07 18:15:36.000000000 +0100
+++ jinja2-3.1.2/debian/changelog	2025-02-27 22:30:54.000000000 +0100
@@ -1,3 +1,31 @@
+jinja2 (3.1.2-1+deb12u2) bookworm; urgency=medium
+
+  * Non-maintainer upload by the LTS security team.
+  * Fix CVE-2024-56201:
+    In versions on the 3.x branch prior to 3.1.5, a bug in the Jinja compiler
+    allows an attacker that controls both the content and filename of a template
+    to execute arbitrary Python code, regardless of if Jinja's sandbox is used.
+    To exploit the vulnerability, an attacker needs to control both the filename
+    and the contents of a template. Whether that is the case depends on the type
+    of application using Jinja. This vulnerability impacts users of applications
+    which execute untrusted templates where the template author can also choose
+    the template filename.
+  * Fix CVE-2024-56326:
+    Prior to 3.1.5, An oversight in how the Jinja sandboxed environment detects
+    calls to str.format allows an attacker that controls the content of a
+    template to execute arbitrary Python code. To exploit the vulnerability, an
+    attacker needs to control the content of a template. Whether that is the
+    case depends on the type of application using Jinja. This vulnerability
+    impacts users of applications which execute untrusted templates. Jinja's
+    sandbox does catch calls to str.format and ensures they don't escape the
+    sandbox. However, it's possible to store a reference to a malicious string's
+    format method, then pass that to a filter that calls it. No such filters are
+    built-in to Jinja, but could be present through custom filters in an
+    application. After the fix, such indirect calls are also handled by the
+    sandbox.
+
+ -- Lee Garrett <deb...@rocketjump.eu>  Thu, 27 Feb 2025 22:30:54 +0100
+
 jinja2 (3.1.2-1+deb12u1) bookworm; urgency=medium
 
   * Non-maintainer upload.
diff -Nru jinja2-3.1.2/debian/gbp.conf jinja2-3.1.2/debian/gbp.conf
--- jinja2-3.1.2/debian/gbp.conf	1970-01-01 01:00:00.000000000 +0100
+++ jinja2-3.1.2/debian/gbp.conf	2025-02-27 21:54:52.000000000 +0100
@@ -0,0 +1,10 @@
+# Configuration for git-buildpackage and affiliated tools
+
+[DEFAULT]
+debian-branch = debian/bookworm
+pristine-tar = True
+sign-tags = True 
+upstream-branch = upstream/bookworm
+
+[import-orig]
+merge-mode = replace
diff -Nru jinja2-3.1.2/debian/patches/0006-Fix-CVE-2024-56201.patch jinja2-3.1.2/debian/patches/0006-Fix-CVE-2024-56201.patch
--- jinja2-3.1.2/debian/patches/0006-Fix-CVE-2024-56201.patch	1970-01-01 01:00:00.000000000 +0100
+++ jinja2-3.1.2/debian/patches/0006-Fix-CVE-2024-56201.patch	2025-02-27 22:29:56.000000000 +0100
@@ -0,0 +1,61 @@
+Description: Fix CVE-2024-56201 
+Origin: backport, https://github.com/pallets/jinja/commit/767b23617628419ae3709ccfb02f9602ae9fe51f
+Reviewed-by: Lee Garrett <deb...@rocketjump.eu>
+Last-Update: 2025-02-27 
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+
+diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py
+index 3458095..27d86c3 100644
+--- a/src/jinja2/compiler.py
++++ b/src/jinja2/compiler.py
+@@ -1122,9 +1122,14 @@ class CodeGenerator(NodeVisitor):
+             )
+             self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
+             self.indent()
++            # The position will contain the template name, and will be formatted
++            # into a string that will be compiled into an f-string. Curly braces
++            # in the name must be replaced with escapes so that they will not be
++            # executed as part of the f-string.
++            position = self.position(node).replace("{", "{{").replace("}", "}}")
+             message = (
+                 "the template {included_template.__name__!r}"
+-                f" (imported on {self.position(node)})"
++                f" (imported on {position})"
+                 f" does not export the requested name {name!r}"
+             )
+             self.writeline(
+diff --git a/tests/test_compile.py b/tests/test_compile.py
+index 42a773f..aecf149 100644
+--- a/tests/test_compile.py
++++ b/tests/test_compile.py
+@@ -1,6 +1,9 @@
+ import os
+ import re
+ 
++import pytest
++
++from jinja2 import UndefinedError
+ from jinja2.environment import Environment
+ from jinja2.loaders import DictLoader
+ 
+@@ -26,3 +29,19 @@ def test_import_as_with_context_deterministic(tmp_path):
+     expect = [f"'bar{i}': " for i in range(10)]
+     found = re.findall(r"'bar\d': ", content)[:10]
+     assert found == expect
++
++
++def test_undefined_import_curly_name():
++    env = Environment(
++        loader=DictLoader(
++            {
++                "{bad}": "{% from 'macro' import m %}{{ m() }}",
++                "macro": "",
++            }
++        )
++    )
++
++    # Must not raise `NameError: 'bad' is not defined`, as that would indicate
++    # that `{bad}` is being interpreted as an f-string. It must be escaped.
++    with pytest.raises(UndefinedError):
++        env.get_template("{bad}").render()
diff -Nru jinja2-3.1.2/debian/patches/0007-Fix-CVE-2024-56326.patch jinja2-3.1.2/debian/patches/0007-Fix-CVE-2024-56326.patch
--- jinja2-3.1.2/debian/patches/0007-Fix-CVE-2024-56326.patch	1970-01-01 01:00:00.000000000 +0100
+++ jinja2-3.1.2/debian/patches/0007-Fix-CVE-2024-56326.patch	2025-02-27 22:30:08.000000000 +0100
@@ -0,0 +1,165 @@
+Description: Fix CVE-2024-56326
+Origin: backport, https://github.com/pallets/jinja/commit/48b0687e05a5466a91cd5812d604fa37ad0943b4
+Reviewed-by: Lee Garrett <deb...@rocketjump.eu>
+Last-Update: 2025-02-27
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py
+index f443c18..26d9d7e 100644
+--- a/src/jinja2/sandbox.py
++++ b/src/jinja2/sandbox.py
+@@ -10,6 +10,7 @@ try:
+     from collections.abc import deque
+ except ImportError:
+     from collections import deque
++from functools import update_wrapper
+ from string import Formatter
+ 
+ from markupsafe import EscapeFormatter
+@@ -83,20 +84,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.
+@@ -316,6 +303,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)
+@@ -333,6 +323,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)
+@@ -348,34 +341,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
+@@ -385,9 +393,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/tests/test_security.py b/tests/test_security.py
+index 0e8dc5c..81a32ae 100644
+--- a/tests/test_security.py
++++ b/tests/test_security.py
+@@ -171,3 +171,20 @@ class TestStringFormatMap:
+             '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}'
+         )
+         assert t.render() == "a42b&lt;foo&gt;"
++
++    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()
diff -Nru jinja2-3.1.2/debian/patches/series jinja2-3.1.2/debian/patches/series
--- jinja2-3.1.2/debian/patches/series	2024-12-07 18:15:36.000000000 +0100
+++ jinja2-3.1.2/debian/patches/series	2025-02-27 22:21:40.000000000 +0100
@@ -3,3 +3,5 @@
 0003-fix-nose-leftovers.patch
 0001-xmlattr-filter-disallows-keys-with-spaces.patch
 0002-disallow-invalid-characters-in-keys-to-xmlattr-filte.patch
+0006-Fix-CVE-2024-56201.patch
+0007-Fix-CVE-2024-56326.patch

Reply via email to