• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""A sandbox layer that ensures unsafe operations cannot be performed.
2Useful when the template itself comes from an untrusted source.
3"""
4import operator
5import types
6import typing as t
7from _string import formatter_field_name_split  # type: ignore
8from collections import abc
9from collections import deque
10from string import Formatter
11
12from markupsafe import EscapeFormatter
13from markupsafe import Markup
14
15from .environment import Environment
16from .exceptions import SecurityError
17from .runtime import Context
18from .runtime import Undefined
19
20F = t.TypeVar("F", bound=t.Callable[..., t.Any])
21
22#: maximum number of items a range may produce
23MAX_RANGE = 100000
24
25#: Unsafe function attributes.
26UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
27
28#: Unsafe method attributes. Function attributes are unsafe for methods too.
29UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
30
31#: unsafe generator attributes.
32UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
33
34#: unsafe attributes on coroutines
35UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
36
37#: unsafe attributes on async generators
38UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
39
40_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = (
41    (
42        abc.MutableSet,
43        frozenset(
44            [
45                "add",
46                "clear",
47                "difference_update",
48                "discard",
49                "pop",
50                "remove",
51                "symmetric_difference_update",
52                "update",
53            ]
54        ),
55    ),
56    (
57        abc.MutableMapping,
58        frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
59    ),
60    (
61        abc.MutableSequence,
62        frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
63    ),
64    (
65        deque,
66        frozenset(
67            [
68                "append",
69                "appendleft",
70                "clear",
71                "extend",
72                "extendleft",
73                "pop",
74                "popleft",
75                "remove",
76                "rotate",
77            ]
78        ),
79    ),
80)
81
82
83def inspect_format_method(callable: t.Callable) -> t.Optional[str]:
84    if not isinstance(
85        callable, (types.MethodType, types.BuiltinMethodType)
86    ) or callable.__name__ not in ("format", "format_map"):
87        return None
88
89    obj = callable.__self__
90
91    if isinstance(obj, str):
92        return obj
93
94    return None
95
96
97def safe_range(*args: int) -> range:
98    """A range that can't generate ranges with a length of more than
99    MAX_RANGE items.
100    """
101    rng = range(*args)
102
103    if len(rng) > MAX_RANGE:
104        raise OverflowError(
105            "Range too big. The sandbox blocks ranges larger than"
106            f" MAX_RANGE ({MAX_RANGE})."
107        )
108
109    return rng
110
111
112def unsafe(f: F) -> F:
113    """Marks a function or method as unsafe.
114
115    .. code-block: python
116
117        @unsafe
118        def delete(self):
119            pass
120    """
121    f.unsafe_callable = True  # type: ignore
122    return f
123
124
125def is_internal_attribute(obj: t.Any, attr: str) -> bool:
126    """Test if the attribute given is an internal python attribute.  For
127    example this function returns `True` for the `func_code` attribute of
128    python objects.  This is useful if the environment method
129    :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
130
131    >>> from jinja2.sandbox import is_internal_attribute
132    >>> is_internal_attribute(str, "mro")
133    True
134    >>> is_internal_attribute(str, "upper")
135    False
136    """
137    if isinstance(obj, types.FunctionType):
138        if attr in UNSAFE_FUNCTION_ATTRIBUTES:
139            return True
140    elif isinstance(obj, types.MethodType):
141        if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
142            return True
143    elif isinstance(obj, type):
144        if attr == "mro":
145            return True
146    elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
147        return True
148    elif isinstance(obj, types.GeneratorType):
149        if attr in UNSAFE_GENERATOR_ATTRIBUTES:
150            return True
151    elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
152        if attr in UNSAFE_COROUTINE_ATTRIBUTES:
153            return True
154    elif hasattr(types, "AsyncGeneratorType") and isinstance(
155        obj, types.AsyncGeneratorType
156    ):
157        if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
158            return True
159    return attr.startswith("__")
160
161
162def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
163    """This function checks if an attribute on a builtin mutable object
164    (list, dict, set or deque) or the corresponding ABCs would modify it
165    if called.
166
167    >>> modifies_known_mutable({}, "clear")
168    True
169    >>> modifies_known_mutable({}, "keys")
170    False
171    >>> modifies_known_mutable([], "append")
172    True
173    >>> modifies_known_mutable([], "index")
174    False
175
176    If called with an unsupported object, ``False`` is returned.
177
178    >>> modifies_known_mutable("foo", "upper")
179    False
180    """
181    for typespec, unsafe in _mutable_spec:
182        if isinstance(obj, typespec):
183            return attr in unsafe
184    return False
185
186
187class SandboxedEnvironment(Environment):
188    """The sandboxed environment.  It works like the regular environment but
189    tells the compiler to generate sandboxed code.  Additionally subclasses of
190    this environment may override the methods that tell the runtime what
191    attributes or functions are safe to access.
192
193    If the template tries to access insecure code a :exc:`SecurityError` is
194    raised.  However also other exceptions may occur during the rendering so
195    the caller has to ensure that all exceptions are caught.
196    """
197
198    sandboxed = True
199
200    #: default callback table for the binary operators.  A copy of this is
201    #: available on each instance of a sandboxed environment as
202    #: :attr:`binop_table`
203    default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
204        "+": operator.add,
205        "-": operator.sub,
206        "*": operator.mul,
207        "/": operator.truediv,
208        "//": operator.floordiv,
209        "**": operator.pow,
210        "%": operator.mod,
211    }
212
213    #: default callback table for the unary operators.  A copy of this is
214    #: available on each instance of a sandboxed environment as
215    #: :attr:`unop_table`
216    default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
217        "+": operator.pos,
218        "-": operator.neg,
219    }
220
221    #: a set of binary operators that should be intercepted.  Each operator
222    #: that is added to this set (empty by default) is delegated to the
223    #: :meth:`call_binop` method that will perform the operator.  The default
224    #: operator callback is specified by :attr:`binop_table`.
225    #:
226    #: The following binary operators are interceptable:
227    #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
228    #:
229    #: The default operation form the operator table corresponds to the
230    #: builtin function.  Intercepted calls are always slower than the native
231    #: operator call, so make sure only to intercept the ones you are
232    #: interested in.
233    #:
234    #: .. versionadded:: 2.6
235    intercepted_binops: t.FrozenSet[str] = frozenset()
236
237    #: a set of unary operators that should be intercepted.  Each operator
238    #: that is added to this set (empty by default) is delegated to the
239    #: :meth:`call_unop` method that will perform the operator.  The default
240    #: operator callback is specified by :attr:`unop_table`.
241    #:
242    #: The following unary operators are interceptable: ``+``, ``-``
243    #:
244    #: The default operation form the operator table corresponds to the
245    #: builtin function.  Intercepted calls are always slower than the native
246    #: operator call, so make sure only to intercept the ones you are
247    #: interested in.
248    #:
249    #: .. versionadded:: 2.6
250    intercepted_unops: t.FrozenSet[str] = frozenset()
251
252    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
253        super().__init__(*args, **kwargs)
254        self.globals["range"] = safe_range
255        self.binop_table = self.default_binop_table.copy()
256        self.unop_table = self.default_unop_table.copy()
257
258    def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
259        """The sandboxed environment will call this method to check if the
260        attribute of an object is safe to access.  Per default all attributes
261        starting with an underscore are considered private as well as the
262        special attributes of internal python objects as returned by the
263        :func:`is_internal_attribute` function.
264        """
265        return not (attr.startswith("_") or is_internal_attribute(obj, attr))
266
267    def is_safe_callable(self, obj: t.Any) -> bool:
268        """Check if an object is safely callable. By default callables
269        are considered safe unless decorated with :func:`unsafe`.
270
271        This also recognizes the Django convention of setting
272        ``func.alters_data = True``.
273        """
274        return not (
275            getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
276        )
277
278    def call_binop(
279        self, context: Context, operator: str, left: t.Any, right: t.Any
280    ) -> t.Any:
281        """For intercepted binary operator calls (:meth:`intercepted_binops`)
282        this function is executed instead of the builtin operator.  This can
283        be used to fine tune the behavior of certain operators.
284
285        .. versionadded:: 2.6
286        """
287        return self.binop_table[operator](left, right)
288
289    def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
290        """For intercepted unary operator calls (:meth:`intercepted_unops`)
291        this function is executed instead of the builtin operator.  This can
292        be used to fine tune the behavior of certain operators.
293
294        .. versionadded:: 2.6
295        """
296        return self.unop_table[operator](arg)
297
298    def getitem(
299        self, obj: t.Any, argument: t.Union[str, t.Any]
300    ) -> t.Union[t.Any, Undefined]:
301        """Subscribe an object from sandboxed code."""
302        try:
303            return obj[argument]
304        except (TypeError, LookupError):
305            if isinstance(argument, str):
306                try:
307                    attr = str(argument)
308                except Exception:
309                    pass
310                else:
311                    try:
312                        value = getattr(obj, attr)
313                    except AttributeError:
314                        pass
315                    else:
316                        if self.is_safe_attribute(obj, argument, value):
317                            return value
318                        return self.unsafe_undefined(obj, argument)
319        return self.undefined(obj=obj, name=argument)
320
321    def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
322        """Subscribe an object from sandboxed code and prefer the
323        attribute.  The attribute passed *must* be a bytestring.
324        """
325        try:
326            value = getattr(obj, attribute)
327        except AttributeError:
328            try:
329                return obj[attribute]
330            except (TypeError, LookupError):
331                pass
332        else:
333            if self.is_safe_attribute(obj, attribute, value):
334                return value
335            return self.unsafe_undefined(obj, attribute)
336        return self.undefined(obj=obj, name=attribute)
337
338    def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
339        """Return an undefined object for unsafe attributes."""
340        return self.undefined(
341            f"access to attribute {attribute!r} of"
342            f" {type(obj).__name__!r} object is unsafe.",
343            name=attribute,
344            obj=obj,
345            exc=SecurityError,
346        )
347
348    def format_string(
349        self,
350        s: str,
351        args: t.Tuple[t.Any, ...],
352        kwargs: t.Dict[str, t.Any],
353        format_func: t.Optional[t.Callable] = None,
354    ) -> str:
355        """If a format call is detected, then this is routed through this
356        method so that our safety sandbox can be used for it.
357        """
358        formatter: SandboxedFormatter
359        if isinstance(s, Markup):
360            formatter = SandboxedEscapeFormatter(self, escape=s.escape)
361        else:
362            formatter = SandboxedFormatter(self)
363
364        if format_func is not None and format_func.__name__ == "format_map":
365            if len(args) != 1 or kwargs:
366                raise TypeError(
367                    "format_map() takes exactly one argument"
368                    f" {len(args) + (kwargs is not None)} given"
369                )
370
371            kwargs = args[0]
372            args = ()
373
374        rv = formatter.vformat(s, args, kwargs)
375        return type(s)(rv)
376
377    def call(
378        __self,  # noqa: B902
379        __context: Context,
380        __obj: t.Any,
381        *args: t.Any,
382        **kwargs: t.Any,
383    ) -> t.Any:
384        """Call an object from sandboxed code."""
385        fmt = inspect_format_method(__obj)
386        if fmt is not None:
387            return __self.format_string(fmt, args, kwargs, __obj)
388
389        # the double prefixes are to avoid double keyword argument
390        # errors when proxying the call.
391        if not __self.is_safe_callable(__obj):
392            raise SecurityError(f"{__obj!r} is not safely callable")
393        return __context.call(__obj, *args, **kwargs)
394
395
396class ImmutableSandboxedEnvironment(SandboxedEnvironment):
397    """Works exactly like the regular `SandboxedEnvironment` but does not
398    permit modifications on the builtin mutable objects `list`, `set`, and
399    `dict` by using the :func:`modifies_known_mutable` function.
400    """
401
402    def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
403        if not super().is_safe_attribute(obj, attr, value):
404            return False
405
406        return not modifies_known_mutable(obj, attr)
407
408
409class SandboxedFormatter(Formatter):
410    def __init__(self, env: Environment, **kwargs: t.Any) -> None:
411        self._env = env
412        super().__init__(**kwargs)
413
414    def get_field(
415        self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
416    ) -> t.Tuple[t.Any, str]:
417        first, rest = formatter_field_name_split(field_name)
418        obj = self.get_value(first, args, kwargs)
419        for is_attr, i in rest:
420            if is_attr:
421                obj = self._env.getattr(obj, i)
422            else:
423                obj = self._env.getitem(obj, i)
424        return obj, first
425
426
427class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
428    pass
429