Last active
April 15, 2019 13:55
-
-
Save chrismaddalena/e277425272ccec4bb50a2edc2a5a11d2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def compare_dumps(first_hashdump, second_hashdump): | |
"""Compare the two password dumps and return a dictionary of the results. JSON output: | |
{ | |
"accounts": { | |
"CHRISM": { | |
"enabled": true, | |
"pwdlastset": "2019-04-14 22:53:08", | |
"domain": "DOMAIN.COM" | |
}, | |
"matching_accounts": {} | |
"CHRISM_DA": { | |
"enabled": true, | |
"pwdlastset": "2019-04-13 13:37:12", | |
"domain": "SECURE.DOMAIN.COM" | |
} | |
}, | |
"hash": { | |
"value": "32-CHAR NTLM HASH VALUE", | |
"sanitized_value": "a123************************b456", | |
"matched_value": "32-CHAR NTLM HASH VALUE", | |
"sanitized_matched_value": "a123************************b456", | |
"cracked": false | |
} | |
}, | |
... | |
}, | |
"results": { | |
"total": TOTAL MATCHES WITH ENABLED ACCOUNTS IN SECOND DUMP, | |
"disabled_matches": TOTAL MATCHES WITH DISABLED ACCOUNTS IN SECOND DUMP, | |
"unique_accounts": TOTAL UNIQUE MATCHES FROM FIRST DUMP | |
} | |
""" | |
# Setup dictionaries and counters for matches | |
shared_passwords = {} | |
shared_passwords['results'] = {} | |
shared_passwords['accounts'] = {} | |
shared_password_count = 0 | |
disabled_shared_password_count = 0 | |
# Connect to the Neo4j database | |
neo4j_driver = setup_database_conn() | |
# Process the hash dumps and potfile | |
first_hashes = process_hashes(first_hashdump) | |
second_hashes = process_hashes(second_hashdump) | |
potfile_hashes = process_potfile('hashcat.potfile') | |
# Create a progress bar because this can take a while | |
with click.progressbar(length=len(first_hashes), label='Sniffing through hashes') as bar: | |
# Loop over every username and hash in the first list | |
for first_username, first_ntlm_hash in first_hashes.items(): | |
# Proceed only if the NTLM hash appears as a value in the second dump | |
if first_ntlm_hash in second_hashes.values(): | |
# Loop over every username nad hash in the second list | |
for second_username, second_ntlm_hash in second_hashes.items(): | |
# Proceed if the first NTLM hash matches this NTLM hash | |
if first_ntlm_hash == second_ntlm_hash: | |
# Get the user data from BloodHound | |
first_results = execute_query(neo4j_driver, first_username) | |
if check_records(first_results): | |
# Enumerate over the results (there _could_ be more than one) | |
for first_record in first_results: | |
first_enabled = first_record[0] | |
first_pwdlastset = datetime.utcfromtimestamp(first_record[1]).strftime('%Y-%m-%d %H:%M:%S') | |
first_domain = first_record[2] | |
# Only continue if this first account is enabled (it should be based on the query) | |
if first_enabled: | |
# Record the account details in the dictionary | |
shared_passwords['accounts'][first_username] = {} | |
shared_passwords['accounts'][first_username]['username'] = first_username.upper() + '@' + first_domain | |
shared_passwords['accounts'][first_username]['enabled'] = first_enabled | |
shared_passwords['accounts'][first_username]['pwdlastset'] = first_pwdlastset | |
shared_passwords['accounts'][first_username]['domain'] = first_domain | |
shared_passwords['accounts'][first_username]['matching_accounts'] = {} | |
# Check if the hash was cracked | |
cracked = False | |
if first_ntlm_hash in potfile_hashes.keys(): | |
cracked = True | |
# Add details about the hash to the entry for this account | |
shared_passwords['accounts'][first_username]['hash'] = {} | |
shared_passwords['accounts'][first_username]['hash']['value'] = first_ntlm_hash | |
shared_passwords['accounts'][first_username]['hash']['sanitized_value'] = sanitize(first_ntlm_hash) | |
shared_passwords['accounts'][first_username]['hash']['matched_value'] =second_ntlm_hash | |
shared_passwords['accounts'][first_username]['hash']['sanitized_matched_value'] = sanitize(second_ntlm_hash) | |
shared_passwords['accounts'][first_username]['hash']['cracked'] = cracked | |
# Record a plaintext value if the hash was cracked | |
if cracked: | |
shared_passwords['accounts'][first_username]['hash']['plaintext'] = potfile_hashes[first_ntlm_hash] | |
# Collect the second account from BloodHound | |
second_results = execute_query(neo4j_driver, second_username) | |
# This time proceed if there are results or record the result as a disabled account match | |
if check_records(second_results): | |
for second_record in second_results: | |
second_enabled = second_record[0] | |
second_pwdlastset = datetime.utcfromtimestamp(second_record[1]).strftime('%Y-%m-%d %H:%M:%S') | |
second_domain = second_record[2] | |
if second_enabled: | |
# Track accounts in list two that share a password with a list one account | |
shared_password_count += 1 | |
# Add the results to the 'matching_account' key | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username] = {} | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['username'] = second_username.upper() + '@' + second_domain | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['enabled'] = second_enabled | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['pwdlastset'] = second_pwdlastset | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['domain'] = second_domain | |
else: | |
# Same as above but for disabled accounts | |
second_results = execute_query(neo4j_driver, second_username, enabled=False) | |
if check_records(second_results): | |
disabled_shared_password_count += 1 | |
for second_record in second_results: | |
second_enabled = second_record[0] | |
second_pwdlastset = datetime.utcfromtimestamp(second_record[1]).strftime('%Y-%m-%d %H:%M:%S') | |
second_domain = second_record[2] | |
# Add the results to the 'matching_account' key | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username] = {} | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['username'] = second_username.upper() + '@' + second_domain | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['enabled'] = second_enabled | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['pwdlastset'] = second_pwdlastset | |
shared_passwords['accounts'][first_username]['matching_accounts'][second_username]['domain'] = second_domain | |
# Manually update the progress by 1 for each account loop | |
bar.update(1) | |
# Record the final numeric results in the 'results' key in the dictionary | |
shared_passwords['results']['total'] = shared_password_count | |
shared_passwords['results']['disabled_matches'] = disabled_shared_password_count | |
shared_passwords['results']['unique_accounts'] = len(shared_passwords['accounts']) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment