[stable-2.6] Wrap CLI passwords as AnsibleUnsafeText (#63352) (#63393)

* [stable-2.6] Wrap CLI passwords as AnsibleUnsafeText (#63352)

* isa string should rewrap as unsafe in get_validated_value

* _is_unsafe shouldn't be concerned with underlying types

* Start with passwords as text, instead of bytes

* Remove unused imports

* Add changelog fragment

* Update changelog with CVE.
(cherry picked from commit baeff7462d)

Co-authored-by: Matt Martz <matt@sivel.net>

* Update tests
This commit is contained in:
Matt Martz 2019-10-11 12:32:01 -05:00 committed by Toshio Kuratomi
parent d961f676c0
commit 16684f1187
8 changed files with 52 additions and 8 deletions

View file

@ -0,0 +1,6 @@
bugfixes:
- >
**security issue** - Convert CLI provided passwords to text initially, to
prevent unsafe context being lost when converting from bytes->text during
post processing of PlayContext. This prevents CLI provided passwords from
being incorrectly templated (CVE-2019-14856)

View file

@ -42,7 +42,7 @@ from ansible.parsing.dataloader import DataLoader
from ansible.release import __version__
from ansible.utils.path import unfrackpath
from ansible.utils.vars import load_extra_vars, load_options_vars
from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes
from ansible.utils.unsafe_proxy import AnsibleUnsafeText
from ansible.vars.manager import VariableManager
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
@ -329,8 +329,6 @@ class CLI(with_metaclass(ABCMeta, object)):
if op.ask_pass:
sshpass = getpass.getpass(prompt="SSH password: ")
become_prompt = "%s password[defaults to SSH password]: " % become_prompt_method
if sshpass:
sshpass = to_bytes(sshpass, errors='strict', nonstring='simplerepr')
else:
become_prompt = "%s password: " % become_prompt_method
@ -338,17 +336,15 @@ class CLI(with_metaclass(ABCMeta, object)):
becomepass = getpass.getpass(prompt=become_prompt)
if op.ask_pass and becomepass == '':
becomepass = sshpass
if becomepass:
becomepass = to_bytes(becomepass)
except EOFError:
pass
# we 'wrap' the passwords to prevent templating as
# they can contain special chars and trigger it incorrectly
if sshpass:
sshpass = AnsibleUnsafeBytes(sshpass)
sshpass = AnsibleUnsafeText(to_text(sshpass))
if becomepass:
becomepass = AnsibleUnsafeBytes(becomepass)
becomepass = AnsibleUnsafeText(to_text(becomepass))
return (sshpass, becomepass)

View file

@ -204,7 +204,7 @@ class AnsibleContext(Context):
for item in val:
if self._is_unsafe(item):
return True
elif isinstance(val, string_types) and hasattr(val, '__UNSAFE__'):
elif hasattr(val, '__UNSAFE__'):
return True
return False

View file

@ -0,0 +1,2 @@
needs/target/setup_pexpect
shippable/posix/group3

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -eux
ANSIBLE_ROLES_PATH=../ ansible-playbook setup.yml
python test-cli.py

View file

@ -0,0 +1,4 @@
- hosts: localhost
gather_facts: no
roles:
- setup_pexpect

View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
# Copyright (c) 2019 Matt Martz <matt@sivel.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import pexpect
os.environ['ANSIBLE_NOCOLOR'] = '1'
out = pexpect.run(
'ansible localhost -m debug -a msg="{{ ansible_password }}" -k',
events={
'SSH password:': '{{ 1 + 2 }}\n'
}
)
assert b'{{ 1 + 2 }}' in out

View file

@ -26,6 +26,7 @@ from ansible.module_utils.six import string_types
from ansible.playbook.attribute import FieldAttribute
from ansible.template import Templar
from ansible.playbook import base
from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText
from units.mock.loader import DictDataLoader
@ -632,3 +633,10 @@ class TestBaseSubClass(TestBase):
ds = {'test_attr_method_missing': a_string}
bsc = self._base_validate(ds)
self.assertEquals(bsc.test_attr_method_missing, a_string)
def test_get_validated_value_string_rewrap_unsafe(self):
value = AnsibleUnsafeText(u'bar')
ds = {'test_attr_string': value}
bsc = self._base_validate(ds)
self.assertIsInstance(bsc.test_attr_string, AnsibleUnsafeText)
self.assertEquals(bsc.test_attr_string, AnsibleUnsafeText(u'bar'))