You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
7.9 KiB
218 lines
7.9 KiB
diff --git a/jinja2/nodes.py b/jinja2/nodes.py |
|
index c5697e6..9465943 100644 |
|
--- a/jinja2/nodes.py |
|
+++ b/jinja2/nodes.py |
|
@@ -599,7 +599,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) |
|
|
|
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py |
|
index da479c1..7e31a7a 100644 |
|
--- a/jinja2/sandbox.py |
|
+++ b/jinja2/sandbox.py |
|
@@ -12,12 +12,19 @@ |
|
:copyright: (c) 2010 by the Jinja Team. |
|
:license: BSD. |
|
""" |
|
+import types |
|
import operator |
|
+from collections import Mapping |
|
from jinja2.environment import Environment |
|
from jinja2.exceptions import SecurityError |
|
from jinja2._compat import string_types, function_type, method_type, \ |
|
- traceback_type, code_type, frame_type, generator_type, PY2 |
|
+ traceback_type, code_type, frame_type, generator_type, text_type, PY2 |
|
+from jinja2.utils import Markup |
|
|
|
+has_format = False |
|
+if hasattr(text_type, 'format'): |
|
+ from string import Formatter |
|
+ has_format = True |
|
|
|
#: maximum number of items a range may produce |
|
MAX_RANGE = 100000 |
|
@@ -32,6 +39,12 @@ UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self']) |
|
#: 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']) |
|
+ |
|
# On versions > python 2 the special attributes on functions are gone, |
|
# but they remain on methods and generators for whatever reason. |
|
if not PY2: |
|
@@ -92,6 +105,79 @@ _mutable_spec = ( |
|
])) |
|
) |
|
|
|
+# Bundled EscapeFormatter class from markupsafe >= 0.21 which is used by |
|
+# jinja2 for fixing CVE-2016-10745 |
|
+# Copyright 2010 Pallets |
|
+# BSD 3-Clause License |
|
+# https://github.com/pallets/markupsafe/blob/79ee6ce0ed93c6da73512f069d7db866d955df04/LICENSE.rst |
|
+if hasattr(text_type, "format"): |
|
+ |
|
+ class EscapeFormatter(Formatter): |
|
+ def __init__(self, escape): |
|
+ self.escape = escape |
|
+ |
|
+ def format_field(self, value, format_spec): |
|
+ if hasattr(value, "__html_format__"): |
|
+ rv = value.__html_format__(format_spec) |
|
+ elif hasattr(value, "__html__"): |
|
+ if format_spec: |
|
+ raise ValueError( |
|
+ "Format specifier {0} given, but {1} does not" |
|
+ " define __html_format__. A class that defines" |
|
+ " __html__ must define __html_format__ to work" |
|
+ " with format specifiers.".format(format_spec, type(value)) |
|
+ ) |
|
+ rv = value.__html__() |
|
+ else: |
|
+ # We need to make sure the format spec is unicode here as |
|
+ # otherwise the wrong callback methods are invoked. For |
|
+ # instance a byte string there would invoke __str__ and |
|
+ # not __unicode__. |
|
+ rv = Formatter.format_field(self, value, text_type(format_spec)) |
|
+ return text_type(self.escape(rv)) |
|
+ |
|
+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 |
|
@@ -146,6 +232,12 @@ def is_internal_attribute(obj, attr): |
|
elif isinstance(obj, generator_type): |
|
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('__') |
|
|
|
|
|
@@ -184,8 +276,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 |
|
|
|
@@ -347,8 +439,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): |
|
@@ -366,3 +474,37 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment): |
|
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)
|
|
|