From 3465a13824f68b97976b13b00429d83344f93b48 Mon Sep 17 00:00:00 2001 From: Martin Krizek <martin.krizek@gmail.com> Date: Mon, 22 Oct 2018 17:42:59 +0200 Subject: [PATCH] user: do not pass ssh_key_passphrase on cmdline CVE-2018-16837 Co-authored-by: Toshio Kuratomi <a.badger@gmail.com> Modified by Bruno Cornec for Mageia --- ...ot-pass-ssh_key_passphrase-on-cmdline.yaml | 2 + lib/ansible/modules/system/user.py | 57 +++++++++++++++++-- test/integration/targets/user/tasks/main.yml | 29 ++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/user-do-not-pass-ssh_key_passphrase-on-cmdline.yaml diff --git a/changelogs/fragments/user-do-not-pass-ssh_key_passphrase-on-cmdline.yaml b/changelogs/fragments/user-do-not-pass-ssh_key_passphrase-on-cmdline.yaml new file mode 100644 index 0000000000000..70b4f35a57641 --- /dev/null +++ b/changelogs/fragments/user-do-not-pass-ssh_key_passphrase-on-cmdline.yaml @@ -0,0 +1,2 @@ +bugfixes: + - user: do not pass ssh_key_passphrase on cmdline (CVE-2018-16837) diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py index 1bb6b442f71c8..4118f7a452761 100644 --- a/lib/ansible/modules/system/user.py +++ b/lib/ansible/modules/system/user.py @@ -248,10 +248,13 @@ import pwd import grp import platform +import pty +import select +import subprocess import socket import time import shutil -from ansible.module_utils._text import to_native +from ansible.module_utils._text import to_native, to_bytes, to_text from ansible.module_utils.basic import load_platform_subclass, AnsibleModule from ansible.module_utils.pycompat24 import get_exception @@ -860,13 +862,58 @@ def ssh_key_gen(self): cmd.append(self.ssh_comment) cmd.append('-f') cmd.append(ssh_key_file) - cmd.append('-N') if self.ssh_passphrase is not None: - cmd.append(self.ssh_passphrase) + if self.module.check_mode: + self.module.debug('In check mode, would have run: "%s"' % cmd) + return (0, '', '') + + master_in_fd, slave_in_fd = pty.openpty() + master_out_fd, slave_out_fd = pty.openpty() + master_err_fd, slave_err_fd = pty.openpty() + env = os.environ.copy() + env['LC_ALL'] = 'C' + try: + p = subprocess.Popen([to_bytes(c) for c in cmd], + stdin=slave_in_fd, + stdout=slave_out_fd, + stderr=slave_err_fd, + preexec_fn=os.setsid, + env=env) + out_buffer = b'' + err_buffer = b'' + while p.poll() is None: + r, w, e = select.select([master_out_fd, master_err_fd], [], [], 1) + first_prompt = b'Enter passphrase (empty for no passphrase):' + second_prompt = b'Enter same passphrase again' + prompt = first_prompt + for fd in r: + if fd == master_out_fd: + chunk = os.read(master_out_fd, 10240) + out_buffer += chunk + if prompt in out_buffer: + os.write(master_in_fd, self.ssh_passphrase + b'\r') + prompt = second_prompt + else: + chunk = os.read(master_err_fd, 10240) + err_buffer += chunk + if prompt in err_buffer: + os.write(master_in_fd, self.ssh_passphrase + b'\r') + prompt = second_prompt + if b'Overwrite (y/n)?' in out_buffer or b'Overwrite (y/n)?' in err_buffer: + # This created between us checking for existence and now + return (None, 'Key already exists', '') + + rc = p.returncode + out = to_native(out_buffer) + err = to_native(err_buffer) + except OSError as e: + return (1, '', to_native(e)) else: + cmd.append('-N') cmd.append('') - (rc, out, err) = self.execute_command(cmd) + (rc, out, err) = self.execute_command(cmd) + if rc == 0 and not self.module.check_mode: # If the keys were successfully created, we should be able # to tweak ownership. diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml index 86595a48f47ff..d001cbdc48434 100644 --- a/test/integration/targets/user/tasks/main.yml +++ b/test/integration/targets/user/tasks/main.yml @@ -108,3 +108,32 @@ assert: that: - '"ansibulluser" not in user_names2.stdout_lines' + + +# Test creating ssh key with passphrase +- name: Remove ansibulluser + user: + name: ansibulluser + state: absent + +- name: Create user with ssh key + user: + name: ansibulluser + state: present + generate_ssh_key: yes + ssh_key_file: "{{ output_dir }}/test_id_rsa" + ssh_key_passphrase: secret_passphrase + +- name: Unlock ssh key + command: "ssh-keygen -y -f {{ output_dir }}/test_id_rsa -P secret_passphrase" + register: result + +- name: Check that ssh key was unlocked successfully + assert: + that: + - result.rc == 0 + +- name: Clean ssh key + file: + path: "{{ output_dir }}/test_id_rsa" + state: absent