From ed56f51f185a1ffd7ea57130d260098686fcc7c2 Mon Sep 17 00:00:00 2001
From: James Cammarata <jimi@sngx.net>
Date: Mon, 8 May 2017 10:37:10 -0500
Subject: [PATCH] Fixing security issue with lookup returns not tainting the
 jinja2 environment

CVE-2017-7481

Lookup returns wrap the result in unsafe, however when used through the
standard templar engine, this does not result in the jinja2 environment being
marked as unsafe as a whole. This means the lookup result looses the unsafe
protection and may become simple unicode strings, which can result in bad
things being re-templated.

This also adds a global lookup param and cfg options for lookups to allow
unsafe returns, so users can force the previous (insecure) behavior.
---
 docs/docsite/rst/intro_configuration.rst | 14 ++++++++++++++
 examples/ansible.cfg                     |  8 +++++++-
 lib/ansible/constants.py                 |  1 +
 lib/ansible/template/__init__.py         | 11 +++++++++--
 4 files changed, 31 insertions(+), 3 deletions(-)

Index: ansible-2.2.0.0/examples/ansible.cfg
===================================================================
--- ansible-2.2.0.0.orig/examples/ansible.cfg
+++ ansible-2.2.0.0/examples/ansible.cfg
@@ -265,6 +265,12 @@
 # set to 0 for unlimited (RAM may suffer!).
 #max_diff_size = 1048576
 
+# When enabled, this option allows lookups (via variables like {{lookup('foo')}} or when used as
+# a loop with `with_foo`) to return data that is not marked "unsafe". This means the data may contain
+# jinja2 templating language which will be run through the templating engine.
+# ENABLING THIS COULD BE A SECURITY RISK
+#allow_unsafe_lookups = False
+
 [privilege_escalation]
 #become=True
 #become_method=sudo
Index: ansible-2.2.0.0/lib/ansible/constants.py
===================================================================
--- ansible-2.2.0.0.orig/lib/ansible/constants.py
+++ ansible-2.2.0.0/lib/ansible/constants.py
@@ -191,6 +191,7 @@ DEFAULT_FORCE_HANDLERS    = get_config(p
 DEFAULT_INVENTORY_IGNORE  = get_config(p, DEFAULTS, 'inventory_ignore_extensions', 'ANSIBLE_INVENTORY_IGNORE', ["~", ".orig", ".bak", ".ini", ".cfg", ".retry", ".pyc", ".pyo"], islist=True)
 DEFAULT_VAR_COMPRESSION_LEVEL = get_config(p, DEFAULTS, 'var_compression_level', 'ANSIBLE_VAR_COMPRESSION_LEVEL', 0, integer=True)
 DEFAULT_INTERNAL_POLL_INTERVAL = get_config(p, DEFAULTS, 'internal_poll_interval', None, 0.001, floating=True)
+DEFAULT_ALLOW_UNSAFE_LOOKUPS = get_config(p, DEFAULTS, 'allow_unsafe_lookups', None, False, boolean=True)
 ERROR_ON_MISSING_HANDLER  = get_config(p, DEFAULTS, 'error_on_missing_handler', 'ANSIBLE_ERROR_ON_MISSING_HANDLER', True, boolean=True)
 
 # static includes
Index: ansible-2.2.0.0/lib/ansible/template/__init__.py
===================================================================
--- ansible-2.2.0.0.orig/lib/ansible/template/__init__.py
+++ ansible-2.2.0.0/lib/ansible/template/__init__.py
@@ -224,6 +224,9 @@ class Templar:
             loader=FileSystemLoader(self._basedir),
         )
 
+        # the current rendering context under which the templar class is working
+        self.cur_context = None
+
         self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string))
 
         self.block_start    = self.environment.block_start_string
@@ -487,6 +490,7 @@ class Templar:
 
         if instance is not None:
             wantlist = kwargs.pop('wantlist', False)
+            allow_unsafe = kwargs.pop('allow_unsafe', C.DEFAULT_ALLOW_UNSAFE_LOOKUPS)
 
             from ansible.utils.listify import listify_lookup_plugin_terms
             loop_terms = listify_lookup_plugin_terms(terms=args, templar=self, loader=self._loader, fail_on_undefined=True, convert_bare=False)
@@ -500,7 +504,8 @@ class Templar:
                     raise
                 ran = None
 
-            if ran:
+            if ran and not allow_unsafe:
+                from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var
                 if wantlist:
                     ran = wrap_var(ran)
                 else:
@@ -512,6 +517,8 @@ class Templar:
                         else:
                             ran = wrap_var(ran)
 
+                if self.cur_context:
+                    self.cur_context.unsafe = True
             return ran
         else:
             raise AnsibleError("lookup plugin (%s) not found" % name)
@@ -565,7 +572,7 @@ class Templar:
 
             jvars = AnsibleJ2Vars(self, t.globals)
 
-            new_context = t.new_context(jvars, shared=True)
+            self.cur_context = new_context = t.new_context(jvars, shared=True)
             rf = t.root_render_func(new_context)
 
             try:
