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