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

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

159 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 _string import formatter_field_name_split # type: ignore 

9from collections import abc 

10from collections import deque 

11from functools import update_wrapper 

12from string import Formatter 

13 

14from markupsafe import EscapeFormatter 

15from markupsafe import Markup 

16 

17from .environment import Environment 

18from .exceptions import SecurityError 

19from .runtime import Context 

20from .runtime import Undefined 

21 

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

23 

24#: maximum number of items a range may produce 

25MAX_RANGE = 100000 

26 

27#: Unsafe function attributes. 

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

29 

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

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

32 

33#: unsafe generator attributes. 

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

35 

36#: unsafe attributes on coroutines 

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

38 

39#: unsafe attributes on async generators 

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

41 

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

43 ( 

44 abc.MutableSet, 

45 frozenset( 

46 [ 

47 "add", 

48 "clear", 

49 "difference_update", 

50 "discard", 

51 "pop", 

52 "remove", 

53 "symmetric_difference_update", 

54 "update", 

55 ] 

56 ), 

57 ), 

58 ( 

59 abc.MutableMapping, 

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

61 ), 

62 ( 

63 abc.MutableSequence, 

64 frozenset( 

65 ["append", "clear", "pop", "reverse", "insert", "sort", "extend", "remove"] 

66 ), 

67 ), 

68 ( 

69 deque, 

70 frozenset( 

71 [ 

72 "append", 

73 "appendleft", 

74 "clear", 

75 "extend", 

76 "extendleft", 

77 "pop", 

78 "popleft", 

79 "remove", 

80 "rotate", 

81 ] 

82 ), 

83 ), 

84) 

85 

86 

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

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

89 MAX_RANGE items. 

90 """ 

91 rng = range(*args) 

92 

93 if len(rng) > MAX_RANGE: 

94 raise OverflowError( 

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

96 f" MAX_RANGE ({MAX_RANGE})." 

97 ) 

98 

99 return rng 

100 

101 

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

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

104 

105 .. code-block: python 

106 

107 @unsafe 

108 def delete(self): 

109 pass 

110 """ 

111 f.unsafe_callable = True # type: ignore 

112 return f 

113 

114 

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

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

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

118 python objects. This is useful if the environment method 

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

120 

121 >>> from jinja2.sandbox import is_internal_attribute 

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

123 True 

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

125 False 

126 """ 

127 if isinstance(obj, types.FunctionType): 

128 if attr in UNSAFE_FUNCTION_ATTRIBUTES: 

129 return True 

130 elif isinstance(obj, types.MethodType): 

131 if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES: 

132 return True 

133 elif isinstance(obj, type): 

134 if attr == "mro": 

135 return True 

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

137 return True 

138 elif isinstance(obj, types.GeneratorType): 

139 if attr in UNSAFE_GENERATOR_ATTRIBUTES: 

140 return True 

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

142 if attr in UNSAFE_COROUTINE_ATTRIBUTES: 

143 return True 

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

145 obj, types.AsyncGeneratorType 

146 ): 

147 if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES: 

148 return True 

149 return attr.startswith("__") 

150 

151 

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

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

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

155 if called. 

156 

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

158 True 

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

160 False 

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

162 True 

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

164 False 

165 

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

167 

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

169 False 

170 """ 

171 for typespec, unsafe in _mutable_spec: 

172 if isinstance(obj, typespec): 

173 return attr in unsafe 

174 return False 

175 

176 

177class SandboxedEnvironment(Environment): 

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

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

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

181 attributes or functions are safe to access. 

182 

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

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

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

186 """ 

187 

188 sandboxed = True 

189 

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

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

192 #: :attr:`binop_table` 

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

194 "+": operator.add, 

195 "-": operator.sub, 

196 "*": operator.mul, 

197 "/": operator.truediv, 

198 "//": operator.floordiv, 

199 "**": operator.pow, 

200 "%": operator.mod, 

201 } 

202 

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

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

205 #: :attr:`unop_table` 

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

207 "+": operator.pos, 

208 "-": operator.neg, 

209 } 

210 

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

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

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

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

215 #: 

216 #: The following binary operators are interceptable: 

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

218 #: 

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

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

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

222 #: interested in. 

223 #: 

224 #: .. versionadded:: 2.6 

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

226 

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

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

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

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

231 #: 

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

233 #: 

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

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

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

237 #: interested in. 

238 #: 

239 #: .. versionadded:: 2.6 

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

241 

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

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

244 self.globals["range"] = safe_range 

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

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

247 

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

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

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

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

252 special attributes of internal python objects as returned by the 

253 :func:`is_internal_attribute` function. 

254 """ 

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

256 

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

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

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

260 

261 This also recognizes the Django convention of setting 

262 ``func.alters_data = True``. 

263 """ 

264 return not ( 

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

266 ) 

267 

268 def call_binop( 

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

270 ) -> t.Any: 

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

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

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

274 

275 .. versionadded:: 2.6 

276 """ 

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

278 

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

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

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

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

283 

284 .. versionadded:: 2.6 

285 """ 

286 return self.unop_table[operator](arg) 

287 

288 def getitem( 

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

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

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

292 try: 

293 return obj[argument] 

294 except (TypeError, LookupError): 

295 if isinstance(argument, str): 

296 try: 

297 attr = str(argument) 

298 except Exception: 

299 pass 

300 else: 

301 try: 

302 value = getattr(obj, attr) 

303 except AttributeError: 

304 pass 

305 else: 

306 fmt = self.wrap_str_format(value) 

307 if fmt is not None: 

308 return fmt 

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

310 return value 

311 return self.unsafe_undefined(obj, argument) 

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

313 

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

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

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

317 """ 

318 try: 

319 value = getattr(obj, attribute) 

320 except AttributeError: 

321 try: 

322 return obj[attribute] 

323 except (TypeError, LookupError): 

324 pass 

325 else: 

326 fmt = self.wrap_str_format(value) 

327 if fmt is not None: 

328 return fmt 

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

330 return value 

331 return self.unsafe_undefined(obj, attribute) 

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

333 

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

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

336 return self.undefined( 

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

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

339 name=attribute, 

340 obj=obj, 

341 exc=SecurityError, 

342 ) 

343 

344 def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]: 

345 """If the given value is a ``str.format`` or ``str.format_map`` method, 

346 return a new function than handles sandboxing. This is done at access 

347 rather than in :meth:`call`, so that calls made without ``call`` are 

348 also sandboxed. 

349 """ 

350 if not isinstance( 

351 value, (types.MethodType, types.BuiltinMethodType) 

352 ) or value.__name__ not in ("format", "format_map"): 

353 return None 

354 

355 f_self: t.Any = value.__self__ 

356 

357 if not isinstance(f_self, str): 

358 return None 

359 

360 str_type: t.Type[str] = type(f_self) 

361 is_format_map = value.__name__ == "format_map" 

362 formatter: SandboxedFormatter 

363 

364 if isinstance(f_self, Markup): 

365 formatter = SandboxedEscapeFormatter(self, escape=f_self.escape) 

366 else: 

367 formatter = SandboxedFormatter(self) 

368 

369 vformat = formatter.vformat 

370 

371 def wrapper(*args: t.Any, **kwargs: t.Any) -> str: 

372 if is_format_map: 

373 if kwargs: 

374 raise TypeError("format_map() takes no keyword arguments") 

375 

376 if len(args) != 1: 

377 raise TypeError( 

378 f"format_map() takes exactly one argument ({len(args)} given)" 

379 ) 

380 

381 kwargs = args[0] 

382 args = () 

383 

384 return str_type(vformat(f_self, args, kwargs)) 

385 

386 return update_wrapper(wrapper, value) 

387 

388 def call( 

389 __self, # noqa: B902 

390 __context: Context, 

391 __obj: t.Any, 

392 *args: t.Any, 

393 **kwargs: t.Any, 

394 ) -> t.Any: 

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

396 

397 # the double prefixes are to avoid double keyword argument 

398 # errors when proxying the call. 

399 if not __self.is_safe_callable(__obj): 

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

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

402 

403 

404class ImmutableSandboxedEnvironment(SandboxedEnvironment): 

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

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

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

408 """ 

409 

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

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

412 return False 

413 

414 return not modifies_known_mutable(obj, attr) 

415 

416 

417class SandboxedFormatter(Formatter): 

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

419 self._env = env 

420 super().__init__(**kwargs) 

421 

422 def get_field( 

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

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

425 first, rest = formatter_field_name_split(field_name) 

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

427 for is_attr, i in rest: 

428 if is_attr: 

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

430 else: 

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

432 return obj, first 

433 

434 

435class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter): 

436 pass