|
| 1 | +From c81e4a5da52f6782157d608356c9a82eaf908a89 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Kanishk-Bansal <kbkanishk975@gmail.com> |
| 3 | +Date: Thu, 2 Jan 2025 11:09:30 +0000 |
| 4 | +Subject: [PATCH] Fix CVE-2024-56326 |
| 5 | + |
| 6 | +--- |
| 7 | + src/jinja2/sandbox.py | 77 ++++++++++++++++++++++--------------------- |
| 8 | + 1 file changed, 40 insertions(+), 37 deletions(-) |
| 9 | + |
| 10 | +diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py |
| 11 | +index 4294884..96519a2 100644 |
| 12 | +--- a/src/jinja2/sandbox.py |
| 13 | ++++ b/src/jinja2/sandbox.py |
| 14 | +@@ -7,6 +7,7 @@ import typing as t |
| 15 | + from _string import formatter_field_name_split # type: ignore |
| 16 | + from collections import abc |
| 17 | + from collections import deque |
| 18 | ++from functools import update_wrapper |
| 19 | + from string import Formatter |
| 20 | + |
| 21 | + from markupsafe import EscapeFormatter |
| 22 | +@@ -80,19 +81,6 @@ _mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( |
| 23 | + ) |
| 24 | + |
| 25 | + |
| 26 | +-def inspect_format_method(callable: t.Callable) -> t.Optional[str]: |
| 27 | +- if not isinstance( |
| 28 | +- callable, (types.MethodType, types.BuiltinMethodType) |
| 29 | +- ) or callable.__name__ not in ("format", "format_map"): |
| 30 | +- return None |
| 31 | +- |
| 32 | +- obj = callable.__self__ |
| 33 | +- |
| 34 | +- if isinstance(obj, str): |
| 35 | +- return obj |
| 36 | +- |
| 37 | +- return None |
| 38 | +- |
| 39 | + |
| 40 | + def safe_range(*args: int) -> range: |
| 41 | + """A range that can't generate ranges with a length of more than |
| 42 | +@@ -313,6 +301,9 @@ class SandboxedEnvironment(Environment): |
| 43 | + except AttributeError: |
| 44 | + pass |
| 45 | + else: |
| 46 | ++ fmt = self.wrap_str_format(value) |
| 47 | ++ if fmt is not None: |
| 48 | ++ return fmt |
| 49 | + if self.is_safe_attribute(obj, argument, value): |
| 50 | + return value |
| 51 | + return self.unsafe_undefined(obj, argument) |
| 52 | +@@ -330,6 +321,9 @@ class SandboxedEnvironment(Environment): |
| 53 | + except (TypeError, LookupError): |
| 54 | + pass |
| 55 | + else: |
| 56 | ++ fmt = self.wrap_str_format(value) |
| 57 | ++ if fmt is not None: |
| 58 | ++ return fmt |
| 59 | + if self.is_safe_attribute(obj, attribute, value): |
| 60 | + return value |
| 61 | + return self.unsafe_undefined(obj, attribute) |
| 62 | +@@ -345,34 +339,46 @@ class SandboxedEnvironment(Environment): |
| 63 | + exc=SecurityError, |
| 64 | + ) |
| 65 | + |
| 66 | +- def format_string( |
| 67 | +- self, |
| 68 | +- s: str, |
| 69 | +- args: t.Tuple[t.Any, ...], |
| 70 | +- kwargs: t.Dict[str, t.Any], |
| 71 | +- format_func: t.Optional[t.Callable] = None, |
| 72 | +- ) -> str: |
| 73 | +- """If a format call is detected, then this is routed through this |
| 74 | +- method so that our safety sandbox can be used for it. |
| 75 | ++ def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]: |
| 76 | ++ """If the given value is a ``str.format`` or ``str.format_map`` method, |
| 77 | ++ return a new function than handles sandboxing. This is done at access |
| 78 | ++ rather than in :meth:`call`, so that calls made without ``call`` are |
| 79 | ++ also sandboxed. |
| 80 | + """ |
| 81 | ++ if not isinstance( |
| 82 | ++ value, (types.MethodType, types.BuiltinMethodType) |
| 83 | ++ ) or value.__name__ not in ("format", "format_map"): |
| 84 | ++ return None |
| 85 | ++ f_self: t.Any = value.__self__ |
| 86 | ++ if not isinstance(f_self, str): |
| 87 | ++ return None |
| 88 | ++ str_type: t.Type[str] = type(f_self) |
| 89 | ++ is_format_map = value.__name__ == "format_map" |
| 90 | + formatter: SandboxedFormatter |
| 91 | +- if isinstance(s, Markup): |
| 92 | +- formatter = SandboxedEscapeFormatter(self, escape=s.escape) |
| 93 | ++ |
| 94 | ++ if isinstance(f_self, Markup): |
| 95 | ++ formatter = SandboxedEscapeFormatter(self, escape=f_self.escape) |
| 96 | + else: |
| 97 | + formatter = SandboxedFormatter(self) |
| 98 | + |
| 99 | +- if format_func is not None and format_func.__name__ == "format_map": |
| 100 | +- if len(args) != 1 or kwargs: |
| 101 | +- raise TypeError( |
| 102 | +- "format_map() takes exactly one argument" |
| 103 | +- f" {len(args) + (kwargs is not None)} given" |
| 104 | +- ) |
| 105 | ++ vformat = formatter.vformat |
| 106 | ++ |
| 107 | ++ def wrapper(*args: t.Any, **kwargs: t.Any) -> str: |
| 108 | ++ if is_format_map: |
| 109 | ++ if kwargs: |
| 110 | ++ raise TypeError("format_map() takes no keyword arguments") |
| 111 | ++ |
| 112 | ++ if len(args) != 1: |
| 113 | ++ raise TypeError( |
| 114 | ++ f"format_map() takes exactly one argument ({len(args)} given)" |
| 115 | ++ ) |
| 116 | ++ |
| 117 | ++ kwargs = args[0] |
| 118 | ++ args = () |
| 119 | + |
| 120 | +- kwargs = args[0] |
| 121 | +- args = () |
| 122 | ++ return str_type(vformat(f_self, args, kwargs)) |
| 123 | + |
| 124 | +- rv = formatter.vformat(s, args, kwargs) |
| 125 | +- return type(s)(rv) |
| 126 | ++ return update_wrapper(wrapper, value) |
| 127 | + |
| 128 | + def call( |
| 129 | + __self, # noqa: B902 |
| 130 | +@@ -382,9 +388,6 @@ class SandboxedEnvironment(Environment): |
| 131 | + **kwargs: t.Any, |
| 132 | + ) -> t.Any: |
| 133 | + """Call an object from sandboxed code.""" |
| 134 | +- fmt = inspect_format_method(__obj) |
| 135 | +- if fmt is not None: |
| 136 | +- return __self.format_string(fmt, args, kwargs, __obj) |
| 137 | + |
| 138 | + # the double prefixes are to avoid double keyword argument |
| 139 | + # errors when proxying the call. |
| 140 | +-- |
| 141 | +2.45.2 |
| 142 | + |
0 commit comments