From 4b4e2126cdb34ce47c8d817e899e830d47a2bf8c Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 10 Mar 2023 01:12:59 -0500 Subject: [PATCH] fix: catch BrokenPipeErrors when connecting via SMB --- cme/protocols/ldap.py | 4 +- cme/protocols/mssql.py | 4 ++ cme/protocols/mssql/database.py | 2 +- cme/protocols/smb.py | 12 +++++- cme/protocols/smb/database.py | 67 ++++++++++++++++++--------------- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index eeebef63..7f46405b 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -259,7 +259,9 @@ class ldap(connection): self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] try: - self.conn.login('' , '') + self.conn.login('', '') + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") except Exception as e: if "STATUS_NOT_SUPPORTED" in str(e): self.no_ntlm = True diff --git a/cme/protocols/mssql.py b/cme/protocols/mssql.py index bc0de329..71b57e31 100755 --- a/cme/protocols/mssql.py +++ b/cme/protocols/mssql.py @@ -251,6 +251,8 @@ class mssql(connection): add_user_bh(self.username, self.domain, self.logger, self.config) if not self.args.continue_on_success: return True + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") except Exception as e: self.logger.error(u'{}\\{}:{} {}'.format(domain, username, @@ -298,6 +300,8 @@ class mssql(connection): add_user_bh(self.username, self.domain, self.logger, self.config) if not self.args.continue_on_success: return True + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") except Exception as e: self.logger.error(u'{}\\{}:{} {}'.format(domain, username, diff --git a/cme/protocols/mssql/database.py b/cme/protocols/mssql/database.py index 387ffe87..7e68b1f7 100755 --- a/cme/protocols/mssql/database.py +++ b/cme/protocols/mssql/database.py @@ -362,7 +362,7 @@ class database: # if we're filtering by domain controllers elif filter_term == 'dc': q = q.filter( - self.ComputersTable.c.dc == 1 + self.ComputersTable.c.dc == True ) if domain: q = q.filter( diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 5cd958cd..b502e35b 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -264,6 +264,8 @@ class smb(connection): no_ntlm = False try: self.conn.login('', '') + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") except Exception as e: if "STATUS_NOT_SUPPORTED" in str(e): # no ntlm supported @@ -481,7 +483,10 @@ class smb(connection): self.password = password self.username = username self.domain = domain - self.conn.login(self.username, self.password, domain) + try: + self.conn.login(self.username, self.password, domain) + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") self.check_if_admin() self.db.add_credential('plaintext', domain, self.username, self.password) @@ -548,7 +553,10 @@ class smb(connection): nthash = self.hash self.domain = domain - self.conn.login(self.username, '', domain, lmhash, nthash) + try: + self.conn.login(self.username, '', domain, lmhash, nthash) + except BrokenPipeError as e: + self.logger.error(f"Broken Pipe Error while attempting to login") self.check_if_admin() self.db.add_credential('hash', domain, self.username, nthash) diff --git a/cme/protocols/smb/database.py b/cme/protocols/smb/database.py index 66e21c74..00a17164 100755 --- a/cme/protocols/smb/database.py +++ b/cme/protocols/smb/database.py @@ -17,11 +17,6 @@ from sqlalchemy.ext.compiler import compiles warnings.filterwarnings("ignore", category=SAWarning) -@compiles(insert) -def _prefix_insert_with_ignore(ins, compiler, **kw): - return compiler.visit_insert(ins.prefix_with('OR IGNORE'), **kw) - - class database: def __init__(self, db_engine): self.ComputersTable = None @@ -35,8 +30,14 @@ class database: self.db_engine = db_engine self.metadata = MetaData() asyncio.run(self.reflect_tables()) - session_factory = sessionmaker(bind=self.db_engine, expire_on_commit=True, class_=AsyncSession) - + # we don't use async_sessionmaker or async_scoped_session because when `database` is initialized, + # there is no running async loop + session_factory = sessionmaker( + bind=self.db_engine, + expire_on_commit=True, + class_=AsyncSession + ) + Session = scoped_session(session_factory) # this is still named "conn" when it is the session object; TODO: rename self.conn = Session() @@ -77,7 +78,6 @@ class database: "zerologon" boolean, "petitpotam" boolean )''') - # type = hash, plaintext db_conn.execute('''CREATE TABLE "users" ( "id" integer PRIMARY KEY, @@ -88,7 +88,6 @@ class database: "pillaged_from_computerid" integer, FOREIGN KEY(pillaged_from_computerid) REFERENCES computers(id) )''') - db_conn.execute('''CREATE TABLE "groups" ( "id" integer PRIMARY KEY, "domain" text, @@ -96,7 +95,6 @@ class database: "member_count_ad" integer, "last_query_time" text )''') - # This table keeps track of which credential has admin access over which machine and vice-versa db_conn.execute('''CREATE TABLE "admin_relations" ( "id" integer PRIMARY KEY, @@ -105,7 +103,6 @@ class database: FOREIGN KEY(userid) REFERENCES users(id), FOREIGN KEY(computerid) REFERENCES computers(id) )''') - db_conn.execute('''CREATE TABLE "loggedin_relations" ( "id" integer PRIMARY KEY, "userid" integer, @@ -113,7 +110,6 @@ class database: FOREIGN KEY(userid) REFERENCES users(id), FOREIGN KEY(computerid) REFERENCES computers(id) )''') - db_conn.execute('''CREATE TABLE "group_relations" ( "id" integer PRIMARY KEY, "userid" integer, @@ -121,7 +117,6 @@ class database: FOREIGN KEY(userid) REFERENCES users(id), FOREIGN KEY(groupid) REFERENCES groups(id) )''') - db_conn.execute('''CREATE TABLE "shares" ( "id" integer PRIMARY KEY, "computerid" text, @@ -133,7 +128,6 @@ class database: FOREIGN KEY(userid) REFERENCES users(id) UNIQUE(computerid, userid, name) )''') - db_conn.execute('''CREATE TABLE "dpapi_secrets" ( "id" integer PRIMARY KEY, "computer" text, @@ -144,15 +138,13 @@ class database: "url" text, UNIQUE(computer, dpapi_type, windows_user, username, password, url) )''') - db_conn.execute('''CREATE TABLE "dpapi_backupkey" ( "id" integer PRIMARY KEY, "domain" text, "pvk" text, UNIQUE(domain) )''') - - #db_conn.execute('''CREATE TABLE "ntds_dumps" ( + # db_conn.execute('''CREATE TABLE "ntds_dumps" ( # "id" integer PRIMARY KEY, # "computerid", integer, # "domain" text, @@ -162,7 +154,8 @@ class database: # )''') # pull/545 - def add_computer(self, ip, hostname, domain, os, smbv1, signing, spooler=None, zerologon=None, petitpotam=None, dc=None): + def add_computer(self, ip, hostname, domain, os, smbv1, signing, spooler=None, zerologon=None, petitpotam=None, + dc=None): """ Check if this host has already been added to the database, if not, add it in. TODO: return inserted or updated row ids as a list @@ -308,15 +301,16 @@ class database: q = update(self.GroupRelationsTable).values(gr_data) asyncio.run(self.conn.execute(q)) - logging.debug('add_credential(credtype={}, domain={}, username={}, password={}, groupid={}, pillaged_from={}) => {}'.format( - credtype, - domain, - username, - password, - group_id, - pillaged_from, - user_rowid - )) + logging.debug( + 'add_credential(credtype={}, domain={}, username={}, password={}, groupid={}, pillaged_from={}) => {}'.format( + credtype, + domain, + username, + password, + group_id, + pillaged_from, + user_rowid + )) return user_rowid def remove_credentials(self, creds_id): @@ -397,7 +391,7 @@ class database: elif host_ids: for host_id in host_ids: q = q.filter( - self.AdminRelationsTable.c.hostid == host_id + self.AdminRelationsTable.c.hostid == host_id ) asyncio.run(self.conn.execute(q)) @@ -465,12 +459,24 @@ class database: # if we're filtering by domain controllers elif filter_term == 'dc': q = q.filter( - self.ComputersTable.c.dc == 1 + self.ComputersTable.c.dc == True ) if domain: q = q.filter( func.lower(self.ComputersTable.c.domain) == func.lower(domain) ) + elif filter_term == 'spooler': + q = q.filter( + self.ComputersTable.c.spooler == True + ) + elif filter_term == 'zerologon': + q = q.filter( + self.ComputersTable.c.zerologon == True + ) + elif filter_term == 'petitpotam': + q = q.filter( + self.ComputersTable.c.petitpotam == True + ) # if we're filtering by ip/hostname elif filter_term and filter_term != "": like_term = func.lower(f"%{filter_term}%") @@ -589,7 +595,8 @@ class database: results = asyncio.run(self.conn.execute(q)).all() - logging.debug(f"get_groups(filterTerm={filter_term}, groupName={group_name}, groupDomain={group_domain}) => {results}") + logging.debug( + f"get_groups(filterTerm={filter_term}, groupName={group_name}, groupDomain={group_domain}) => {results}") return results def get_group_relations(self, user_id=None, group_id=None):