fix CVE-2024-56326

This commit is contained in:
changtao 2024-12-15 08:05:52 +08:00
parent bf97ca7b4d
commit d3b014d1e0
2 changed files with 195 additions and 1 deletions

View File

@ -0,0 +1,187 @@
From 91a972f5808973cd441f4dc06873b2f8378f30c7 Mon Sep 17 00:00:00 2001
From: Lydxn <hlyndon20@gmail.com>
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":"<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()
--
2.43.0

View File

@ -2,7 +2,7 @@
Name: python-jinja2 Name: python-jinja2
Version: 3.1.3 Version: 3.1.3
Release: 2 Release: 3
Summary: A full-featured template engine for Python Summary: A full-featured template engine for Python
License: BSD-3-Clause License: BSD-3-Clause
URL: http://jinja.pocoo.org/ URL: http://jinja.pocoo.org/
@ -11,6 +11,7 @@ Source0: https://files.pythonhosted.org/packages/source/J/Jinja2/Jinja2-%
BuildArch: noarch BuildArch: noarch
Patch0001: 0001-disallow-invalid-characters-in-keys-to-xmlattr-filte.patch Patch0001: 0001-disallow-invalid-characters-in-keys-to-xmlattr-filte.patch
Patch0002: backport-CVE-2024-56326.patch
%description %description
Jinja2 is one of the most used template engines for Python. It is inspired by Django's 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 %doc Jinja2-%{version}/examples
%changelog %changelog
* Thu Dec 26 2024 changtao <changtao@kylinos.cn> - 3.1.3-3
- Type: CVE
- CVE: CVE-2024-56326
- SUG: NA
- DESC: fix CVE-2024-56326
* Tue May 7 2024 xuchenchen <xuchenchen@kylinos.cn> - 3.1.3-2 * Tue May 7 2024 xuchenchen <xuchenchen@kylinos.cn> - 3.1.3-2
- Type: CVE - Type: CVE
- CVE: CVE-2024-34064 - CVE: CVE-2024-34064