VYPR
Moderate severityNVD Advisory· Published Jul 30, 2019· Updated Aug 4, 2024

CVE-2019-10156

CVE-2019-10156

Description

A flaw was discovered in the way Ansible templating was implemented in versions before 2.6.18, 2.7.12 and 2.8.2, causing the possibility of information disclosure through unexpected variable substitution. By taking advantage of unintended variable substitution the content of any variable may be disclosed.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ansiblePyPI
< 2.6.182.6.18
ansiblePyPI
>= 2.7.0a1, < 2.7.122.7.12
ansiblePyPI
>= 2.8.0a1, < 2.8.22.8.2

Affected products

1

Patches

3
04e94274fb92

safe_eval fix (#57188)

https://github.com/ansible/ansibleBrian CocaJun 6, 2019via ghsa
10 files changed · +73 12
  • changelogs/fragments/fix_safe_eval.yml+2 0 added
    @@ -0,0 +1,2 @@
    +bugfixes:
    +    - Handle improper variable substitution that was happening in safe_eval, it was always meant to just do 'type enforcement' and have Jinja2 deal with all variable interpolation. Also see CVE-2019-10156
    
  • lib/ansible/template/__init__.py+1 1 modified
    @@ -545,7 +545,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True
                                     # if this looks like a dictionary or list, convert it to such using the safe_eval method
                                     if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
                                             result.startswith("[") or result in ("True", "False"):
    -                                    eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
    +                                    eval_results = safe_eval(result, include_exceptions=True)
                                         if eval_results[1] is None:
                                             result = eval_results[0]
                                             if unsafe:
    
  • lib/ansible/template/safe_eval.py+6 2 modified
    @@ -42,10 +42,14 @@ def safe_eval(expr, locals=None, include_exceptions=False):
     
         # define certain JSON types
         # eg. JSON booleans are unknown to python eval()
    -    JSON_TYPES = {
    +    OUR_GLOBALS = {
    +        '__builtins__': {},  # avoid global builtins as per eval docs
             'false': False,
             'null': None,
             'true': True,
    +        # also add back some builtins we do need
    +        'True': True,
    +        'False': False,
         }
     
         # this is the whitelist of AST nodes we are going to
    @@ -138,7 +142,7 @@ def generic_visit(self, node, inside_call=False):
             # Note: passing our own globals and locals here constrains what
             # callables (and other identifiers) are recognized.  this is in
             # addition to the filtering of builtins done in CleansingNodeVisitor
    -        result = eval(compiled, JSON_TYPES, dict(locals))
    +        result = eval(compiled, OUR_GLOBALS, dict(locals))
     
             if include_exceptions:
                 return (result, None)
    
  • test/integration/targets/docker_image/tasks/tests/old-options.yml+1 1 modified
    @@ -5,7 +5,7 @@
     
     - name: Registering image name
       set_fact:
    -    inames: "{{ inames }} + [iname]"
    +    inames: "{{ inames + [iname]}}"
     
     ####################################################################
     ## build ###########################################################
    
  • test/integration/targets/meraki_static_route/tasks/main.yml+3 3 modified
    @@ -35,7 +35,7 @@
         register: create_route
     
       - set_fact:
    -      route_ids: "{{ route_ids }} + [ '{{ create_route.data.id }}' ]"
    +      route_ids: "{{ route_ids + [create_route.data.id] }}"
     
       - name: Create second static_route
         meraki_static_route:
    @@ -50,7 +50,7 @@
         register: second_create
     
       - set_fact:
    -      route_ids: "{{ route_ids }} + [ '{{ second_create.data.id }}' ]"
    +      route_ids: "{{ route_ids + [second_create.data.id] }}"
     
       - assert:
           that:
    @@ -166,4 +166,4 @@
             state: absent
             org_name: '{{test_org_name}}'
             net_name: IntTestNetwork
    -      delegate_to: localhost
    \ No newline at end of file
    +      delegate_to: localhost
    
  • test/integration/targets/netapp_eseries_host/tasks/run.yml+2 2 modified
    @@ -204,7 +204,7 @@
       set_fact:
         port_info: []
     - set_fact:
    -    port_info: "{{ port_info }} + [{{ item[0] |combine(item[1]) }}]"
    +    port_info: "{{ port_info + [item[0] |combine(item[1])] }}"
       loop: "{{ tmp }}"
     
     # Compile list of expected host port information for verifying changes
    @@ -225,7 +225,7 @@
       set_fact:
         expected_port_info: []
     - set_fact:
    -    expected_port_info: "{{ expected_port_info }} + [{{ item[0] |combine(item[1]) }}]"
    +    expected_port_info: "{{ expected_port_info + [ item[0] |combine(item[1]) ] }}"
       loop: "{{ tmp }}"
     
     # Verify that each host object has the expected protocol type and address/port
    
  • test/integration/targets/postgresql/tasks/main.yml+1 1 modified
    @@ -235,7 +235,7 @@
         - 'yes'
     
     - set_fact:
    -    encryption_values: '{{ encryption_values }} + ["no"]'
    +    encryption_values: '{{ encryption_values + ["no"]}}'
       when: postgres_version_resp.stdout is version('10', '<=')
     
     - include: test_password.yml
    
  • test/integration/targets/template/corner_cases.yml+51 0 added
    @@ -0,0 +1,51 @@
    +- name: test tempating corner cases
    +  hosts: localhost
    +  gather_facts: false
    +  vars:
    +    empty_list: []
    +    dont: I SHOULD NOT BE TEMPLATED
    +    other: I WORK
    +  tasks:
    +    - name: 'ensure we are not interpolating data from outside of j2 delmiters'
    +      assert:
    +        that:
    +            - '"I SHOULD NOT BE TEMPLATED" not in adjacent'
    +            - globals1 == "[[], globals()]"
    +            - globals2 == "[[], globals]"
    +      vars:
    +        adjacent: "{{ empty_list }} + [dont]"
    +        globals1: "[{{ empty_list }}, globals()]"
    +        globals2: "[{{ empty_list }}, globals]"
    +
    +    - name: 'ensure we can add lists'
    +      assert:
    +        that:
    +            - (empty_list + [other]) == [other]
    +            - (empty_list + [other, other]) == [other, other]
    +            - (dont_exist|default([]) + [other]) == [other]
    +            - ([other] + [empty_list, other]) == [other, [], other]
    +
    +    - name: 'ensure comments go away and we still dont interpolate in string'
    +      assert:
    +        that:
    +            - 'comm1 == " + [dont]"'
    +            - 'comm2 == " #} + [dont]"'
    +      vars:
    +        comm1: '{# {{nothing}} {# #} + [dont]'
    +        comm2: "{# {{nothing}} {# #} #} + [dont]"
    +
    +    - name: test additions with facts, set them up
    +      set_fact:
    +        inames: []
    +        iname: "{{ prefix ~ '-options' }}"
    +        iname_1: "{{ prefix ~ '-options-1' }}"
    +      vars:
    +        prefix: 'bo'
    +
    +    - name: add the facts
    +      set_fact:
    +        inames: '{{ inames + [iname, iname_1] }}'
    +
    +    - assert:
    +        that:
    +            - inames == ['bo-options', 'bo-options-1']
    
  • test/integration/targets/template/runme.sh+4 0 modified
    @@ -12,3 +12,7 @@ ansible-playbook ansible_managed.yml -c  ansible_managed.cfg -i ../../inventory
     
     # Test for #42585
     ANSIBLE_ROLES_PATH=../ ansible-playbook custom_template.yml -i ../../inventory -e @../../integration_config.yml -v "$@"
    +
    +
    +# Test for several corner cases #57188
    +ansible-playbook corner_cases.yml -v "$@"
    
  • test/legacy/ovs.yaml+2 2 modified
    @@ -22,7 +22,7 @@
             when: "limit_to in ['*', 'openvswitch_db']"
           rescue:
             - set_fact:
    -            failed_modules: "{{ failed_modules }} + [ 'openvswitch_db' ]"
    +            failed_modules: "{{ failed_modules + [ 'openvswitch_db' ]}}"
                 test_failed: true
     
     
    @@ -33,4 +33,4 @@
         - name: Has any previous test failed?
           fail:
             msg: "One or more tests failed, check log for details"
    -      when: test_failed
    \ No newline at end of file
    +      when: test_failed
    
3ff6505e8ff0

safe_eval fix (#57188)

https://github.com/ansible/ansibleBrian CocaJun 6, 2019via ghsa
7 files changed · +66 6
  • changelogs/fragments/fix_safe_eval.yml+2 0 added
    @@ -0,0 +1,2 @@
    +bugfixes:
    +    - Handle improper variable substitution that was happening in safe_eval, it was always meant to just do 'type enforcement' and have Jinja2 deal with all variable interpolation. Also see CVE-2019-10156
    
  • lib/ansible/template/__init__.py+1 1 modified
    @@ -484,7 +484,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True
                                 # if this looks like a dictionary or list, convert it to such using the safe_eval method
                                 if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
                                         result.startswith("[") or result in ("True", "False"):
    -                                eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
    +                                eval_results = safe_eval(result, include_exceptions=True)
                                     if eval_results[1] is None:
                                         result = eval_results[0]
                                         if unsafe:
    
  • lib/ansible/template/safe_eval.py+6 2 modified
    @@ -42,10 +42,14 @@ def safe_eval(expr, locals=None, include_exceptions=False):
     
         # define certain JSON types
         # eg. JSON booleans are unknown to python eval()
    -    JSON_TYPES = {
    +    OUR_GLOBALS = {
    +        '__builtins__': {},  # avoid global builtins as per eval docs
             'false': False,
             'null': None,
             'true': True,
    +        # also add back some builtins we do need
    +        'True': True,
    +        'False': False,
         }
     
         # this is the whitelist of AST nodes we are going to
    @@ -130,7 +134,7 @@ def generic_visit(self, node, inside_call=False):
             # Note: passing our own globals and locals here constrains what
             # callables (and other identifiers) are recognized.  this is in
             # addition to the filtering of builtins done in CleansingNodeVisitor
    -        result = eval(compiled, JSON_TYPES, dict(locals))
    +        result = eval(compiled, OUR_GLOBALS, dict(locals))
     
             if include_exceptions:
                 return (result, None)
    
  • test/integration/targets/postgresql/tasks/main.yml+1 1 modified
    @@ -203,7 +203,7 @@
         - 'yes'
     
     - set_fact:
    -    encryption_values: '{{ encryption_values }} + ["no"]'
    +    encryption_values: '{{ encryption_values + ["no"]}}'
       when: postgres_version_resp.stdout is version('10', '<=')
     
     - include: test_password.yml
    
  • test/integration/targets/template/corner_cases.yml+51 0 added
    @@ -0,0 +1,51 @@
    +- name: test tempating corner cases
    +  hosts: localhost
    +  gather_facts: false
    +  vars:
    +    empty_list: []
    +    dont: I SHOULD NOT BE TEMPLATED
    +    other: I WORK
    +  tasks:
    +    - name: 'ensure we are not interpolating data from outside of j2 delmiters'
    +      assert:
    +        that:
    +            - '"I SHOULD NOT BE TEMPLATED" not in adjacent'
    +            - globals1 == "[[], globals()]"
    +            - globals2 == "[[], globals]"
    +      vars:
    +        adjacent: "{{ empty_list }} + [dont]"
    +        globals1: "[{{ empty_list }}, globals()]"
    +        globals2: "[{{ empty_list }}, globals]"
    +
    +    - name: 'ensure we can add lists'
    +      assert:
    +        that:
    +            - (empty_list + [other]) == [other]
    +            - (empty_list + [other, other]) == [other, other]
    +            - (dont_exist|default([]) + [other]) == [other]
    +            - ([other] + [empty_list, other]) == [other, [], other]
    +
    +    - name: 'ensure comments go away and we still dont interpolate in string'
    +      assert:
    +        that:
    +            - 'comm1 == " + [dont]"'
    +            - 'comm2 == " #} + [dont]"'
    +      vars:
    +        comm1: '{# {{nothing}} {# #} + [dont]'
    +        comm2: "{# {{nothing}} {# #} #} + [dont]"
    +
    +    - name: test additions with facts, set them up
    +      set_fact:
    +        inames: []
    +        iname: "{{ prefix ~ '-options' }}"
    +        iname_1: "{{ prefix ~ '-options-1' }}"
    +      vars:
    +        prefix: 'bo'
    +
    +    - name: add the facts
    +      set_fact:
    +        inames: '{{ inames + [iname, iname_1] }}'
    +
    +    - assert:
    +        that:
    +            - inames == ['bo-options', 'bo-options-1']
    
  • test/integration/targets/template/runme.sh+3 0 modified
    @@ -9,3 +9,6 @@ ansible testhost -i testhost, -m debug -a 'msg={{ hostvars["localhost"] }}' -e "
     
     # Test for https://github.com/ansible/ansible/issues/27262
     ansible-playbook ansible_managed.yml -c  ansible_managed.cfg -i ../../inventory -e @../../integration_config.yml -v "$@"
    +
    +# Test for several corner cases #57188
    +ansible-playbook corner_cases.yml -v "$@"
    
  • test/legacy/ovs.yaml+2 2 modified
    @@ -22,7 +22,7 @@
             when: "limit_to in ['*', 'openvswitch_db']"
           rescue:
             - set_fact:
    -            failed_modules: "{{ failed_modules }} + [ 'openvswitch_db' ]"
    +            failed_modules: "{{ failed_modules + [ 'openvswitch_db' ]}}"
                 test_failed: true
     
     
    @@ -33,4 +33,4 @@
         - name: Has any previous test failed?
           fail:
             msg: "One or more tests failed, check log for details"
    -      when: test_failed
    \ No newline at end of file
    +      when: test_failed
    
a11c3edfa41e

safe_eval fix (#57188)

https://github.com/ansible/ansibleBrian CocaJun 6, 2019via ghsa
8 files changed · +68 8
  • changelogs/fragments/fix_safe_eval.yml+2 0 added
    @@ -0,0 +1,2 @@
    +bugfixes:
    +    - Handle improper variable substitution that was happening in safe_eval, it was always meant to just do 'type enforcement' and have Jinja2 deal with all variable interpolation. Also see CVE-2019-10156
    
  • lib/ansible/template/__init__.py+1 1 modified
    @@ -501,7 +501,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True
                                     # if this looks like a dictionary or list, convert it to such using the safe_eval method
                                     if (result.startswith("{") and not result.startswith(self.environment.variable_start_string)) or \
                                             result.startswith("[") or result in ("True", "False"):
    -                                    eval_results = safe_eval(result, locals=self._available_variables, include_exceptions=True)
    +                                    eval_results = safe_eval(result, include_exceptions=True)
                                         if eval_results[1] is None:
                                             result = eval_results[0]
                                             if unsafe:
    
  • lib/ansible/template/safe_eval.py+6 2 modified
    @@ -42,10 +42,14 @@ def safe_eval(expr, locals=None, include_exceptions=False):
     
         # define certain JSON types
         # eg. JSON booleans are unknown to python eval()
    -    JSON_TYPES = {
    +    OUR_GLOBALS = {
    +        '__builtins__': {},  # avoid global builtins as per eval docs
             'false': False,
             'null': None,
             'true': True,
    +        # also add back some builtins we do need
    +        'True': True,
    +        'False': False,
         }
     
         # this is the whitelist of AST nodes we are going to
    @@ -130,7 +134,7 @@ def generic_visit(self, node, inside_call=False):
             # Note: passing our own globals and locals here constrains what
             # callables (and other identifiers) are recognized.  this is in
             # addition to the filtering of builtins done in CleansingNodeVisitor
    -        result = eval(compiled, JSON_TYPES, dict(locals))
    +        result = eval(compiled, OUR_GLOBALS, dict(locals))
     
             if include_exceptions:
                 return (result, None)
    
  • test/integration/targets/netapp_eseries_host/tasks/run.yml+2 2 modified
    @@ -204,7 +204,7 @@
       set_fact:
         port_info: []
     - set_fact:
    -    port_info: "{{ port_info }} + [{{ item[0] |combine(item[1]) }}]"
    +    port_info: "{{ port_info + [item[0] |combine(item[1])] }}"
       loop: "{{ tmp }}"
     
     # Compile list of expected host port information for verifying changes
    @@ -225,7 +225,7 @@
       set_fact:
         expected_port_info: []
     - set_fact:
    -    expected_port_info: "{{ expected_port_info }} + [{{ item[0] |combine(item[1]) }}]"
    +    expected_port_info: "{{ expected_port_info + [ item[0] |combine(item[1]) ] }}"
       loop: "{{ tmp }}"
     
     # Verify that each host object has the expected protocol type and address/port
    
  • test/integration/targets/postgresql/tasks/main.yml+1 1 modified
    @@ -203,7 +203,7 @@
         - 'yes'
     
     - set_fact:
    -    encryption_values: '{{ encryption_values }} + ["no"]'
    +    encryption_values: '{{ encryption_values + ["no"]}}'
       when: postgres_version_resp.stdout is version('10', '<=')
     
     - include: test_password.yml
    
  • test/integration/targets/template/corner_cases.yml+51 0 added
    @@ -0,0 +1,51 @@
    +- name: test tempating corner cases
    +  hosts: localhost
    +  gather_facts: false
    +  vars:
    +    empty_list: []
    +    dont: I SHOULD NOT BE TEMPLATED
    +    other: I WORK
    +  tasks:
    +    - name: 'ensure we are not interpolating data from outside of j2 delmiters'
    +      assert:
    +        that:
    +            - '"I SHOULD NOT BE TEMPLATED" not in adjacent'
    +            - globals1 == "[[], globals()]"
    +            - globals2 == "[[], globals]"
    +      vars:
    +        adjacent: "{{ empty_list }} + [dont]"
    +        globals1: "[{{ empty_list }}, globals()]"
    +        globals2: "[{{ empty_list }}, globals]"
    +
    +    - name: 'ensure we can add lists'
    +      assert:
    +        that:
    +            - (empty_list + [other]) == [other]
    +            - (empty_list + [other, other]) == [other, other]
    +            - (dont_exist|default([]) + [other]) == [other]
    +            - ([other] + [empty_list, other]) == [other, [], other]
    +
    +    - name: 'ensure comments go away and we still dont interpolate in string'
    +      assert:
    +        that:
    +            - 'comm1 == " + [dont]"'
    +            - 'comm2 == " #} + [dont]"'
    +      vars:
    +        comm1: '{# {{nothing}} {# #} + [dont]'
    +        comm2: "{# {{nothing}} {# #} #} + [dont]"
    +
    +    - name: test additions with facts, set them up
    +      set_fact:
    +        inames: []
    +        iname: "{{ prefix ~ '-options' }}"
    +        iname_1: "{{ prefix ~ '-options-1' }}"
    +      vars:
    +        prefix: 'bo'
    +
    +    - name: add the facts
    +      set_fact:
    +        inames: '{{ inames + [iname, iname_1] }}'
    +
    +    - assert:
    +        that:
    +            - inames == ['bo-options', 'bo-options-1']
    
  • test/integration/targets/template/runme.sh+3 0 modified
    @@ -9,3 +9,6 @@ ansible testhost -i testhost, -m debug -a 'msg={{ hostvars["localhost"] }}' -e "
     
     # Test for https://github.com/ansible/ansible/issues/27262
     ansible-playbook ansible_managed.yml -c  ansible_managed.cfg -i ../../inventory -e @../../integration_config.yml -v "$@"
    +
    +# Test for several corner cases #57188
    +ansible-playbook corner_cases.yml -v "$@"
    
  • test/legacy/ovs.yaml+2 2 modified
    @@ -22,7 +22,7 @@
             when: "limit_to in ['*', 'openvswitch_db']"
           rescue:
             - set_fact:
    -            failed_modules: "{{ failed_modules }} + [ 'openvswitch_db' ]"
    +            failed_modules: "{{ failed_modules + [ 'openvswitch_db' ]}}"
                 test_failed: true
     
     
    @@ -33,4 +33,4 @@
         - name: Has any previous test failed?
           fail:
             msg: "One or more tests failed, check log for details"
    -      when: test_failed
    \ No newline at end of file
    +      when: test_failed
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

13

News mentions

0

No linked articles in our index yet.