split PS wrapper and payload (CVE-2018-16859) (#49142)

* prevent scriptblock logging from logging payload contents
* added tests to verify no payload contents in PS Operational event log
* fix script action to send split-aware wrapper
* fix CLIXML error parser (return to -EncodedCommand exposed problems with it)
This commit is contained in:
Matt Davis 2018-11-26 15:28:21 -08:00 committed by GitHub
parent e7104a445b
commit 8c1f701e6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 30 deletions

View file

@ -282,6 +282,7 @@ class Connection(ConnectionBase):
# 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

View file

@ -103,6 +103,7 @@ import traceback
import json
import tempfile
import subprocess
import xml.etree.ElementTree as ET
HAVE_KERBEROS = False
try:
@ -531,14 +532,17 @@ class Connection(ConnectionBase):
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):