Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jinja2/sandbox.py: 33%
150 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
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
12from markupsafe import EscapeFormatter
13from markupsafe import Markup
15from .environment import Environment
16from .exceptions import SecurityError
17from .runtime import Context
18from .runtime import Undefined
20F = t.TypeVar("F", bound=t.Callable[..., t.Any])
22#: maximum number of items a range may produce
23MAX_RANGE = 100000
25#: Unsafe function attributes.
26UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
28#: Unsafe method attributes. Function attributes are unsafe for methods too.
29UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
31#: unsafe generator attributes.
32UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
34#: unsafe attributes on coroutines
35UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
37#: unsafe attributes on async generators
38UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
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)
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
89 obj = callable.__self__
91 if isinstance(obj, str):
92 return obj
94 return None
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)
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 )
109 return rng
112def unsafe(f: F) -> F:
113 """Marks a function or method as unsafe.
115 .. code-block: python
117 @unsafe
118 def delete(self):
119 pass
120 """
121 f.unsafe_callable = True # type: ignore
122 return f
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.
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("__")
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.
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
176 If called with an unsupported object, ``False`` is returned.
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
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.
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 """
198 sandboxed = True
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 }
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 }
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()
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()
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()
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))
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`.
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 )
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.
285 .. versionadded:: 2.6
286 """
287 return self.binop_table[operator](left, right)
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.
294 .. versionadded:: 2.6
295 """
296 return self.unop_table[operator](arg)
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)
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)
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 )
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)
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 )
371 kwargs = args[0]
372 args = ()
374 rv = formatter.vformat(s, args, kwargs)
375 return type(s)(rv)
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)
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)
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 """
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
406 return not modifies_known_mutable(obj, attr)
409class SandboxedFormatter(Formatter):
410 def __init__(self, env: Environment, **kwargs: t.Any) -> None:
411 self._env = env
412 super().__init__(**kwargs)
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
427class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
428 pass