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

150 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +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 

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