From a0aa53d1a1d6075a7ae98ace138712ee6cb45ae4 Mon Sep 17 00:00:00 2001 From: Martin Krizek 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 --- ...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 0000000000..70b4f35a57 --- /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 1bb6b442f7..4118f7a452 100644 --- a/lib/ansible/modules/system/user.py +++ b/lib/ansible/modules/system/user.py @@ -355,13 +355,15 @@ import grp import os import re import platform +import pty import pwd +import select import shutil import socket +import subprocess import time -import re -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 try: @@ -860,13 +862,58 @@ class User(object): 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 86595a48f4..d001cbdc48 100644 --- a/test/integration/targets/user/tasks/main.yml +++ b/test/integration/targets/user/tasks/main.yml @@ -493,3 +493,32 @@ - result.bakup - shadow_backups.files | map(attribute='path') | list | length > 0 when: ansible_os_family == 'Solaris' + + +# 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