Moderate severityNVD Advisory· Published Nov 29, 2018· Updated Aug 5, 2024
CVE-2018-16859
CVE-2018-16859
Description
Execution of Ansible playbooks on Windows platforms with PowerShell ScriptBlock logging and Module logging enabled can allow for 'become' passwords to appear in EventLogs in plaintext. A local user with administrator privileges on the machine can view these logs and discover the plaintext password. Ansible Engine 2.8 and older are believed to be vulnerable.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ansiblePyPI | >= 2.7.0a1, < 2.7.3 | 2.7.3 |
ansiblePyPI | < 2.5.12 | 2.5.12 |
ansiblePyPI | >= 2.6.0a1, < 2.6.9 | 2.6.9 |
Affected products
1Patches
30d746b4198absplit PS wrapper and payload (CVE-2018-16859) (#49145)
8 files changed · +57 −17
changelogs/fragments/ps_sb_logging.yaml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: +- Windows - prevent sensitive content from appearing in scriptblock logging (CVE 2018-16859)
lib/ansible/executor/module_common.py+2 −1 modified@@ -850,7 +850,8 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas # FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it module_json = json.dumps(exec_manifest) - b_module_data = exec_wrapper.replace(b"$json_raw = ''", b"$json_raw = @'\r\n%s\r\n'@" % to_bytes(module_json)) + # delimit the payload JSON from the wrapper to keep sensitive contents out of scriptblocks (which can be logged) + b_module_data = to_bytes(exec_wrapper) + b'\0\0\0\0' + to_bytes(module_json) elif module_substyle == 'jsonargs': module_args_json = to_bytes(json.dumps(module_args))
lib/ansible/executor/powershell/bootstrap_wrapper.ps1+7 −0 added@@ -0,0 +1,7 @@ +&chcp.com 65001 > $null +$exec_wrapper_str = $input | Out-String +$split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries) +If (-not $split_parts.Length -eq 2) { throw "invalid payload" } +Set-Variable -Name json_raw -Value $split_parts[1] +$exec_wrapper = [ScriptBlock]::Create($split_parts[0]) +&$exec_wrapper
lib/ansible/executor/powershell/__init__.py+0 −0 addedlib/ansible/plugins/action/script.py+7 −6 modified@@ -119,12 +119,13 @@ def run(self, tmp=None, task_vars=None): exec_data = None # WinRM requires a special wrapper to work with environment variables if self._connection.transport == "winrm": - pay = self._connection._create_raw_wrapper_payload(script_cmd, - env_dict) - exec_data = exec_wrapper.replace(b"$json_raw = ''", - b"$json_raw = @'\r\n%s\r\n'@" - % to_bytes(pay)) - script_cmd = "-" + pay = self._connection._create_raw_wrapper_payload(script_cmd, env_dict) + exec_data = to_bytes(exec_wrapper) + b"\0\0\0\0" + to_bytes(pay) + + # build the necessary exec wrapper command + # FUTURE: this still doesn't let script work on Windows with non-pipelined connections or + # full manual exec of KEEP_REMOTE_FILES + script_cmd = self._connection._shell.build_module_command(env_string='', shebang='#!powershell', cmd='') result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
lib/ansible/plugins/connection/winrm.py+9 −5 modified@@ -104,6 +104,7 @@ import json import tempfile import subprocess +import xml.etree.ElementTree as ET HAVE_KERBEROS = False try: @@ -537,14 +538,17 @@ def exec_command(self, cmd, in_data=None, sudoable=True): return (result.status_code, result.std_out, result.std_err) def is_clixml(self, value): - return value.startswith(b"#< CLIXML") + return value.startswith(b"#< CLIXML\r\n") # hacky way to get just stdout- not always sure of doc framing here, so use with care def parse_clixml_stream(self, clixml_doc, stream_name='Error'): - clear_xml = clixml_doc.replace(b'#< CLIXML\r\n', b'') - doc = xmltodict.parse(clear_xml) - lines = [l.get('#text', '').replace('_x000D__x000A_', '') for l in doc.get('Objs', {}).get('S', {}) if l.get('@S') == stream_name] - return '\r\n'.join(lines) + clixml = ET.fromstring(clixml_doc.split(b"\r\n", 1)[-1]) + namespace_match = re.match(r'{(.*)}', clixml.tag) + namespace = "{%s}" % namespace_match.group(1) if namespace_match else "" + + strings = clixml.findall("./%sS" % namespace) + lines = [e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream_name] + return to_bytes('\r\n'.join(lines)) # FUTURE: determine buffer size at runtime via remote winrm config? def _put_file_stdin_iterator(self, in_path, out_path, buffer_size=250000):
lib/ansible/plugins/shell/powershell.py+12 −4 modified@@ -44,6 +44,7 @@ import os import re import shlex +import pkgutil from ansible.errors import AnsibleError from ansible.module_utils._text import to_text @@ -78,8 +79,10 @@ # stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives # exec runspace, capture output, cleanup, return module output - # NB: do not adjust the following line- it is replaced when doing non-streamed module output - $json_raw = '' + # only init and stream in $json_raw if it wasn't set by the enclosing scope + if (-not $(Get-Variable "json_raw" -ErrorAction SilentlyContinue)) { + $json_raw = '' + } } process { $input_as_string = [string]$input @@ -1966,18 +1969,23 @@ def checksum(self, path, *args, **kwargs): return self._encode_script(script) def build_module_command(self, env_string, shebang, cmd, arg_path=None): + bootstrap_wrapper = pkgutil.get_data("ansible.executor.powershell", "bootstrap_wrapper.ps1") + # pipelining bypass if cmd == '': - return '-' + return self._encode_script(script=bootstrap_wrapper, strict_mode=False, preserve_rc=False) # non-pipelining cmd_parts = shlex.split(cmd, posix=False) cmd_parts = list(map(to_text, cmd_parts)) if shebang and shebang.lower() == '#!powershell': if not self._unquote(cmd_parts[0]).lower().endswith('.ps1'): + # we're running a module via the bootstrap wrapper cmd_parts[0] = '"%s.ps1"' % self._unquote(cmd_parts[0]) - cmd_parts.insert(0, '&') + wrapper_cmd = "type " + cmd_parts[0] + " | " + self._encode_script(script=bootstrap_wrapper, + strict_mode=False, preserve_rc=False) + return wrapper_cmd elif shebang and shebang.startswith('#!'): cmd_parts.insert(0, shebang[2:]) elif not shebang:
test/integration/targets/win_become/tasks/main.yml+18 −1 modified@@ -1,7 +1,7 @@ - set_fact: become_test_username: ansible_become_test become_test_admin_username: ansible_become_admin - gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }} + gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}" - name: create unprivileged user win_user: @@ -29,6 +29,10 @@ - SeInteractiveLogonRight - SeBatchLogonRight +- name: fetch current target date/time for log filtering + raw: '[datetime]::now | Out-String' + register: test_starttime + - name: execute tests and ensure that test user is deleted regardless of success/failure block: - name: ensure current user is not the become user @@ -219,6 +223,7 @@ assert: that: - whoami_out is successful + - become_test_username in whoami_out.stdout when: os_version.stdout_lines[0] == "async" - name: test failure with string become invalid key @@ -320,6 +325,18 @@ - nonascii_output.stdout_lines[0] == 'über den Fußgängerübergang gehen' - nonascii_output.stderr == '' + - name: get PS events containing password or module args created since test start + raw: | + $dt=[datetime]"{{ test_starttime.stdout|trim }}" + (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational | + ? { $_.TimeCreated -ge $dt -and $_.Message -match "{{ gen_pw }}|whoami" }).Count + register: ps_log_count + + - name: assert no PS events contain password or module args + assert: + that: + - ps_log_count.stdout | int == 0 + # FUTURE: test raw + script become behavior once they're running under the exec wrapper again # FUTURE: add standalone playbook tests to include password prompting and play become keywords
2f8d3fcf4110split PS wrapper and payload (CVE-2018-16859) (#49143)
9 files changed · +66 −24
changelogs/fragments/ps_sb_logging.yaml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: +- Windows - prevent sensitive content from appearing in scriptblock logging (CVE 2018-16859)
lib/ansible/executor/module_common.py+2 −1 modified@@ -912,7 +912,8 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas # FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it module_json = json.dumps(exec_manifest) - b_module_data = exec_wrapper.replace(b"$json_raw = ''", b"$json_raw = @'\r\n%s\r\n'@" % to_bytes(module_json)) + # delimit the payload JSON from the wrapper to keep sensitive contents out of scriptblocks (which can be logged) + b_module_data = to_bytes(exec_wrapper) + b'\0\0\0\0' + to_bytes(module_json) elif module_substyle == 'jsonargs': module_args_json = to_bytes(json.dumps(module_args))
lib/ansible/executor/powershell/bootstrap_wrapper.ps1+7 −0 added@@ -0,0 +1,7 @@ +&chcp.com 65001 > $null +$exec_wrapper_str = $input | Out-String +$split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries) +If (-not $split_parts.Length -eq 2) { throw "invalid payload" } +Set-Variable -Name json_raw -Value $split_parts[1] +$exec_wrapper = [ScriptBlock]::Create($split_parts[0]) +&$exec_wrapper
lib/ansible/executor/powershell/__init__.py+0 −0 addedlib/ansible/plugins/action/script.py+7 −6 modified@@ -126,12 +126,13 @@ def run(self, tmp=None, task_vars=None): exec_data = None # WinRM requires a special wrapper to work with environment variables if self._connection.transport == "winrm": - pay = self._connection._create_raw_wrapper_payload(script_cmd, - env_dict) - exec_data = exec_wrapper.replace(b"$json_raw = ''", - b"$json_raw = @'\r\n%s\r\n'@" - % to_bytes(pay)) - script_cmd = "-" + pay = self._connection._create_raw_wrapper_payload(script_cmd, env_dict) + exec_data = to_bytes(exec_wrapper) + b"\0\0\0\0" + to_bytes(pay) + + # build the necessary exec wrapper command + # FUTURE: this still doesn't let script work on Windows with non-pipelined connections or + # full manual exec of KEEP_REMOTE_FILES + script_cmd = self._connection._shell.build_module_command(env_string='', shebang='#!powershell', cmd='') result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
lib/ansible/plugins/connection/psrp.py+1 −0 modified@@ -278,6 +278,7 @@ def exec_command(self, cmd, in_data=None, sudoable=True): # starting a new interpreter to save on time b_command = base64.b64decode(cmd.split(" ")[-1]) script = to_text(b_command, 'utf-16-le') + in_data = to_text(in_data, errors="surrogate_or_strict", nonstring="passthru") display.vvv("PSRP: EXEC %s" % script, host=self._psrp_host) else: # in other cases we want to execute the cmd as the script
lib/ansible/plugins/connection/winrm.py+9 −5 modified@@ -103,6 +103,7 @@ import json import tempfile import subprocess +import xml.etree.ElementTree as ET HAVE_KERBEROS = False try: @@ -550,14 +551,17 @@ def exec_command(self, cmd, in_data=None, sudoable=True): return (result.status_code, result.std_out, result.std_err) def is_clixml(self, value): - return value.startswith(b"#< CLIXML") + return value.startswith(b"#< CLIXML\r\n") # hacky way to get just stdout- not always sure of doc framing here, so use with care def parse_clixml_stream(self, clixml_doc, stream_name='Error'): - clear_xml = clixml_doc.replace(b'#< CLIXML\r\n', b'') - doc = xmltodict.parse(clear_xml) - lines = [l.get('#text', '').replace('_x000D__x000A_', '') for l in doc.get('Objs', {}).get('S', {}) if l.get('@S') == stream_name] - return '\r\n'.join(lines) + clixml = ET.fromstring(clixml_doc.split(b"\r\n", 1)[-1]) + namespace_match = re.match(r'{(.*)}', clixml.tag) + namespace = "{%s}" % namespace_match.group(1) if namespace_match else "" + + strings = clixml.findall("./%sS" % namespace) + lines = [e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream_name] + return to_bytes('\r\n'.join(lines)) # FUTURE: determine buffer size at runtime via remote winrm config? def _put_file_stdin_iterator(self, in_path, out_path, buffer_size=250000):
lib/ansible/plugins/shell/powershell.py+20 −11 modified@@ -44,6 +44,7 @@ import os import re import shlex +import pkgutil from ansible.errors import AnsibleError from ansible.module_utils._text import to_text @@ -78,8 +79,10 @@ # stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives # exec runspace, capture output, cleanup, return module output - # NB: do not adjust the following line- it is replaced when doing non-streamed module output - $json_raw = '' + # only init and stream in $json_raw if it wasn't set by the enclosing scope + if (-not $(Get-Variable "json_raw" -ErrorAction SilentlyContinue)) { + $json_raw = '' + } } process { $input_as_string = [string]$input @@ -929,9 +932,11 @@ class NativeWaitHandle : WaitHandle $become_exec_wrapper = { chcp.com 65001 > $null $ProgressPreference = "SilentlyContinue" - $exec_wrapper_str = [System.Console]::In.ReadToEnd() - $exec_wrapper = [ScriptBlock]::Create($exec_wrapper_str) - &$exec_wrapper + $raw = [System.Console]::In.ReadToEnd() + $split_parts = $raw.Split(@("`0`0`0`0"), 0) + If (-not $split_parts.Length -eq 2) { throw "invalid payload" } + $json_raw = $split_parts[1] + &([ScriptBlock]::Create($split_parts[0])) } $exec_wrapper = { @@ -955,10 +960,9 @@ class NativeWaitHandle : WaitHandle # stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives # exec runspace, capture output, cleanup, return module output. Do not change this as it is set become before being passed to the # become process. - $json_raw = "" - If (-not $json_raw) { - Write-Error "no input given" -Category InvalidArgument + if (-not $(Get-Variable "json_raw" -ErrorAction SilentlyContinue)) { + Write-Error "no payload supplied" -Category InvalidArgument } $payload = ConvertTo-HashtableFromPsCustomObject -myPsObject (ConvertFrom-Json $json_raw) @@ -1095,7 +1099,7 @@ class NativeWaitHandle : WaitHandle # wrapper which calls our read wrapper passed through stdin. Cannot use 'powershell -' as # the $ErrorActionPreference is always set to Stop and cannot be changed $payload_string = $payload | ConvertTo-Json -Depth 99 -Compress - $exec_wrapper = $exec_wrapper.ToString().Replace('$json_raw = ""', "`$json_raw = '$payload_string'") + $exec_wrapper = $exec_wrapper.ToString() + "`0`0`0`0" + $payload_string $rc = 0 $exec_command = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($become_exec_wrapper.ToString())) @@ -1584,18 +1588,23 @@ def checksum(self, path, *args, **kwargs): return self._encode_script(script) def build_module_command(self, env_string, shebang, cmd, arg_path=None): + bootstrap_wrapper = pkgutil.get_data("ansible.executor.powershell", "bootstrap_wrapper.ps1") + # pipelining bypass if cmd == '': - return '-' + return self._encode_script(script=bootstrap_wrapper, strict_mode=False, preserve_rc=False) # non-pipelining cmd_parts = shlex.split(cmd, posix=False) cmd_parts = list(map(to_text, cmd_parts)) if shebang and shebang.lower() == '#!powershell': if not self._unquote(cmd_parts[0]).lower().endswith('.ps1'): + # we're running a module via the bootstrap wrapper cmd_parts[0] = '"%s.ps1"' % self._unquote(cmd_parts[0]) - cmd_parts.insert(0, '&') + wrapper_cmd = "type " + cmd_parts[0] + " | " + self._encode_script(script=bootstrap_wrapper, + strict_mode=False, preserve_rc=False) + return wrapper_cmd elif shebang and shebang.startswith('#!'): cmd_parts.insert(0, shebang[2:]) elif not shebang:
test/integration/targets/win_become/tasks/main.yml+18 −1 modified@@ -1,7 +1,7 @@ - set_fact: become_test_username: ansible_become_test become_test_admin_username: ansible_become_admin - gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }} + gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}" - name: create unprivileged user win_user: @@ -29,6 +29,10 @@ - SeInteractiveLogonRight - SeBatchLogonRight +- name: fetch current target date/time for log filtering + raw: '[datetime]::now | Out-String' + register: test_starttime + - name: execute tests and ensure that test user is deleted regardless of success/failure block: - name: ensure current user is not the become user @@ -199,6 +203,7 @@ assert: that: - whoami_out is successful + - become_test_username in whoami_out.stdout - name: test failure with string become invalid key vars: *become_vars @@ -312,6 +317,18 @@ - nonascii_output.stdout_lines[0] == 'über den Fußgängerübergang gehen' - nonascii_output.stderr == '' + - name: get PS events containing password or module args created since test start + raw: | + $dt=[datetime]"{{ test_starttime.stdout|trim }}" + (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational | + ? { $_.TimeCreated -ge $dt -and $_.Message -match "{{ gen_pw }}|whoami" }).Count + register: ps_log_count + + - name: assert no PS events contain password or module args + assert: + that: + - ps_log_count.stdout | int == 0 + # FUTURE: test raw + script become behavior once they're running under the exec wrapper again # FUTURE: add standalone playbook tests to include password prompting and play become keywords
4d748d34f939split PS wrapper and payload (CVE-2018-16859)
8 files changed · +57 −17
changelogs/fragments/ps_sb_logging.yaml+2 −0 added@@ -0,0 +1,2 @@ +bugfixes: +- Windows - prevent sensitive content from appearing in scriptblock logging (CVE 2018-16859)
lib/ansible/executor/module_common.py+2 −1 modified@@ -850,7 +850,8 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas # FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it module_json = json.dumps(exec_manifest) - b_module_data = exec_wrapper.replace(b"$json_raw = ''", b"$json_raw = @'\r\n%s\r\n'@" % to_bytes(module_json)) + # delimit the payload JSON from the wrapper to keep sensitive contents out of scriptblocks (which can be logged) + b_module_data = to_bytes(exec_wrapper) + b'\0\0\0\0' + to_bytes(module_json) elif module_substyle == 'jsonargs': module_args_json = to_bytes(json.dumps(module_args))
lib/ansible/executor/powershell/bootstrap_wrapper.ps1+7 −0 added@@ -0,0 +1,7 @@ +&chcp.com 65001 > $null +$exec_wrapper_str = $input | Out-String +$split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries) +If (-not $split_parts.Length -eq 2) { throw "invalid payload" } +Set-Variable -Name json_raw -Value $split_parts[1] +$exec_wrapper = [ScriptBlock]::Create($split_parts[0]) +&$exec_wrapper
lib/ansible/executor/powershell/__init__.py+0 −0 addedlib/ansible/plugins/action/script.py+7 −6 modified@@ -126,12 +126,13 @@ def run(self, tmp=None, task_vars=None): exec_data = None # WinRM requires a special wrapper to work with environment variables if self._connection.transport == "winrm": - pay = self._connection._create_raw_wrapper_payload(script_cmd, - env_dict) - exec_data = exec_wrapper.replace(b"$json_raw = ''", - b"$json_raw = @'\r\n%s\r\n'@" - % to_bytes(pay)) - script_cmd = "-" + pay = self._connection._create_raw_wrapper_payload(script_cmd, env_dict) + exec_data = to_bytes(exec_wrapper) + b"\0\0\0\0" + to_bytes(pay) + + # build the necessary exec wrapper command + # FUTURE: this still doesn't let script work on Windows with non-pipelined connections or + # full manual exec of KEEP_REMOTE_FILES + script_cmd = self._connection._shell.build_module_command(env_string='', shebang='#!powershell', cmd='') result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
lib/ansible/plugins/connection/winrm.py+9 −5 modified@@ -103,6 +103,7 @@ import json import tempfile import subprocess +import xml.etree.ElementTree as ET HAVE_KERBEROS = False try: @@ -550,14 +551,17 @@ def exec_command(self, cmd, in_data=None, sudoable=True): return (result.status_code, result.std_out, result.std_err) def is_clixml(self, value): - return value.startswith(b"#< CLIXML") + return value.startswith(b"#< CLIXML\r\n") # hacky way to get just stdout- not always sure of doc framing here, so use with care def parse_clixml_stream(self, clixml_doc, stream_name='Error'): - clear_xml = clixml_doc.replace(b'#< CLIXML\r\n', b'') - doc = xmltodict.parse(clear_xml) - lines = [l.get('#text', '').replace('_x000D__x000A_', '') for l in doc.get('Objs', {}).get('S', {}) if l.get('@S') == stream_name] - return '\r\n'.join(lines) + clixml = ET.fromstring(clixml_doc.split(b"\r\n", 1)[-1]) + namespace_match = re.match(r'{(.*)}', clixml.tag) + namespace = "{%s}" % namespace_match.group(1) if namespace_match else "" + + strings = clixml.findall("./%sS" % namespace) + lines = [e.text.replace('_x000D__x000A_', '') for e in strings if e.attrib.get('S') == stream_name] + return to_bytes('\r\n'.join(lines)) # FUTURE: determine buffer size at runtime via remote winrm config? def _put_file_stdin_iterator(self, in_path, out_path, buffer_size=250000):
lib/ansible/plugins/shell/powershell.py+12 −4 modified@@ -44,6 +44,7 @@ import os import re import shlex +import pkgutil from ansible.errors import AnsibleError from ansible.module_utils._text import to_native, to_text @@ -78,8 +79,10 @@ # stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives # exec runspace, capture output, cleanup, return module output - # NB: do not adjust the following line- it is replaced when doing non-streamed module output - $json_raw = '' + # only init and stream in $json_raw if it wasn't set by the enclosing scope + if (-not $(Get-Variable "json_raw" -ErrorAction SilentlyContinue)) { + $json_raw = '' + } } process { $input_as_string = [string]$input @@ -2022,18 +2025,23 @@ def checksum(self, path, *args, **kwargs): return self._encode_script(script) def build_module_command(self, env_string, shebang, cmd, arg_path=None): + bootstrap_wrapper = pkgutil.get_data("ansible.executor.powershell", "bootstrap_wrapper.ps1") + # pipelining bypass if cmd == '': - return '-' + return self._encode_script(script=bootstrap_wrapper, strict_mode=False, preserve_rc=False) # non-pipelining cmd_parts = shlex.split(to_native(cmd), posix=False) cmd_parts = list(map(to_text, cmd_parts)) if shebang and shebang.lower() == '#!powershell': if not self._unquote(cmd_parts[0]).lower().endswith('.ps1'): + # we're running a module via the bootstrap wrapper cmd_parts[0] = '"%s.ps1"' % self._unquote(cmd_parts[0]) - cmd_parts.insert(0, '&') + wrapper_cmd = "type " + cmd_parts[0] + " | " + self._encode_script(script=bootstrap_wrapper, + strict_mode=False, preserve_rc=False) + return wrapper_cmd elif shebang and shebang.startswith('#!'): cmd_parts.insert(0, shebang[2:]) elif not shebang:
test/integration/targets/win_become/tasks/main.yml+18 −1 modified@@ -1,7 +1,7 @@ - set_fact: become_test_username: ansible_become_test become_test_admin_username: ansible_become_admin - gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }} + gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}" - name: create unprivileged user win_user: @@ -29,6 +29,10 @@ - SeInteractiveLogonRight - SeBatchLogonRight +- name: fetch current target date/time for log filtering + raw: '[datetime]::now | Out-String' + register: test_starttime + - name: execute tests and ensure that test user is deleted regardless of success/failure block: - name: ensure current user is not the become user @@ -219,6 +223,7 @@ assert: that: - whoami_out is successful + - become_test_username in whoami_out.stdout when: os_version.stdout_lines[0] == "async" - name: test failure with string become invalid key @@ -320,6 +325,18 @@ - nonascii_output.stdout_lines[0] == 'über den Fußgängerübergang gehen' - nonascii_output.stderr == '' + - name: get PS events containing password or module args created since test start + raw: | + $dt=[datetime]"{{ test_starttime.stdout|trim }}" + (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational | + ? { $_.TimeCreated -ge $dt -and $_.Message -match "{{ gen_pw }}|whoami" }).Count + register: ps_log_count + + - name: assert no PS events contain password or module args + assert: + that: + - ps_log_count.stdout | int == 0 + # FUTURE: test raw + script become behavior once they're running under the exec wrapper again # FUTURE: add standalone playbook tests to include password prompting and play become keywords
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
18- lists.opensuse.org/opensuse-security-announce/2019-04/msg00021.htmlghsavendor-advisoryx_refsource_SUSEWEB
- lists.opensuse.org/opensuse-security-announce/2019-06/msg00077.htmlghsavendor-advisoryx_refsource_SUSEWEB
- lists.opensuse.org/opensuse-security-announce/2019-08/msg00020.htmlghsavendor-advisoryx_refsource_SUSEWEB
- access.redhat.com/errata/RHSA-2018:3770ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2018:3771ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2018:3772ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2018:3773ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-v735-2pp6-h86rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-16859ghsaADVISORY
- www.securityfocus.com/bid/106004mitrevdb-entryx_refsource_BID
- bugzilla.redhat.com/show_bug.cgighsax_refsource_CONFIRMWEB
- github.com/ansible/ansible/blob/v2.5.13/changelogs/CHANGELOG-v2.5.rstghsaWEB
- github.com/ansible/ansible/commit/0d746b4198abf84290a093b83cf02b4203d73d9fghsaWEB
- github.com/ansible/ansible/commit/2f8d3fcf41107efafc14d51ab6e14531ca8f8c87ghsaWEB
- github.com/ansible/ansible/commit/4d748d34f9392aa469da00a85c8e2d5fe6cec52bghsaWEB
- github.com/ansible/ansible/pull/49142ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/ansible/PYSEC-2018-60.yamlghsaWEB
- web.archive.org/web/20200227102121/http://www.securityfocus.com/bid/106004ghsaWEB
News mentions
0No linked articles in our index yet.