Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jinja2/sandbox.py: 33%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

150 statements  

1"""A sandbox layer that ensures unsafe operations cannot be performed. 

2Useful when the template itself comes from an untrusted source. 

3""" 

4 

5import operator 

6import types 

7import typing as t 

8from collections import abc 

9from collections import deque 

10from string import Formatter 

11 

12from _string import formatter_field_name_split # type: ignore 

13from markupsafe import EscapeFormatter 

14from markupsafe import Markup 

15 

16from .environment import Environment 

17from .exceptions import SecurityError 

18from .runtime import Context 

19from .runtime import Undefined 

20 

21F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 

22 

23#: maximum number of items a range may produce 

24MAX_RANGE = 100000 

25 

26#: Unsafe function attributes. 

27UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set() 

28 

29#: Unsafe method attributes. Function attributes are unsafe for methods too. 

30UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set() 

31 

32#: unsafe generator attributes. 

33UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"} 

34 

35#: unsafe attributes on coroutines 

36UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"} 

37 

38#: unsafe attributes on async generators 

39UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"} 

40 

41_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = ( 

42 ( 

43 abc.MutableSet, 

44 frozenset( 

45 [ 

46 "add", 

47 "clear", 

48 "difference_update", 

49 "discard", 

50 "pop", 

51 "remove", 

52 "symmetric_difference_update", 

53 "update", 

54 ] 

55 ), 

56 ), 

57 ( 

58 abc.MutableMapping, 

59 frozenset(["clear", "pop", "popitem", "setdefault", "update"]), 

60 ), 

61 ( 

62 abc.MutableSequence, 

63 frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]), 

64 ), 

65 ( 

66 deque, 

67 frozenset( 

68 [ 

69 "append", 

70 "appendleft", 

71 "clear", 

72 "extend", 

73 "extendleft", 

74 "pop", 

75 "popleft", 

76 "remove", 

77 "rotate", 

78 ] 

79 ), 

80 ), 

81) 

82 

83 

84def inspect_format_method(callable: t.Callable[..., t.Any]) -> t.Optional[str]: 

85 if not isinstance( 

86 callable, (types.MethodType, types.BuiltinMethodType) 

87 ) or callable.__name__ not in ("format", "format_map"): 

88 return None 

89 

90 obj = callable.__self__ 

91 

92 if isinstance(obj, str): 

93 return obj 

94 

95 return None 

96 

97 

98def safe_range(*args: int) -> range: 

99 """A range that can't generate ranges with a length of more than 

100 MAX_RANGE items. 

101 """ 

102 rng = range(*args) 

103 

104 if len(rng) > MAX_RANGE: 

105 raise OverflowError( 

106 "Range too big. The sandbox blocks ranges larger than" 

107 f" MAX_RANGE ({MAX_RANGE})." 

108 ) 

109 

110 return rng 

111 

112 

113def unsafe(f: F) -> F: 

114 """Marks a function or method as unsafe. 

115 

116 .. code-block: python 

117 

118 @unsafe 

119 def delete(self): 

120 pass 

121 """ 

122 f.unsafe_callable = True # type: ignore 

123 return f 

124 

125 

126def is_internal_attribute(obj: t.Any, attr: str) -> bool: 

127 """Test if the attribute given is an internal python attribute. For 

128 example this function returns `True` for the `func_code` attribute of 

129 python objects. This is useful if the environment method 

130 :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden. 

131 

132 >>> from jinja2.sandbox import is_internal_attribute 

133 >>> is_internal_attribute(str, "mro") 

134 True 

135 >>> is_internal_attribute(str, "upper") 

136 False 

137 """ 

138 if isinstance(obj, types.FunctionType): 

139 if attr in UNSAFE_FUNCTION_ATTRIBUTES: 

140 return True 

141 elif isinstance(obj, types.MethodType): 

142 if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES: 

143 return True 

144 elif isinstance(obj, type): 

145 if attr == "mro": 

146 return True 

147 elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)): 

148 return True 

149 elif isinstance(obj, types.GeneratorType): 

150 if attr in UNSAFE_GENERATOR_ATTRIBUTES: 

151 return True 

152 elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType): 

153 if attr in UNSAFE_COROUTINE_ATTRIBUTES: 

154 return True 

155 elif hasattr(types, "AsyncGeneratorType") and isinstance( 

156 obj, types.AsyncGeneratorType 

157 ): 

158 if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES: 

159 return True 

160 return attr.startswith("__") 

161 

162 

163def modifies_known_mutable(obj: t.Any, attr: str) -> bool: 

164 """This function checks if an attribute on a builtin mutable object 

165 (list, dict, set or deque) or the corresponding ABCs would modify it 

166 if called. 

167 

168 >>> modifies_known_mutable({}, "clear") 

169 True 

170 >>> modifies_known_mutable({}, "keys") 

171 False 

172 >>> modifies_known_mutable([], "append") 

173 True 

174 >>> modifies_known_mutable([], "index") 

175 False 

176 

177 If called with an unsupported object, ``False`` is returned. 

178 

179 >>> modifies_known_mutable("foo", "upper") 

180 False 

181 """ 

182 for typespec, unsafe in _mutable_spec: 

183 if isinstance(obj, typespec): 

184 return attr in unsafe 

185 return False 

186 

187 

188class SandboxedEnvironment(Environment): 

189 """The sandboxed environment. It works like the regular environment but 

190 tells the compiler to generate sandboxed code. Additionally subclasses of 

191 this environment may override the methods that tell the runtime what 

192 attributes or functions are safe to access. 

193 

194 If the template tries to access insecure code a :exc:`SecurityError` is 

195 raised. However also other exceptions may occur during the rendering so 

196 the caller has to ensure that all exceptions are caught. 

197 """ 

198 

199 sandboxed = True 

200 

201 #: default callback table for the binary operators. A copy of this is 

202 #: available on each instance of a sandboxed environment as 

203 #: :attr:`binop_table` 

204 default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = { 

205 "+": operator.add, 

206 "-": operator.sub, 

207 "*": operator.mul, 

208 "/": operator.truediv, 

209 "//": operator.floordiv, 

210 "**": operator.pow, 

211 "%": operator.mod, 

212 } 

213 

214 #: default callback table for the unary operators. A copy of this is 

215 #: available on each instance of a sandboxed environment as 

216 #: :attr:`unop_table` 

217 default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = { 

218 "+": operator.pos, 

219 "-": operator.neg, 

220 } 

221 

222 #: a set of binary operators that should be intercepted. Each operator 

223 #: that is added to this set (empty by default) is delegated to the 

224 #: :meth:`call_binop` method that will perform the operator. The default 

225 #: operator callback is specified by :attr:`binop_table`. 

226 #: 

227 #: The following binary operators are interceptable: 

228 #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**`` 

229 #: 

230 #: The default operation form the operator table corresponds to the 

231 #: builtin function. Intercepted calls are always slower than the native 

232 #: operator call, so make sure only to intercept the ones you are 

233 #: interested in. 

234 #: 

235 #: .. versionadded:: 2.6 

236 intercepted_binops: t.FrozenSet[str] = frozenset() 

237 

238 #: a set of unary operators that should be intercepted. Each operator 

239 #: that is added to this set (empty by default) is delegated to the 

240 #: :meth:`call_unop` method that will perform the operator. The default 

241 #: operator callback is specified by :attr:`unop_table`. 

242 #: 

243 #: The following unary operators are interceptable: ``+``, ``-`` 

244 #: 

245 #: The default operation form the operator table corresponds to the 

246 #: builtin function. Intercepted calls are always slower than the native 

247 #: operator call, so make sure only to intercept the ones you are 

248 #: interested in. 

249 #: 

250 #: .. versionadded:: 2.6 

251 intercepted_unops: t.FrozenSet[str] = frozenset() 

252 

253 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: 

254 super().__init__(*args, **kwargs) 

255 self.globals["range"] = safe_range 

256 self.binop_table = self.default_binop_table.copy() 

257 self.unop_table = self.default_unop_table.copy() 

258 

259 def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: 

260 """The sandboxed environment will call this method to check if the 

261 attribute of an object is safe to access. Per default all attributes 

262 starting with an underscore are considered private as well as the 

263 special attributes of internal python objects as returned by the 

264 :func:`is_internal_attribute` function. 

265 """ 

266 return not (attr.startswith("_") or is_internal_attribute(obj, attr)) 

267 

268 def is_safe_callable(self, obj: t.Any) -> bool: 

269 """Check if an object is safely callable. By default callables 

270 are considered safe unless decorated with :func:`unsafe`. 

271 

272 This also recognizes the Django convention of setting 

273 ``func.alters_data = True``. 

274 """ 

275 return not ( 

276 getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False) 

277 ) 

278 

279 def call_binop( 

280 self, context: Context, operator: str, left: t.Any, right: t.Any 

281 ) -> t.Any: 

282 """For intercepted binary operator calls (:meth:`intercepted_binops`) 

283 this function is executed instead of the builtin operator. This can 

284 be used to fine tune the behavior of certain operators. 

285 

286 .. versionadded:: 2.6 

287 """ 

288 return self.binop_table[operator](left, right) 

289 

290 def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any: 

291 """For intercepted unary operator calls (:meth:`intercepted_unops`) 

292 this function is executed instead of the builtin operator. This can 

293 be used to fine tune the behavior of certain operators. 

294 

295 .. versionadded:: 2.6 

296 """ 

297 return self.unop_table[operator](arg) 

298 

299 def getitem( 

300 self, obj: t.Any, argument: t.Union[str, t.Any] 

301 ) -> t.Union[t.Any, Undefined]: 

302 """Subscribe an object from sandboxed code.""" 

303 try: 

304 return obj[argument] 

305 except (TypeError, LookupError): 

306 if isinstance(argument, str): 

307 try: 

308 attr = str(argument) 

309 except Exception: 

310 pass 

311 else: 

312 try: 

313 value = getattr(obj, attr) 

314 except AttributeError: 

315 pass 

316 else: 

317 if self.is_safe_attribute(obj, argument, value): 

318 return value 

319 return self.unsafe_undefined(obj, argument) 

320 return self.undefined(obj=obj, name=argument) 

321 

322 def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]: 

323 """Subscribe an object from sandboxed code and prefer the 

324 attribute. The attribute passed *must* be a bytestring. 

325 """ 

326 try: 

327 value = getattr(obj, attribute) 

328 except AttributeError: 

329 try: 

330 return obj[attribute] 

331 except (TypeError, LookupError): 

332 pass 

333 else: 

334 if self.is_safe_attribute(obj, attribute, value): 

335 return value 

336 return self.unsafe_undefined(obj, attribute) 

337 return self.undefined(obj=obj, name=attribute) 

338 

339 def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined: 

340 """Return an undefined object for unsafe attributes.""" 

341 return self.undefined( 

342 f"access to attribute {attribute!r} of" 

343 f" {type(obj).__name__!r} object is unsafe.", 

344 name=attribute, 

345 obj=obj, 

346 exc=SecurityError, 

347 ) 

348 

349 def format_string( 

350 self, 

351 s: str, 

352 args: t.Tuple[t.Any, ...], 

353 kwargs: t.Dict[str, t.Any], 

354 format_func: t.Optional[t.Callable[..., t.Any]] = None, 

355 ) -> str: 

356 """If a format call is detected, then this is routed through this 

357 method so that our safety sandbox can be used for it. 

358 """ 

359 formatter: SandboxedFormatter 

360 if isinstance(s, Markup): 

361 formatter = SandboxedEscapeFormatter(self, escape=s.escape) 

362 else: 

363 formatter = SandboxedFormatter(self) 

364 

365 if format_func is not None and format_func.__name__ == "format_map": 

366 if len(args) != 1 or kwargs: 

367 raise TypeError( 

368 "format_map() takes exactly one argument" 

369 f" {len(args) + (kwargs is not None)} given" 

370 ) 

371 

372 kwargs = args[0] 

373 args = () 

374 

375 rv = formatter.vformat(s, args, kwargs) 

376 return type(s)(rv) 

377 

378 def call( 

379 __self, # noqa: B902 

380 __context: Context, 

381 __obj: t.Any, 

382 *args: t.Any, 

383 **kwargs: t.Any, 

384 ) -> t.Any: 

385 """Call an object from sandboxed code.""" 

386 fmt = inspect_format_method(__obj) 

387 if fmt is not None: 

388 return __self.format_string(fmt, args, kwargs, __obj) 

389 

390 # the double prefixes are to avoid double keyword argument 

391 # errors when proxying the call. 

392 if not __self.is_safe_callable(__obj): 

393 raise SecurityError(f"{__obj!r} is not safely callable") 

394 return __context.call(__obj, *args, **kwargs) 

395 

396 

397class ImmutableSandboxedEnvironment(SandboxedEnvironment): 

398 """Works exactly like the regular `SandboxedEnvironment` but does not 

399 permit modifications on the builtin mutable objects `list`, `set`, and 

400 `dict` by using the :func:`modifies_known_mutable` function. 

401 """ 

402 

403 def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool: 

404 if not super().is_safe_attribute(obj, attr, value): 

405 return False 

406 

407 return not modifies_known_mutable(obj, attr) 

408 

409 

410class SandboxedFormatter(Formatter): 

411 def __init__(self, env: Environment, **kwargs: t.Any) -> None: 

412 self._env = env 

413 super().__init__(**kwargs) 

414 

415 def get_field( 

416 self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any] 

417 ) -> t.Tuple[t.Any, str]: 

418 first, rest = formatter_field_name_split(field_name) 

419 obj = self.get_value(first, args, kwargs) 

420 for is_attr, i in rest: 

421 if is_attr: 

422 obj = self._env.getattr(obj, i) 

423 else: 

424 obj = self._env.getitem(obj, i) 

425 return obj, first 

426 

427 

428class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter): 

429 pass