From 29e239469ee36f063abbbdffc0327335ea891c54 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:20:13 -0500 Subject: [PATCH 1/8] add initial poc --- nxc/protocols/mssql.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index a7dac3b1..3d4a2fd8 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -15,6 +15,7 @@ from nxc.protocols.mssql.mssqlexec import MSSQLEXEC from impacket import tds, ntlm from impacket.krb5.ccache import CCache +from impacket.dcerpc.v5.dtypes import SID from impacket.tds import ( SQLErrorException, TDS_LOGINACK_TOKEN, @@ -416,3 +417,30 @@ class mssql(connection): else: _type = f"{key['Type']:d}" return f"(ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')})" + + def rid_brute(self, max_rid=None): + entries = [] + if not max_rid: + max_rid = int(self.args.rid_brute) + + + + domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] + domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] + for rid in range(500, max_rid + 1): + query = f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{rid:d}'))" + user = self.conn.sql_query(query)[0][""] + if user == "NULL": + continue + sid_type = "SID TYPE?" + self.logger.highlight(f"{rid}: {user} ({sid_type})") + entries.append( + { + "rid": rid, + "domain": domain, + "username": user.split("\\")[1], + #"sidtype": sid_type, #?? + } + ) + return entries \ No newline at end of file From 9b5317c234ad80dad9c127869ad97cd7fd25a379 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:20:24 -0500 Subject: [PATCH 2/8] add rid-brute argument for mssql --- nxc/protocols/mssql/proto_args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/protocols/mssql/proto_args.py b/nxc/protocols/mssql/proto_args.py index 1bb5363f..b810ccea 100644 --- a/nxc/protocols/mssql/proto_args.py +++ b/nxc/protocols/mssql/proto_args.py @@ -29,4 +29,6 @@ def proto_args(parser, parents): tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\\\Windows\\\\Temp\\\\whoami.txt") tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt") + mapping_enum_group = mssql_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") + mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") return parser \ No newline at end of file From 3b58928ab281969520565e01201214c675b10690 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:28:57 -0500 Subject: [PATCH 3/8] add batch query --- nxc/protocols/mssql.py | 54 ++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 3d4a2fd8..631d8942 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -428,19 +428,43 @@ class mssql(connection): domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] - for rid in range(500, max_rid + 1): - query = f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{rid:d}'))" - user = self.conn.sql_query(query)[0][""] - if user == "NULL": - continue - sid_type = "SID TYPE?" - self.logger.highlight(f"{rid}: {user} ({sid_type})") - entries.append( - { - "rid": rid, - "domain": domain, - "username": user.split("\\")[1], - #"sidtype": sid_type, #?? - } - ) + + so_far = 0 + simultaneous = 1000 + for _j in range(max_rid // simultaneous + 1): + sids_to_check = (max_rid - so_far) % simultaneous if (max_rid - so_far) // simultaneous == 0 else simultaneous + if sids_to_check == 0: + break + sid_queries = [f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{i:d}'))" for i in range(so_far, so_far + sids_to_check)] + + raw_output = self.conn.sql_query(";".join(sid_queries)) + + for n, item in enumerate(raw_output): + username = item[""] + if username == "NULL": + continue + rid = so_far + n + sid_type = "SID TYPE ??" + self.logger.highlight(f"{rid}: {username} ({sid_type})") + entries.append( + { + "rid": rid, + "domain": domain, + "username": username.split("\\")[1], + } + ) + + so_far += simultaneous + # if user == "NULL": + # continue + # sid_type = "SID TYPE?" + # + # entries.append( + # { + # "rid": rid, + # "domain": domain, + # "username": user.split("\\")[1], + # #"sidtype": sid_type, #?? + # } + # ) return entries \ No newline at end of file From 6534a4592b722019d4c5e20eec12018d1d3abca9 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:34:57 -0500 Subject: [PATCH 4/8] remove sid type. unsure if there is any way to query this --- nxc/protocols/mssql.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 631d8942..efe3cdf5 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -444,8 +444,7 @@ class mssql(connection): if username == "NULL": continue rid = so_far + n - sid_type = "SID TYPE ??" - self.logger.highlight(f"{rid}: {username} ({sid_type})") + self.logger.highlight(f"{rid}: {username}") entries.append( { "rid": rid, @@ -455,16 +454,4 @@ class mssql(connection): ) so_far += simultaneous - # if user == "NULL": - # continue - # sid_type = "SID TYPE?" - # - # entries.append( - # { - # "rid": rid, - # "domain": domain, - # "username": user.split("\\")[1], - # #"sidtype": sid_type, #?? - # } - # ) return entries \ No newline at end of file From 07554152db7d3317f51fb8e2e378c3fd784e9a7c Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:47:45 -0500 Subject: [PATCH 5/8] add e2e test --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 321aa9bb..717c3c45 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -213,6 +213,7 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-p ##### MSSQL netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex +netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute ##### MSSQL PowerShell netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 From b76c84876700e78104e1d3644d56e1b015ca6da1 Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:52:05 -0500 Subject: [PATCH 6/8] comment code --- nxc/protocols/mssql.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index efe3cdf5..9c95f132 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -423,9 +423,10 @@ class mssql(connection): if not max_rid: max_rid = int(self.args.rid_brute) - - + # Query domain domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + + # Query known group to determine raw SID & convert to canon raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] @@ -435,8 +436,9 @@ class mssql(connection): sids_to_check = (max_rid - so_far) % simultaneous if (max_rid - so_far) // simultaneous == 0 else simultaneous if sids_to_check == 0: break + + # Batch query multiple sids at a time sid_queries = [f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{i:d}'))" for i in range(so_far, so_far + sids_to_check)] - raw_output = self.conn.sql_query(";".join(sid_queries)) for n, item in enumerate(raw_output): From 977a3d60a3d1a7a17849416ae586ed66484c260a Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 15:54:48 -0500 Subject: [PATCH 7/8] add error checking for when not on a domain-joined machine --- nxc/protocols/mssql.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 9c95f132..7160182e 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -423,12 +423,16 @@ class mssql(connection): if not max_rid: max_rid = int(self.args.rid_brute) - # Query domain - domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + try: + # Query domain + domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + + # Query known group to determine raw SID & convert to canon + raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] + domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] + except Exception as e: + self.logger.fail(f"Error parsing SID. Not domain joined?: {e}") - # Query known group to determine raw SID & convert to canon - raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] - domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] so_far = 0 simultaneous = 1000 From b4b67141251b1a12f0ea6bcd98de8a8a2c21a0ad Mon Sep 17 00:00:00 2001 From: Adamkadaban Date: Sat, 23 Nov 2024 21:28:53 -0500 Subject: [PATCH 8/8] remove extra newline for better formatting --- nxc/protocols/mssql.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 7160182e..655484a7 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -433,7 +433,6 @@ class mssql(connection): except Exception as e: self.logger.fail(f"Error parsing SID. Not domain joined?: {e}") - so_far = 0 simultaneous = 1000 for _j in range(max_rid // simultaneous + 1): @@ -460,4 +459,4 @@ class mssql(connection): ) so_far += simultaneous - return entries \ No newline at end of file + return entries