mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 22:02:50 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user