Fix CVE-2022-3697 CVE-2023-5115
(cherry picked from commit df1a4b8f2ff1520ed099c383f74797c4f9eace39)
This commit is contained in:
parent
ce0b6dd397
commit
738fb41810
366
CVE-2022-3697.patch
Normal file
366
CVE-2022-3697.patch
Normal file
@ -0,0 +1,366 @@
|
||||
From: =?utf-8?q?Bastien_Roucari=C3=A8s?= <rouca@debian.org>
|
||||
Date: Sun, 24 Dec 2023 11:59:07 +0000
|
||||
Subject: CVE-2022-3697: ec2_instance - validate options on tower_callback
|
||||
|
||||
A flaw was found in Ansible in the amazon.aws collection when using the
|
||||
tower_callback parameter from the amazon.aws.ec2_instance module.
|
||||
This flaw allows an attacker to take advantage of this issue as
|
||||
the module is handling the parameter insecurely, leading to the password leaking in the logs
|
||||
|
||||
https://salsa.debian.org/python-team/packages/ansible/-/blob/debian/2.7.7+dfsg-1+deb10u2/debian/patches/0029-CVE-2022-3697-ec2_instance-validate-options-on-tower.patch?ref_type=tags
|
||||
origin: https://patch-diff.githubusercontent.com/raw/ansible-collections/amazon.aws/pull/1207.patch
|
||||
bug: https://github.com/ansible-collections/amazon.aws/pull/1199
|
||||
bug-redhat: https://bugzilla.redhat.com/show_bug.cgi?id=2137664
|
||||
|
||||
---
|
||||
.../20221021-ec2_instance-tower_callback.yml | 5 +
|
||||
lib/ansible/module_utils/aws/tower.py | 83 +++++++++++
|
||||
.../modules/cloud/amazon/ec2_instance.py | 137 ++++++++----------
|
||||
test/units/module_utils/aws/test_tower.py | 40 +++++
|
||||
4 files changed, 187 insertions(+), 78 deletions(-)
|
||||
create mode 100644 changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
create mode 100644 lib/ansible/module_utils/aws/tower.py
|
||||
create mode 100644 test/units/module_utils/aws/test_tower.py
|
||||
|
||||
diff --git a/changelogs/fragments/20221021-ec2_instance-tower_callback.yml b/changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
new file mode 100644
|
||||
index 00000000..ae961144
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
@@ -0,0 +1,5 @@
|
||||
+minor_changes:
|
||||
+- ec2_instance - refacter ``tower_callback`` code to handle parameter validation as part of the argument specification (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
+- ec2_instance - the ``tower_callback`` parameter has been renamed to ``aap_callback``, ``tower_callback`` remains as an alias. This change should have no observable effect for users outside the module documentation (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
+security_fixes:
|
||||
+- ec2_instance - fixes leak of password into logs when using ``tower_callback.windows=True`` and ``tower_callback.set_password`` (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
diff --git a/lib/ansible/module_utils/aws/tower.py b/lib/ansible/module_utils/aws/tower.py
|
||||
new file mode 100644
|
||||
index 00000000..dd7d9738
|
||||
--- /dev/null
|
||||
+++ b/lib/ansible/module_utils/aws/tower.py
|
||||
@@ -0,0 +1,83 @@
|
||||
+# Copyright: Ansible Project
|
||||
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
+
|
||||
+from __future__ import absolute_import, division, print_function
|
||||
+__metaclass__ = type
|
||||
+
|
||||
+import string
|
||||
+import textwrap
|
||||
+
|
||||
+from ansible.module_utils._text import to_native
|
||||
+from ansible.module_utils.six.moves.urllib import parse as urlparse
|
||||
+
|
||||
+
|
||||
+def _windows_callback_script(passwd=None):
|
||||
+ script_url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
|
||||
+ if passwd is not None:
|
||||
+ passwd = passwd.replace("'", "''")
|
||||
+ script_tpl = """\
|
||||
+ <powershell>
|
||||
+ $admin = [adsi]('WinNT://./administrator, user')
|
||||
+ $admin.PSBase.Invoke('SetPassword', '${PASS}')
|
||||
+ Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('${SCRIPT}'))
|
||||
+ </powershell>
|
||||
+ """
|
||||
+ else:
|
||||
+ script_tpl = """\
|
||||
+ <powershell>
|
||||
+ $admin = [adsi]('WinNT://./administrator, user')
|
||||
+ Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('${SCRIPT}'))
|
||||
+ </powershell>
|
||||
+ """
|
||||
+
|
||||
+ tpl = string.Template(textwrap.dedent(script_tpl))
|
||||
+ return tpl.safe_substitute(PASS=passwd, SCRIPT=script_url)
|
||||
+
|
||||
+
|
||||
+def _linux_callback_script(tower_address, template_id, host_config_key):
|
||||
+ template_id = urlparse.quote(template_id)
|
||||
+ tower_address = urlparse.quote(tower_address)
|
||||
+ host_config_key = host_config_key.replace("'", "'\"'\"'")
|
||||
+
|
||||
+ script_tpl = """\
|
||||
+ #!/bin/bash
|
||||
+ set -x
|
||||
+
|
||||
+ retry_attempts=10
|
||||
+ attempt=0
|
||||
+ while [[ $attempt -lt $retry_attempts ]]
|
||||
+ do
|
||||
+ status_code=$(curl --max-time 10 -v -k -s -i \
|
||||
+ --data 'host_config_key=${host_config_key}' \
|
||||
+ 'https://${tower_address}/api/v2/job_templates/${template_id}/callback/' \
|
||||
+ | head -n 1 \
|
||||
+ | awk '{print $2}')
|
||||
+ if [[ $status_code == 404 ]]
|
||||
+ then
|
||||
+ status_code=$(curl --max-time 10 -v -k -s -i \
|
||||
+ --data 'host_config_key=${host_config_key}' \
|
||||
+ 'https://${tower_address}/api/v1/job_templates/${template_id}/callback/' \
|
||||
+ | head -n 1 \
|
||||
+ | awk '{print $2}')
|
||||
+ # fall back to using V1 API for Tower 3.1 and below, since v2 API will always 404
|
||||
+ fi
|
||||
+ if [[ $status_code == 201 ]]
|
||||
+ then
|
||||
+ exit 0
|
||||
+ fi
|
||||
+ attempt=$(( attempt + 1 ))
|
||||
+ echo "$${status_code} received... retrying in 1 minute. (Attempt $${attempt})"
|
||||
+ sleep 60
|
||||
+ done
|
||||
+ exit 1
|
||||
+ """
|
||||
+ tpl = string.Template(textwrap.dedent(script_tpl))
|
||||
+ return tpl.safe_substitute(tower_address=tower_address,
|
||||
+ template_id=template_id,
|
||||
+ host_config_key=host_config_key)
|
||||
+
|
||||
+
|
||||
+def tower_callback_script(tower_address, job_template_id, host_config_key, windows, passwd):
|
||||
+ if windows:
|
||||
+ return to_native(_windows_callback_script(passwd=passwd))
|
||||
+ return _linux_callback_script(tower_address, job_template_id, host_config_key)
|
||||
diff --git a/lib/ansible/modules/cloud/amazon/ec2_instance.py b/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
index fdde1eef..c74d8820 100644
|
||||
--- a/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
+++ b/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
@@ -46,19 +46,40 @@ options:
|
||||
user_data:
|
||||
description:
|
||||
- Opaque blob of data which is made available to the ec2 instance
|
||||
- tower_callback:
|
||||
+ aap_callback:
|
||||
description:
|
||||
- - Preconfigured user-data to enable an instance to perform a Tower callback (Linux only).
|
||||
+ - Preconfigured user-data to enable an instance to perform an Ansible Automation Platform
|
||||
+ callback (Linux only).
|
||||
+ - For Windows instances, to enable remote access via Ansible set I(windows) to C(true), and
|
||||
+ optionally set an admin password.
|
||||
+ - If using I(windows) and I(set_password), callback ton Ansible Automation Platform will not
|
||||
+ be performed but the instance will be ready to receive winrm connections from Ansible.
|
||||
- Mutually exclusive with I(user_data).
|
||||
- - For Windows instances, to enable remote access via Ansible set I(tower_callback.windows) to true, and optionally set an admin password.
|
||||
- - If using 'windows' and 'set_password', callback to Tower will not be performed but the instance will be ready to receive winrm connections from Ansible.
|
||||
+ type: dict
|
||||
+ aliases: ['tower_callback']
|
||||
suboptions:
|
||||
+ windows:
|
||||
+ description:
|
||||
+ - Set I(windows=True) to use powershell instead of bash for the callback script.
|
||||
+ type: bool
|
||||
+ default: False
|
||||
+ set_password:
|
||||
+ description:
|
||||
+ - Optional admin password to use if I(windows=True).
|
||||
+ type: str
|
||||
tower_address:
|
||||
description:
|
||||
- - IP address or DNS name of Tower server. Must be accessible via this address from the VPC that this instance will be launched in.
|
||||
+ - IP address or DNS name of Tower server. Must be accessible via this address from the
|
||||
+ VPC that this instance will be launched in.
|
||||
+ - Required if I(windows=False).
|
||||
+ type: str
|
||||
job_template_id:
|
||||
description:
|
||||
- - Either the integer ID of the Tower Job Template, or the name (name supported only for Tower 3.2+).
|
||||
+ - Either the integer ID of the Tower Job Template, or the name.
|
||||
+ Using a name for the job template is not supported by Ansible Tower prior to version
|
||||
+ 3.2.
|
||||
+ - Required if I(windows=False).
|
||||
+ type: str
|
||||
host_config_key:
|
||||
description:
|
||||
- Host configuration secret key generated by the Tower job template.
|
||||
@@ -729,71 +750,10 @@ from ansible.module_utils.ec2 import (boto3_conn,
|
||||
camel_dict_to_snake_dict)
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
+from ansible.module_utils.aws.tower import tower_callback_script
|
||||
|
||||
module = None
|
||||
|
||||
-
|
||||
-def tower_callback_script(tower_conf, windows=False, passwd=None):
|
||||
- script_url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
|
||||
- if windows and passwd is not None:
|
||||
- script_tpl = """<powershell>
|
||||
- $admin = [adsi]("WinNT://./administrator, user")
|
||||
- $admin.PSBase.Invoke("SetPassword", "{PASS}")
|
||||
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
|
||||
- </powershell>
|
||||
- """
|
||||
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
|
||||
- elif windows and passwd is None:
|
||||
- script_tpl = """<powershell>
|
||||
- $admin = [adsi]("WinNT://./administrator, user")
|
||||
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
|
||||
- </powershell>
|
||||
- """
|
||||
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
|
||||
- elif not windows:
|
||||
- for p in ['tower_address', 'job_template_id', 'host_config_key']:
|
||||
- if p not in tower_conf:
|
||||
- module.fail_json(msg="Incomplete tower_callback configuration. tower_callback.{0} not set.".format(p))
|
||||
-
|
||||
- if isinstance(tower_conf['job_template_id'], string_types):
|
||||
- tower_conf['job_template_id'] = urlparse.quote(tower_conf['job_template_id'])
|
||||
- tpl = string.Template(textwrap.dedent("""#!/bin/bash
|
||||
- set -x
|
||||
-
|
||||
- retry_attempts=10
|
||||
- attempt=0
|
||||
- while [[ $attempt -lt $retry_attempts ]]
|
||||
- do
|
||||
- status_code=`curl --max-time 10 -v -k -s -i \
|
||||
- --data "host_config_key=${host_config_key}" \
|
||||
- 'https://${tower_address}/api/v2/job_templates/${template_id}/callback/' \
|
||||
- | head -n 1 \
|
||||
- | awk '{print $2}'`
|
||||
- if [[ $status_code == 404 ]]
|
||||
- then
|
||||
- status_code=`curl --max-time 10 -v -k -s -i \
|
||||
- --data "host_config_key=${host_config_key}" \
|
||||
- 'https://${tower_address}/api/v1/job_templates/${template_id}/callback/' \
|
||||
- | head -n 1 \
|
||||
- | awk '{print $2}'`
|
||||
- # fall back to using V1 API for Tower 3.1 and below, since v2 API will always 404
|
||||
- fi
|
||||
- if [[ $status_code == 201 ]]
|
||||
- then
|
||||
- exit 0
|
||||
- fi
|
||||
- attempt=$(( attempt + 1 ))
|
||||
- echo "$${status_code} received... retrying in 1 minute. (Attempt $${attempt})"
|
||||
- sleep 60
|
||||
- done
|
||||
- exit 1
|
||||
- """))
|
||||
- return tpl.safe_substitute(tower_address=tower_conf['tower_address'],
|
||||
- template_id=tower_conf['job_template_id'],
|
||||
- host_config_key=tower_conf['host_config_key'])
|
||||
- raise NotImplementedError("Only windows with remote-prep or non-windows with tower job callback supported so far.")
|
||||
-
|
||||
-
|
||||
@AWSRetry.jittered_backoff()
|
||||
def manage_tags(match, new_tags, purge_tags, ec2):
|
||||
changed = False
|
||||
@@ -1084,6 +1044,21 @@ def discover_security_groups(group, groups, parent_vpc_id=None, subnet_id=None,
|
||||
return list(dict((g['GroupId'], g) for g in found_groups).values())
|
||||
|
||||
|
||||
+def build_userdata(params):
|
||||
+ if params.get('user_data') is not None:
|
||||
+ return {'UserData': to_native(params.get('user_data'))}
|
||||
+ if params.get('aap_callback'):
|
||||
+ userdata = tower_callback_script(
|
||||
+ tower_address=params.get('aap_callback').get('tower_address'),
|
||||
+ job_template_id=params.get('aap_callback').get('job_template_id'),
|
||||
+ host_config_key=params.get('aap_callback').get('host_config_key'),
|
||||
+ windows=params.get('aap_callback').get('windows'),
|
||||
+ passwd=params.get('aap_callback').get('set_passwd'),
|
||||
+ )
|
||||
+ return {'UserData': userdata}
|
||||
+ return {}
|
||||
+
|
||||
+
|
||||
def build_top_level_options(params):
|
||||
spec = {}
|
||||
if params.get('image_id'):
|
||||
@@ -1100,14 +1075,8 @@ def build_top_level_options(params):
|
||||
|
||||
if params.get('key_name') is not None:
|
||||
spec['KeyName'] = params.get('key_name')
|
||||
- if params.get('user_data') is not None:
|
||||
- spec['UserData'] = to_native(params.get('user_data'))
|
||||
- elif params.get('tower_callback') is not None:
|
||||
- spec['UserData'] = tower_callback_script(
|
||||
- tower_conf=params.get('tower_callback'),
|
||||
- windows=params.get('tower_callback').get('windows', False),
|
||||
- passwd=params.get('tower_callback').get('set_password'),
|
||||
- )
|
||||
+
|
||||
+ spec.update(build_userdata(params))
|
||||
|
||||
if params.get('launch_template') is not None:
|
||||
spec['LaunchTemplate'] = {}
|
||||
@@ -1586,7 +1555,19 @@ def main():
|
||||
image_id=dict(type='str'),
|
||||
instance_type=dict(default='t2.micro', type='str'),
|
||||
user_data=dict(type='str'),
|
||||
- tower_callback=dict(type='dict'),
|
||||
+ aap_callback=dict(
|
||||
+ type='dict', aliases=['tower_callback'],
|
||||
+ required_if=[
|
||||
+ ('windows', False, ('tower_address', 'job_template_id', 'host_config_key',), False),
|
||||
+ ],
|
||||
+ options=dict(
|
||||
+ windows=dict(type='bool', default=False),
|
||||
+ set_password=dict(type='str', no_log=True),
|
||||
+ tower_address=dict(type='str'),
|
||||
+ job_template_id=dict(type='str'),
|
||||
+ host_config_key=dict(type='str', no_log=True),
|
||||
+ ),
|
||||
+ ),
|
||||
ebs_optimized=dict(type='bool'),
|
||||
vpc_subnet_id=dict(type='str', aliases=['subnet_id']),
|
||||
availability_zone=dict(type='str'),
|
||||
@@ -1620,7 +1601,7 @@ def main():
|
||||
mutually_exclusive=[
|
||||
['security_groups', 'security_group'],
|
||||
['availability_zone', 'vpc_subnet_id'],
|
||||
- ['tower_callback', 'user_data'],
|
||||
+ ['aap_callback', 'user_data'],
|
||||
['image_id', 'image'],
|
||||
],
|
||||
supports_check_mode=True
|
||||
diff --git a/test/units/module_utils/aws/test_tower.py b/test/units/module_utils/aws/test_tower.py
|
||||
new file mode 100644
|
||||
index 00000000..527423b1
|
||||
--- /dev/null
|
||||
+++ b/test/units/module_utils/aws/test_tower.py
|
||||
@@ -0,0 +1,40 @@
|
||||
+# (c) 2022 Red Hat Inc.
|
||||
+#
|
||||
+# This file is part of Ansible
|
||||
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
+
|
||||
+from __future__ import (absolute_import, division, print_function)
|
||||
+__metaclass__ = type
|
||||
+
|
||||
+# import pytest
|
||||
+
|
||||
+import ansible.module_utils.aws.tower as utils_tower
|
||||
+
|
||||
+WINDOWS_DOWNLOAD = "Invoke-Expression ((New-Object System.Net.Webclient).DownloadString(" \
|
||||
+ "'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'))"
|
||||
+EXAMPLE_PASSWORD = 'MY_EXAMPLE_PASSWORD'
|
||||
+WINDOWS_INVOKE = "$admin.PSBase.Invoke('SetPassword', 'MY_EXAMPLE_PASSWORD'"
|
||||
+
|
||||
+EXAMPLE_TOWER = "tower.example.com"
|
||||
+EXAMPLE_TEMPLATE = 'My Template'
|
||||
+EXAMPLE_KEY = '123EXAMPLE123'
|
||||
+LINUX_TRIGGER_V1 = 'https://tower.example.com/api/v1/job_templates/My%20Template/callback/'
|
||||
+LINUX_TRIGGER_V2 = 'https://tower.example.com/api/v2/job_templates/My%20Template/callback/'
|
||||
+
|
||||
+
|
||||
+def test_windows_callback_no_password():
|
||||
+ user_data = utils_tower._windows_callback_script()
|
||||
+ assert WINDOWS_DOWNLOAD in user_data
|
||||
+ assert 'SetPassword' not in user_data
|
||||
+
|
||||
+
|
||||
+def test_windows_callback_password():
|
||||
+ user_data = utils_tower._windows_callback_script(EXAMPLE_PASSWORD)
|
||||
+ assert WINDOWS_DOWNLOAD in user_data
|
||||
+ assert WINDOWS_INVOKE in user_data
|
||||
+
|
||||
+
|
||||
+def test_linux_callback_with_name():
|
||||
+ user_data = utils_tower._linux_callback_script(EXAMPLE_TOWER, EXAMPLE_TEMPLATE, EXAMPLE_KEY)
|
||||
+ assert LINUX_TRIGGER_V1 in user_data
|
||||
+ assert LINUX_TRIGGER_V2 in user_data
|
||||
--
|
||||
2.33.0
|
||||
|
||||
177
CVE-2023-5115.patch
Normal file
177
CVE-2023-5115.patch
Normal file
@ -0,0 +1,177 @@
|
||||
Description: Fix for CVE-2023-5115, based on upstream patch
|
||||
Author: Lee Garrett <debian@rocketjump.eu>
|
||||
Origin: https://github.com/ansible/ansible/pull/81787
|
||||
https://salsa.debian.org/python-team/packages/ansible/-/blob/debian/2.10.7+merged+base+2.10.17+dfsg-0+deb11u2/debian/patches/0010-fix-CVE-2023-5115.patch?ref_type=tags
|
||||
Forwarded: not-needed
|
||||
Applied-Upstream: https://github.com/ansible/ansible/pull/81787
|
||||
Reviewed-by: Lee Garrett <debian@rocketjump.eu>
|
||||
Last-Update: 2024-07-04
|
||||
---
|
||||
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
|
||||
@@ -0,0 +1,45 @@
|
||||
+#!/usr/bin/env python
|
||||
+"""Create a role archive which overwrites an arbitrary file."""
|
||||
+
|
||||
+import argparse
|
||||
+import pathlib
|
||||
+import tarfile
|
||||
+import tempfile
|
||||
+
|
||||
+
|
||||
+def main() -> None:
|
||||
+ parser = argparse.ArgumentParser(description=__doc__)
|
||||
+ parser.add_argument('archive', type=pathlib.Path, help='archive to create')
|
||||
+ parser.add_argument('content', type=pathlib.Path, help='content to write')
|
||||
+ parser.add_argument('target', type=pathlib.Path, help='file to overwrite')
|
||||
+
|
||||
+ args = parser.parse_args()
|
||||
+
|
||||
+ create_archive(args.archive, args.content, args.target)
|
||||
+
|
||||
+
|
||||
+def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, target_path: pathlib.Path) -> None:
|
||||
+ with (
|
||||
+ tarfile.open(name=archive_path, mode='w') as role_archive,
|
||||
+ tempfile.TemporaryDirectory() as temp_dir_name,
|
||||
+ ):
|
||||
+ temp_dir_path = pathlib.Path(temp_dir_name)
|
||||
+
|
||||
+ meta_main_path = temp_dir_path / 'meta' / 'main.yml'
|
||||
+ meta_main_path.parent.mkdir()
|
||||
+ meta_main_path.write_text('')
|
||||
+
|
||||
+ symlink_path = temp_dir_path / 'symlink'
|
||||
+ symlink_path.symlink_to(target_path)
|
||||
+
|
||||
+ role_archive.add(meta_main_path)
|
||||
+ role_archive.add(symlink_path)
|
||||
+
|
||||
+ content_tarinfo = role_archive.gettarinfo(content_path, str(symlink_path))
|
||||
+
|
||||
+ with content_path.open('rb') as content_file:
|
||||
+ role_archive.addfile(content_tarinfo, content_file)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ main()
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
|
||||
@@ -0,0 +1,44 @@
|
||||
+- name: create test directories
|
||||
+ file:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}'
|
||||
+ state: directory
|
||||
+ loop:
|
||||
+ - source
|
||||
+ - target
|
||||
+ - roles
|
||||
+
|
||||
+- name: create test content
|
||||
+ copy:
|
||||
+ dest: '{{ remote_tmp_dir }}/dir-traversal/source/content.txt'
|
||||
+ content: |
|
||||
+ some content to write
|
||||
+
|
||||
+- name: build dangerous dir traversal role
|
||||
+ script:
|
||||
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
|
||||
+ cmd: create-role-archive.py dangerous.tar content.txt {{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt
|
||||
+ executable: '{{ ansible_playbook_python }}'
|
||||
+
|
||||
+- name: install dangerous role
|
||||
+ command:
|
||||
+ cmd: ansible-galaxy role install --roles-path '{{ remote_tmp_dir }}/dir-traversal/roles' dangerous.tar
|
||||
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
|
||||
+ ignore_errors: true
|
||||
+ register: galaxy_install_dangerous
|
||||
+
|
||||
+- name: check for overwritten file
|
||||
+ stat:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
|
||||
+ register: dangerous_overwrite_stat
|
||||
+
|
||||
+- name: get overwritten content
|
||||
+ slurp:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
|
||||
+ register: dangerous_overwrite_content
|
||||
+ when: dangerous_overwrite_stat.stat.exists
|
||||
+
|
||||
+- assert:
|
||||
+ that:
|
||||
+ - dangerous_overwrite_content.content|default('')|b64decode == ''
|
||||
+ - not dangerous_overwrite_stat.stat.exists
|
||||
+ - galaxy_install_dangerous is failed
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
@@ -0,0 +1 @@
|
||||
+- import_tasks: dir-traversal.yml
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/cve-2023-5115.yml
|
||||
@@ -0,0 +1,3 @@
|
||||
+security_fixes:
|
||||
+- ansible-galaxy - Prevent roles from using symlinks to overwrite
|
||||
+ files outside of the installation directory (CVE-2023-5115)
|
||||
--- a/lib/ansible/galaxy/role.py
|
||||
+++ b/lib/ansible/galaxy/role.py
|
||||
@@ -329,14 +329,36 @@
|
||||
# bits that might be in the file for security purposes
|
||||
# and drop any containing directory, as mentioned above
|
||||
if member.isreg() or member.issym():
|
||||
- n_member_name = to_native(member.name)
|
||||
- n_archive_parent_dir = to_native(archive_parent_dir)
|
||||
- n_parts = n_member_name.replace(n_archive_parent_dir, "", 1).split(os.sep)
|
||||
- n_final_parts = []
|
||||
- for n_part in n_parts:
|
||||
- if n_part != '..' and '~' not in n_part and '$' not in n_part:
|
||||
+ for attr in ('name', 'linkname'):
|
||||
+ attr_value = getattr(member, attr, None)
|
||||
+ if not attr_value:
|
||||
+ continue
|
||||
+ n_attr_value = to_native(attr_value)
|
||||
+ n_archive_parent_dir = to_native(archive_parent_dir)
|
||||
+ n_parts = n_attr_value.replace(n_archive_parent_dir, "", 1).split(os.sep)
|
||||
+ n_final_parts = []
|
||||
+ for n_part in n_parts:
|
||||
+ # TODO if the condition triggers it produces a broken installation.
|
||||
+ # It will create the parent directory as an empty file and will
|
||||
+ # explode if the directory contains valid files.
|
||||
+ # Leaving this as is since the whole module needs a rewrite.
|
||||
+ #
|
||||
+ # Check if we have any files with illegal names,
|
||||
+ # and display a warning if so. This could help users
|
||||
+ # to debug a broken installation.
|
||||
+ if not n_part:
|
||||
+ continue
|
||||
+ if n_part == '..':
|
||||
+ display.warning(f"Illegal filename '{n_part}': '..' is not allowed")
|
||||
+ continue
|
||||
+ if n_part.startswith('~'):
|
||||
+ display.warning(f"Illegal filename '{n_part}': names cannot start with '~'")
|
||||
+ continue
|
||||
+ if '$' in n_part:
|
||||
+ display.warning(f"Illegal filename '{n_part}': names cannot contain '$'")
|
||||
+ continue
|
||||
n_final_parts.append(n_part)
|
||||
- member.name = os.path.join(*n_final_parts)
|
||||
+ setattr(member, attr, os.path.join(*n_final_parts))
|
||||
role_tar_file.extract(member, to_native(self.path))
|
||||
|
||||
# write out the install info file for later use
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/meta/main.yml
|
||||
@@ -0,0 +1 @@
|
||||
+dependencies: [setup_remote_tmp_dir]
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/setup_remote_tmp_dir/defaults/main.yml
|
||||
@@ -0,0 +1,2 @@
|
||||
+setup_remote_tmp_dir_skip_cleanup: no
|
||||
+setup_remote_tmp_dir_cache_path: no
|
||||
--- a/test/integration/targets/setup_remote_tmp_dir/tasks/default.yml
|
||||
+++ b/test/integration/targets/setup_remote_tmp_dir/tasks/default.yml
|
||||
@@ -9,3 +9,4 @@
|
||||
- name: record temporary directory
|
||||
set_fact:
|
||||
remote_tmp_dir: "{{ remote_tmp_dir.path }}"
|
||||
+ cacheable: "{{ setup_remote_tmp_dir_cache_path | bool }}"
|
||||
@ -2,7 +2,7 @@
|
||||
Name: ansible
|
||||
Summary: SSH-based configuration management, deployment, and task execution system
|
||||
Version: 2.9.27
|
||||
Release: 6
|
||||
Release: 7
|
||||
|
||||
License: GPLv3+
|
||||
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
|
||||
@ -21,6 +21,8 @@ Patch6: Fix-build-error-for-sphinx-7.0.patch
|
||||
Patch7: CVE-2024-0690.patch
|
||||
Patch8: CVE-2024-8775.patch
|
||||
Patch9: CVE-2024-9902.patch
|
||||
Patch10:CVE-2022-3697.patch
|
||||
Patch11:CVE-2023-5115.patch
|
||||
|
||||
Provides: ansible-python3 = %{version}-%{release}
|
||||
Obsoletes: ansible-python3 < %{version}-%{release}
|
||||
@ -218,6 +220,9 @@ make PYTHON=/usr/bin/python3 tests-py3
|
||||
%{python3_sitelib}/ansible_test
|
||||
|
||||
%changelog
|
||||
* Sat Feb 08 2025 wangkai <13474090681@163.com> - 2.9.27-7
|
||||
- Fix CVE-2022-3697 CVE-2023-5115
|
||||
|
||||
* Mon Dec 02 2024 yaoxin <yao_xin001@hoperun.com> - 2.9.27-6
|
||||
- Fix CVE-2024-8775 and CVE-2024-9902
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user