File 0001-SECURITY-support-sandboxing-in-format-expressions.patch of Package python-Jinja2.36862
From 9b53045c34e61013dc8f09b7e52a555fa16bed16 Mon Sep 17 00:00:00 2001
From: Armin Ronacher <armin.ronacher@active-4.com>
Date: Thu, 29 Dec 2016 14:13:38 +0100
Subject: [PATCH] SECURITY: support sandboxing in format expressions
---
jinja2/nodes.py | 2 +-
jinja2/sandbox.py | 119 +++++++++++++++++++++++++++++++++++++++--
tests/test_security.py | 27 +++++++++-
3 files changed, 143 insertions(+), 5 deletions(-)
Index: Jinja2-2.8/jinja2/nodes.py
===================================================================
--- Jinja2-2.8.orig/jinja2/nodes.py
+++ Jinja2-2.8/jinja2/nodes.py
@@ -604,7 +604,7 @@ class Call(Expr):
def as_const(self, eval_ctx=None):
eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
+ if eval_ctx.volatile or eval_ctx.environment.sandboxed:
raise Impossible()
obj = self.node.as_const(eval_ctx)
Index: Jinja2-2.8/jinja2/sandbox.py
===================================================================
--- Jinja2-2.8.orig/jinja2/sandbox.py
+++ Jinja2-2.8/jinja2/sandbox.py
@@ -14,9 +14,17 @@
"""
import types
import operator
+from collections import Mapping
from jinja2.environment import Environment
from jinja2.exceptions import SecurityError
-from jinja2._compat import string_types, PY2
+from jinja2._compat import string_types, text_type, PY2
+from jinja2.utils import Markup
+
+has_format = False
+if hasattr(text_type, 'format'):
+ from markupsafe import EscapeFormatter
+ from string import Formatter
+ has_format = True
#: maximum number of items a range may produce
@@ -38,6 +46,12 @@ UNSAFE_METHOD_ATTRIBUTES = set(['im_clas
#: unsafe generator attirbutes.
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
+#: unsafe attributes on coroutines
+UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code'])
+
+#: unsafe attributes on async generators
+UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame'])
+
import warnings
# make sure we don't warn in python 2.6 about stuff we don't care about
@@ -94,6 +108,49 @@ _mutable_spec = (
)
+class _MagicFormatMapping(Mapping):
+ """This class implements a dummy wrapper to fix a bug in the Python
+ standard library for string formatting.
+
+ See http://bugs.python.org/issue13598 for information about why
+ this is necessary.
+ """
+
+ def __init__(self, args, kwargs):
+ self._args = args
+ self._kwargs = kwargs
+ self._last_index = 0
+
+ def __getitem__(self, key):
+ if key == '':
+ idx = self._last_index
+ self._last_index += 1
+ try:
+ return self._args[idx]
+ except LookupError:
+ pass
+ key = str(idx)
+ return self._kwargs[key]
+
+ def __iter__(self):
+ return iter(self._kwargs)
+
+ def __len__(self):
+ return len(self._kwargs)
+
+
+def inspect_format_method(callable):
+ if not has_format:
+ return None
+ if not isinstance(callable, (types.MethodType,
+ types.BuiltinMethodType)) or \
+ callable.__name__ != 'format':
+ return None
+ obj = callable.__self__
+ if isinstance(obj, string_types):
+ return obj
+
+
def safe_range(*args):
"""A range that can't generate ranges with a length of more than
MAX_RANGE items.
@@ -145,6 +202,12 @@ def is_internal_attribute(obj, attr):
elif isinstance(obj, types.GeneratorType):
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
return True
+ elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType):
+ if attr in UNSAFE_COROUTINE_ATTRIBUTES:
+ return True
+ elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType):
+ if attri in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
+ return True
return attr.startswith('__')
@@ -183,8 +246,8 @@ class SandboxedEnvironment(Environment):
attributes or functions are safe to access.
If the template tries to access insecure code a :exc:`SecurityError` is
- raised. However also other exceptions may occour during the rendering so
- the caller has to ensure that all exceptions are catched.
+ raised. However also other exceptions may occur during the rendering so
+ the caller has to ensure that all exceptions are caught.
"""
sandboxed = True
@@ -346,8 +409,24 @@ class SandboxedEnvironment(Environment):
obj.__class__.__name__
), name=attribute, obj=obj, exc=SecurityError)
+ def format_string(self, s, args, kwargs):
+ """If a format call is detected, then this is routed through this
+ method so that our safety sandbox can be used for it.
+ """
+ if isinstance(s, Markup):
+ formatter = SandboxedEscapeFormatter(self, s.escape)
+ else:
+ formatter = SandboxedFormatter(self)
+ kwargs = _MagicFormatMapping(args, kwargs)
+ rv = formatter.vformat(s, args, kwargs)
+ return type(s)(rv)
+
def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
+ fmt = inspect_format_method(__obj)
+ if fmt is not None:
+ return __self.format_string(fmt, args, kwargs)
+
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
@@ -365,3 +444,37 @@ class ImmutableSandboxedEnvironment(Sand
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
return False
return not modifies_known_mutable(obj, attr)
+
+
+if has_format:
+ # This really is not a public API apparenlty.
+ try:
+ from _string import formatter_field_name_split
+ except ImportError:
+ def formatter_field_name_split(field_name):
+ return field_name._formatter_field_name_split()
+
+ class SandboxedFormatterMixin(object):
+
+ def __init__(self, env):
+ self._env = env
+
+ def get_field(self, field_name, args, kwargs):
+ first, rest = formatter_field_name_split(field_name)
+ obj = self.get_value(first, args, kwargs)
+ for is_attr, i in rest:
+ if is_attr:
+ obj = self._env.getattr(obj, i)
+ else:
+ obj = self._env.getitem(obj, i)
+ return obj, first
+
+ class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
+ def __init__(self, env):
+ SandboxedFormatterMixin.__init__(self, env)
+ Formatter.__init__(self)
+
+ class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
+ def __init__(self, env, escape):
+ SandboxedFormatterMixin.__init__(self, env)
+ EscapeFormatter.__init__(self, escape)