Merge pull request #90 from Pennyw0rth/neff-bh-pc

Set computer accounts as owned in bloodhound if local admin privs
main
Marshall Hallenbeck 2023-11-04 16:40:05 -04:00 committed by GitHub
commit 470b4e88cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 35 deletions

View File

@ -1,4 +1,3 @@
def add_user_bh(user, domain, logger, config):
"""Adds a user to the BloodHound graph database.
@ -41,29 +40,16 @@ def add_user_bh(user, domain, logger, config):
encrypted=False,
)
try:
with driver.session() as session, session.begin_transaction() as tx:
for info in users_owned:
distinguished_name = "".join([f"DC={dc}," for dc in info["domain"].split(".")]).rstrip(",")
with driver.session().begin_transaction() as tx:
for user_info in users_owned:
distinguished_name = "".join([f"DC={dc}," for dc in user_info["domain"].split(".")]).rstrip(",")
domain_query = tx.run(f"MATCH (d:Domain) WHERE d.distinguishedname STARTS WITH '{distinguished_name}' RETURN d").data()
if not domain_query:
raise Exception("Domain not found in bloodhound")
logger.debug(f"Domain {user_info['domain']} not found in BloodHound. Falling back to domainless query.")
_add_without_domain(user_info, tx, logger)
else:
domain = domain_query[0]["d"].get("name")
if info["username"][-1] == "$":
user_owned = f"{info['username'][:-1]}.{domain}"
account_type = "Computer"
else:
user_owned = f"{info['username']}@{domain}"
account_type = "User"
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) RETURN c")
if result.data()[0]["c"].get("owned") in (False, None):
logger.debug(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
logger.highlight(f"Node {user_owned} successfully set as owned in BloodHound")
_add_with_domain(user_info, domain, tx, logger)
except AuthError:
logger.fail(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")
return
@ -71,10 +57,47 @@ def add_user_bh(user, domain, logger, config):
logger.fail(f"Neo4J does not seem to be available on {uri}.")
return
except Exception as e:
if "Domain not found in bloodhound" in str(e):
logger.fail("Neo4J Error: Domain not found in BloodHound. Please specify the FQDN ex:domain.local.")
else:
logger.fail(f"Unexpected error with Neo4J: {e}")
logger.fail("Account not found on the domain")
logger.fail(f"Unexpected error with Neo4J: {e}")
return
driver.close()
def _add_with_domain(user_info, domain, tx, logger):
if user_info["username"][-1] == "$":
user_owned = f"{user_info['username'][:-1]}.{domain}"
account_type = "Computer"
else:
user_owned = f"{user_info['username']}@{domain}"
account_type = "User"
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) RETURN c").data()
if len(result) == 0:
logger.fail("Account not found in the BloodHound database.")
return
if result[0]["c"].get("owned") in (False, None):
logger.debug(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name").data()[0]
logger.highlight(f"Node {result['name']} successfully set as owned in BloodHound")
def _add_without_domain(user_info, tx, logger):
if user_info["username"][-1] == "$":
user_owned = user_info["username"][:-1]
account_type = "Computer"
else:
user_owned = user_info["username"]
account_type = "User"
result = tx.run(f"MATCH (c:{account_type}) WHERE c.name STARTS WITH '{user_owned}' RETURN c").data()
if len(result) == 0:
logger.fail("Account not found in the BloodHound database.")
return
elif len(result) >= 2:
logger.fail(f"Multiple accounts found with the name '{user_info['username']}' in the BloodHound database. Please specify the FQDN ex:domain.local")
return
elif result[0]["c"].get("owned") in (False, None):
logger.debug(f"MATCH (c:{account_type} {{name:'{result[0]['c']['name']}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{result[0]['c']['name']}'}}) SET c.owned=True RETURN c.name AS name").data()[0]
logger.highlight(f"Node {result['name']} successfully set as owned in BloodHound")

View File

@ -379,6 +379,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except SessionKeyDecryptionError:
# for PRE-AUTH account
@ -434,6 +436,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except SessionError as e:
error, desc = e.getErrorString()
@ -488,6 +492,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except ldap_impacket.LDAPSessionError as e:
if str(e).find("strongerAuthRequired") >= 0:
@ -514,6 +520,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except Exception as e:
error_code = str(e).split()[-2][:-1]
@ -578,6 +586,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except ldap_impacket.LDAPSessionError as e:
if str(e).find("strongerAuthRequired") >= 0:
@ -603,6 +613,8 @@ class ldap(connection):
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except ldap_impacket.LDAPSessionError as e:
error_code = str(e).split()[-2][:-1]

View File

@ -190,6 +190,8 @@ class mssql(connection):
self.logger.success(f"{domain}{username}{used_ccache} {self.mark_pwned()}")
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except Exception as e:
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
@ -219,6 +221,7 @@ class mssql(connection):
if self.admin_privs:
self.db.add_admin_user("plaintext", domain, username, password, self.host)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
domain = f"{domain}\\" if not self.args.local_auth else ""
out = f"{domain}{username}:{process_secret(password)} {self.mark_pwned()}"
@ -269,6 +272,7 @@ class mssql(connection):
if self.admin_privs:
self.db.add_admin_user("hash", domain, username, ntlm_hash, self.host)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
out = f"{domain}\\{username} {process_secret(ntlm_hash)} {self.mark_pwned()}"
self.logger.success(out)

View File

@ -235,6 +235,8 @@ class rdp(connection):
)
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except Exception as e:
@ -279,6 +281,8 @@ class rdp(connection):
self.logger.success(f"{domain}\\{username}:{process_secret(password)} {self.mark_pwned()}")
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except Exception as e:
if "Authentication failed!" in str(e):
@ -311,6 +315,8 @@ class rdp(connection):
self.logger.success(f"{self.domain}\\{username}:{process_secret(ntlm_hash)} {self.mark_pwned()}")
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
return True
except Exception as e:
if "Authentication failed!" in str(e):

View File

@ -409,14 +409,17 @@ class smb(connection):
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and not self.args.delegate:
add_user_bh(self.username, domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
self.create_conn_obj()
return True
@ -473,6 +476,11 @@ class smb(connection):
self.db.add_loggedin_relation(user_id, host_id)
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
self.logger.debug(f"Adding admin user: {self.domain}/{self.username}:{self.password}@{self.host}")
self.db.add_admin_user(
@ -483,12 +491,7 @@ class smb(connection):
self.host,
user_id=user_id,
)
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
@ -544,14 +547,14 @@ class smb(connection):
self.db.add_loggedin_relation(user_id, host_id)
if self.admin_privs:
self.db.add_admin_user("hash", domain, self.username, nthash, self.host, user_id=user_id)
out = f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if self.admin_privs:
self.db.add_admin_user("hash", domain, self.username, nthash, self.host, user_id=user_id)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:

View File

@ -236,6 +236,7 @@ class winrm(connection):
if self.admin_privs:
self.logger.debug("Inside admin privs")
self.db.add_admin_user("plaintext", domain, self.username, self.password, self.host) # , user_id=user_id)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
@ -287,6 +288,7 @@ class winrm(connection):
if self.admin_privs:
self.db.add_admin_user("hash", domain, self.username, nthash, self.host)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)