diff --git a/nxc/helpers/misc.py b/nxc/helpers/misc.py index dd9dbe8705..2aa98d138f 100755 --- a/nxc/helpers/misc.py +++ b/nxc/helpers/misc.py @@ -26,6 +26,20 @@ def gen_random_string(length=10): return "".join(random.sample(string.ascii_letters, int(length))) +_HOSTNAME_SANITIZE_RE = re.compile(r"[^\w\-.]") + + +def sanitize_hostname(hostname, logger): + """Strip characters from a server-provided hostname that could cause path + traversal, newline injection, or format-string issues when used in file + paths or output content. Logs a warning when the value is modified. + """ + sanitized = _HOSTNAME_SANITIZE_RE.sub("_", hostname) + if sanitized != hostname: + logger.fail(f"Hostname contained invalid characters (received: {hostname!r}), sanitized to: {sanitized!r}") + return sanitized + + def validate_ntlm(data): allowed = re.compile(r"^[0-9a-f]{32}", re.IGNORECASE) return bool(allowed.match(data)) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index b7edc248ec..8127bf3d7b 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -6,7 +6,7 @@ from nxc.config import process_secret, host_info_colors from nxc.connection import connection from nxc.connection import requires_admin -from nxc.helpers.misc import gen_random_string +from nxc.helpers.misc import gen_random_string, sanitize_hostname from nxc.logger import NXCAdapter from nxc.helpers.bloodhound import add_user_bh from nxc.helpers.negotiate_parser import parse_challenge, login7_integrated_auth_error_message @@ -138,7 +138,7 @@ def enum_host_info(self): if challenge.startswith(b"NTLMSSP\x00"): ntlm_info = parse_challenge(challenge) self.targetDomain = self.domain = ntlm_info["domain"] - self.hostname = ntlm_info["hostname"] + self.hostname = sanitize_hostname(ntlm_info["hostname"], self.logger) self.server_os = ntlm_info["os_version"] self.logger.extra["hostname"] = self.hostname else: diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index fdbdd923c4..a84dda304e 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -8,6 +8,7 @@ from impacket.krb5.ccache import CCache from nxc.connection import connection +from nxc.helpers.misc import sanitize_hostname from nxc.helpers.bloodhound import add_user_bh from nxc.logger import NXCAdapter from nxc.config import host_info_colors, process_secret @@ -142,7 +143,7 @@ def create_conn_obj(self): pass else: self.domain = info_domain["dnsdomainname"] - self.hostname = info_domain["computername"] + self.hostname = sanitize_hostname(info_domain["computername"], self.logger) self.server_os = info_domain["os_guess"] + " Build " + str(info_domain["os_build"]) self.logger.extra["hostname"] = self.hostname break diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 6c5618464e..431ad051f8 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -6,6 +6,7 @@ import ipaddress from Cryptodome.Hash import MD4 from textwrap import dedent +from nxc.helpers.misc import sanitize_hostname from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT @@ -200,6 +201,7 @@ def enum_host_info(self): self.hostname = dns_hostname else: self.hostname = self.conn.getServerName() + self.hostname = sanitize_hostname(self.hostname, self.logger) self.targetDomain = self.conn.getServerDNSDomainName() if not self.targetDomain: # Not sure if that can even happen but now we are safe self.targetDomain = self.hostname diff --git a/nxc/protocols/winrm.py b/nxc/protocols/winrm.py index 50bd0010fa..3f758b8c9f 100644 --- a/nxc/protocols/winrm.py +++ b/nxc/protocols/winrm.py @@ -8,6 +8,7 @@ import xml.etree.ElementTree as ET from pypsrp.wsman import NAMESPACES +from nxc.helpers.misc import sanitize_hostname from pypsrp.client import Client from pypsrp.powershell import PSDataStreams from termcolor import colored @@ -68,7 +69,7 @@ def enum_host_info(self): return False self.targetDomain = self.domain = ntlm_info["domain"] - self.hostname = ntlm_info["hostname"] + self.hostname = sanitize_hostname(ntlm_info["hostname"], self.logger) self.server_os = ntlm_info["os_version"] self.logger.extra["hostname"] = self.hostname diff --git a/nxc/protocols/wmi.py b/nxc/protocols/wmi.py index c5e5af0dea..845adc0702 100644 --- a/nxc/protocols/wmi.py +++ b/nxc/protocols/wmi.py @@ -2,6 +2,7 @@ from io import StringIO from nxc.helpers.negotiate_parser import parse_challenge +from nxc.helpers.misc import sanitize_hostname from nxc.config import process_secret from nxc.connection import connection, dcom_FirewallChecker, requires_admin from nxc.logger import NXCAdapter @@ -130,7 +131,7 @@ def enum_host_info(self): bindResp = MSRPCBindAck(response.getData()) ntlm_info = parse_challenge(bindResp["auth_data"]) self.targetDomain = self.domain = ntlm_info["domain"] - self.hostname = ntlm_info["hostname"] + self.hostname = sanitize_hostname(ntlm_info["hostname"], self.logger) self.server_os = ntlm_info["os_version"] self.logger.extra["hostname"] = self.hostname else: