From cc4634a5e73c06c6b4581f11171289ca9228391e Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Tue, 10 Jan 2017 16:54:00 -0600 Subject: [PATCH] Additional fixes for security related to CVE-2016-9587 (cherry picked from commit d316068831f9e08ef96833200ec7df2132263966) --- lib/ansible/playbook/conditional.py | 10 +++++----- lib/ansible/template/__init__.py | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py index 99e377c4db1fcb..57e20a0159e422 100644 --- a/lib/ansible/playbook/conditional.py +++ b/lib/ansible/playbook/conditional.py @@ -104,7 +104,7 @@ def _check_conditional(self, conditional, templar, all_vars): if conditional is None or conditional == '': return True - if conditional in all_vars and '-' not in text_type(all_vars[conditional]): + if conditional in all_vars and re.match("^[_A-Za-z][_a-zA-Z0-9]*$", conditional): conditional = all_vars[conditional] # make sure the templar is using the variables specified with this method @@ -116,12 +116,12 @@ def _check_conditional(self, conditional, templar, all_vars): return conditional # a Jinja2 evaluation that results in something Python can eval! - if hasattr(conditional, '__UNSAFE__') and LOOKUP_REGEX.match(conditional): - raise AnsibleError("The conditional '%s' contains variables which came from an unsafe " \ - "source and also contains a lookup() call, failing conditional check" % conditional) + disable_lookups = False + if hasattr(conditional, '__UNSAFE__'): + disable_lookups = True presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional - val = templar.template(presented).strip() + val = templar.template(presented, disable_lookups=disable_lookups).strip() if val == "True": return True elif val == "False": diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index 53b267543f2525..1a43486f9e7885 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -30,10 +30,8 @@ from jinja2 import Environment from jinja2.loaders import FileSystemLoader from jinja2.exceptions import TemplateSyntaxError, UndefinedError -from jinja2.nodes import EvalContext from jinja2.utils import concat as j2_concat from jinja2.runtime import Context, StrictUndefined - from ansible import constants as C from ansible.compat.six import string_types, text_type from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable @@ -42,7 +40,6 @@ from ansible.template.template import AnsibleJ2Template from ansible.template.vars import AnsibleJ2Vars from ansible.module_utils._text import to_native, to_text -from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var try: from hashlib import sha1 @@ -127,13 +124,6 @@ def _count_newlines_from_end(in_str): # Uncommon cases: zero length string and string containing only newlines return i -class AnsibleEvalContext(EvalContext): - ''' - A custom jinja2 EvalContext, which is currently unused and saved - here for possible future use. - ''' - pass - class AnsibleContext(Context): ''' A custom context, which intercepts resolve() calls and sets a flag @@ -143,7 +133,6 @@ class AnsibleContext(Context): ''' def __init__(self, *args, **kwargs): super(AnsibleContext, self).__init__(*args, **kwargs) - self.eval_ctx = AnsibleEvalContext(self.environment, self.name) self.unsafe = False def _is_unsafe(self, val): @@ -335,7 +324,7 @@ def set_available_variables(self, variables): self._available_variables = variables self._cached_result = {} - def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars = [''], cache = True, bare_deprecated=True): + def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True, static_vars = [''], cache = True, bare_deprecated=True, disable_lookups=False): ''' Templates (possibly recursively) any given data as input. If convert_bare is set to True, the given data will be wrapped as a jinja2 variable ('{{foo}}') @@ -391,6 +380,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True escape_backslashes=escape_backslashes, fail_on_undefined=fail_on_undefined, overrides=overrides, + disable_lookups=disable_lookups, ) unsafe = hasattr(result, '__UNSAFE__') if convert_data and not self._no_type_regex.match(variable) and not unsafe: @@ -401,6 +391,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True if eval_results[1] is None: result = eval_results[0] if unsafe: + from ansible.vars.unsafe_proxy import wrap_var result = wrap_var(result) else: # FIXME: if the safe_eval raised an error, should we do something with it? @@ -482,6 +473,9 @@ def _finalize(self, thing): ''' return thing if thing is not None else '' + def _fail_lookup(self, name, *args, **kwargs): + raise AnsibleError("The lookup `%s` was found, however lookups were disabled from templating" % name) + def _lookup(self, name, *args, **kwargs): instance = self._lookup_loader.get(name.lower(), loader=self._loader, templar=self) @@ -501,6 +495,7 @@ def _lookup(self, name, *args, **kwargs): ran = None if ran: + from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var if wantlist: ran = wrap_var(ran) else: @@ -516,7 +511,7 @@ def _lookup(self, name, *args, **kwargs): else: raise AnsibleError("lookup plugin (%s) not found" % name) - def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None): + def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, disable_lookups=False): # For preserving the number of input newlines in the output (used # later in this method) data_newlines = _count_newlines_from_end(data) @@ -560,7 +555,11 @@ def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes= else: return data - t.globals['lookup'] = self._lookup + if disable_lookups: + t.globals['lookup'] = self._fail_lookup + else: + t.globals['lookup'] = self._lookup + t.globals['finalize'] = self._finalize jvars = AnsibleJ2Vars(self, t.globals) @@ -571,6 +570,7 @@ def do_template(self, data, preserve_trailing_newlines=True, escape_backslashes= try: res = j2_concat(rf) if new_context.unsafe: + from ansible.vars.unsafe_proxy import wrap_var res = wrap_var(res) except TypeError as te: if 'StrictUndefined' in to_native(te):