Security fixes:

* Strip lookup calls out of inventory variables and clean unsafe data
  returned from lookup plugins (CVE-2014-4966)
* Make sure vars don't insert extra parameters into module args and prevent
  duplicate params from superseding previous params (CVE-2014-4967)
This commit is contained in:
James Cammarata
2014-07-21 11:20:49 -05:00
parent 00e089e503
commit 84759faa09
8 changed files with 178 additions and 65 deletions

View File

@@ -53,6 +53,10 @@ VERBOSITY=0
MAX_FILE_SIZE_FOR_DIFF=1*1024*1024
# caching the compilation of the regex used
# to check for lookup calls within data
LOOKUP_REGEX=re.compile(r'lookup\s*\(')
try:
import json
except ImportError:
@@ -341,38 +345,44 @@ def json_loads(data):
return json.loads(data)
def _clean_data(orig_data):
def _clean_data(orig_data, from_remote=False, from_inventory=False):
''' remove template tags from a string '''
data = orig_data
if isinstance(orig_data, basestring):
for pattern,replacement in (('{{','{#'), ('}}','#}'), ('{%','{#'), ('%}','#}')):
sub_list = [('{%','{#'), ('%}','#}')]
if from_remote or (from_inventory and '{{' in data and LOOKUP_REGEX.search(data)):
# if from a remote, we completely disable any jinja2 blocks
sub_list.extend([('{{','{#'), ('}}','#}')])
for pattern,replacement in sub_list:
data = data.replace(pattern, replacement)
return data
def _clean_data_struct(orig_data):
def _clean_data_struct(orig_data, from_remote=False, from_inventory=False):
'''
walk a complex data structure, and use _clean_data() to
remove any template tags that may exist
'''
if not from_remote and not from_inventory:
raise errors.AnsibleErrors("when cleaning data, you must specify either from_remote or from_inventory")
if isinstance(orig_data, dict):
data = orig_data.copy()
for key in data:
new_key = _clean_data_struct(key)
new_val = _clean_data_struct(data[key])
new_key = _clean_data_struct(key, from_remote, from_inventory)
new_val = _clean_data_struct(data[key], from_remote, from_inventory)
if key != new_key:
del data[key]
data[new_key] = new_val
elif isinstance(orig_data, list):
data = orig_data[:]
for i in range(0, len(data)):
data[i] = _clean_data_struct(data[i])
data[i] = _clean_data_struct(data[i], from_remote, from_inventory)
elif isinstance(orig_data, basestring):
data = _clean_data(orig_data)
data = _clean_data(orig_data, from_remote, from_inventory)
else:
data = orig_data
return data
def parse_json(raw_data, from_remote=False):
def parse_json(raw_data, from_remote=False, from_inventory=False):
''' this version for module return data only '''
orig_data = raw_data
@@ -407,10 +417,31 @@ def parse_json(raw_data, from_remote=False):
return { "failed" : True, "parsed" : False, "msg" : orig_data }
if from_remote:
results = _clean_data_struct(results)
results = _clean_data_struct(results, from_remote, from_inventory)
return results
def merge_module_args(current_args, new_args):
'''
merges either a dictionary or string of k=v pairs with another string of k=v pairs,
and returns a new k=v string without duplicates.
'''
if not isinstance(current_args, basestring):
raise errors.AnsibleError("expected current_args to be a basestring")
# we use parse_kv to split up the current args into a dictionary
final_args = parse_kv(current_args)
if isinstance(new_args, dict):
final_args.update(new_args)
elif isinstance(new_args, basestring):
new_args_kv = parse_kv(new_args)
final_args.update(new_args_kv)
# then we re-assemble into a string
module_args = ""
for (k,v) in final_args.iteritems():
if isinstance(v, basestring):
module_args = "%s=%s %s" % (k, pipes.quote(v), module_args)
return module_args.strip()
def smush_braces(data):
''' smush Jinaj2 braces so unresolved templates like {{ foo }} don't get parsed weird by key=value code '''
while '{{ ' in data:
@@ -641,7 +672,7 @@ def parse_kv(args):
for x in vargs:
if "=" in x:
k, v = x.split("=",1)
options[k]=v
options[k] = v
return options
def merge_hash(a, b):
@@ -1089,11 +1120,14 @@ def list_intersection(a, b):
def safe_eval(expr, locals={}, include_exceptions=False):
'''
this is intended for allowing things like:
This is intended for allowing things like:
with_items: a_list_variable
where Jinja2 would return a string
but we do not want to allow it to call functions (outside of Jinja2, where
the env is constrained)
Where Jinja2 would return a string but we do not want to allow it to
call functions (outside of Jinja2, where the env is constrained). If
the input data to this function came from an untrusted (remote) source,
it should first be run through _clean_data_struct() to ensure the data
is further sanitized prior to evaluation.
Based on:
http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe