From 11abd79cfa60b0b0758b957e5b42e5e206f1d41a Mon Sep 17 00:00:00 2001 From: Ada Isopods Date: Thu, 22 May 2025 00:08:29 -0600 Subject: group passwords tried by corresponding username --- cowrieder.py | 54 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 12 deletions(-) (limited to 'cowrieder.py') diff --git a/cowrieder.py b/cowrieder.py index 79abfb6..562cc8f 100644 --- a/cowrieder.py +++ b/cowrieder.py @@ -25,7 +25,7 @@ parser = argparse.ArgumentParser( parser.add_argument('filepath', help="The path to the cowrie JSON log file you want to analyze, or a folder of them. If given a folder, looks for any .json file or any file starting with \"cowrie.json\" and ingests them sorted by file modification timestamp") parser.add_argument('-v', '--verbose', action='store_true', help="Make the output more verbose (do less to collapse less-interesting information)") -parser.add_argument('-V', '--verboser', action='store_true', help="Make the output even more verbose (print timestamp, event id, and message (if applicable) for all events)") +parser.add_argument('-V', '--verboser', action='store_true', help="Make the output even more verbose (one line per event)") parser.add_argument('-W', '--verbosest', action='store_true', help="Make the output even more verboserer (dump as much information as possible as pretty-printed JSON)") parser.add_argument('-P', '--pings', action='store_true', help='Include only connections where no authentication attempt was made') @@ -34,12 +34,12 @@ parser.add_argument('-L', '--logins', action='store_true', help='Include only su parser.add_argument('-C', '--commands', action='store_true', help='Include only logins where only this list of basic test commands were run: ' + str(cmdping_commands)) parser.add_argument('-E', '--eventful', action='store_true', help='Include only the cool interesting sessions that aren\'t any of that other stuff') -parser.add_argument('-r', '--redact-ip', action='store_true', help='Redact IP addresses by converting them to 16-character hashes') -parser.add_argument('-i', '--filter-ip', help='Filter data to a specific IP, censored hash thereof, or netmask (127.0.0.1 / 0b39f7265d91e7c3 / 127.0.0.0/16)') parser.add_argument('-u', '--filter-username', help='Filter data to uses of a specific username') parser.add_argument('-p', '--filter-password', help='Filter data to uses of a specific password. Pass "'+pubkey_attempt_str+'" to filter for attempts to log in using an SSH public key instead of a password, which does happen') parser.add_argument('-z', '--filter-sensor', help='Filter data to a certain \"sensor\". Useful if Cowrie is running in proxy mode to multiple endpoints') +parser.add_argument('-i', '--filter-ip', help='Filter data to a specific IP, censored hash thereof, or netmask (127.0.0.1 / 0b39f7265d91e7c3 / 127.0.0.0/16)') +parser.add_argument('-r', '--redact-ip', action='store_true', help='Redact IP addresses by converting them to 16-character hashes') parser.add_argument('-c', '--no-color', action='store_true', help="Don't color-code terminal output") parser.add_argument('-s', '--stats', action='store_true', help='Print aggregated statistics') @@ -268,6 +268,7 @@ def percent_of_all_sessions(num): login_combos = [] attempt_counts_by_username = {} attempt_counts_by_password = {} +passwords_by_username = {} cnt_pings = 0 cnt_fails = 0 @@ -320,6 +321,11 @@ for a in login_combos: update_cnt_by(a[0], attempt_counts_by_username) update_cnt_by(a[1], attempt_counts_by_password) + if(a[0] not in passwords_by_username): + passwords_by_username.update({a[0]: [a[1]]}) + elif(a[1] not in passwords_by_username[a[0]]): + passwords_by_username[a[0]].append(a[1]) + def cnt_by_to_by_cnt(cnt_by): by_cnt = {} for item in cnt_by: @@ -331,6 +337,7 @@ def cnt_by_to_by_cnt(cnt_by): attempt_counts_by_ip = dict(sorted(attempt_counts_by_ip.items(), key=lambda item: item[1], reverse=True)) attempt_counts_by_username = dict(sorted(attempt_counts_by_username.items(), key=lambda item: item[1], reverse=True)) attempt_counts_by_password = dict(sorted(attempt_counts_by_password.items(), key=lambda item: item[1], reverse=True)) +passwords_by_username = dict(sorted(passwords_by_username.items(), key=lambda item: len(item[1]), reverse=True)) ips_by_attempt_count = cnt_by_to_by_cnt(attempt_counts_by_ip) usernames_by_attempt_count = cnt_by_to_by_cnt(attempt_counts_by_username) @@ -338,9 +345,17 @@ passwords_by_attempt_count = cnt_by_to_by_cnt(attempt_counts_by_password) # Printing logic coming up -def print_by_cnt(by_cnt, is_ip = False, truncate = 3): +def print_dict_of_lists(by_cnt, is_ip = False, truncate = 3, limit = -1): + name_max_length = 0 + for item in by_cnt: + if(name_max_length < len(str(item))): + name_max_length = len(str(item)) + + entries_printed = 0 for num in by_cnt: - out = (" " * (len(str(next(iter(by_cnt)))) - len(str(num)))) + str(num) + ": " + if(limit > 0 and entries_printed >= limit): + break + out = (" " * (name_max_length - len(str(num)))) + str(num) + ": " cnt = 0 for item in by_cnt[num]: if(verbosity == 0 and cnt >= truncate): @@ -354,6 +369,7 @@ def print_by_cnt(by_cnt, is_ip = False, truncate = 3): out += ", " cnt += 1 print(out) + entries_printed += 1 def print_percentage_stats(): if(len(attempt_counts_by_ip) > 1): @@ -428,7 +444,8 @@ if(verbosity == 3): # Maximum verbosity: literally just dump everything we have 'attempt_counts_by_password': attempt_counts_by_password, 'ips_by_attempt_count': ips_by_attempt_count, 'usernames_by_attempt_count': usernames_by_attempt_count, - 'passwords_by_attempt_count': passwords_by_attempt_count + 'passwords_by_attempt_count': passwords_by_attempt_count, + 'passwords_by_username': passwords_by_username } outjson.update({'stats': all_stats}) @@ -454,6 +471,8 @@ elif(verbosity == 2): # High verbosity mode: print timestamp, event type and mes pprint(attempt_counts_by_username, sort_dicts=False) print("\n--- Passwords and number of times they were tried: ---") pprint(attempt_counts_by_password, sort_dicts=False) + print("\n--- Passwords grouped by corresponding username: ---") + pprint(passwords_by_username, sort_dicts=False) print("\n--- Source IPs and number of connections originating from them: ---") pprint(attempt_counts_by_ip, sort_dicts=False) print_percentage_stats() @@ -511,13 +530,24 @@ else: # Default (low) verbosity: just the good stuff else: # Printing logic for successful logins in which no commands were run print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("GREEN") + "Login" + ccgen() + " with credentials: " + str(session_cred_combos(session))) if(print_stats): - print("\n--- Source IP addresses by attempt count: ---") - print_by_cnt(ips_by_attempt_count, True) if(verbosity > 0): + print("\n--- Source IP addresses by attempt count: ---") + print_dict_of_lists(ips_by_attempt_count, True) print("\n--- Credential combos used: ---") print(str(login_combos)) - print("\n--- Usernames by attempt count: ---") - print_by_cnt(usernames_by_attempt_count, truncate=10) - print("\n--- Passwords by attempt count: ---") - print_by_cnt(passwords_by_attempt_count, truncate=10) + print("\n--- Usernames by attempt count: ---") + print_dict_of_lists(usernames_by_attempt_count) + print("\n--- Passwords by attempt count: ---") + print_dict_of_lists(passwords_by_attempt_count) + print("\n--- Passwords by username: ---") + print_dict_of_lists(passwords_by_username) + else: + print("\n--- Top source IP addresses by attempt count: ---") + print_dict_of_lists(ips_by_attempt_count, True, limit = 20) + print("\n--- Top usernames by attempt count: ---") + print_dict_of_lists(usernames_by_attempt_count, truncate=10, limit = 20) + print("\n--- Top passwords by attempt count: ---") + print_dict_of_lists(passwords_by_attempt_count, truncate=10, limit = 20) + print("\n--- Top usernames with passwords tried: ---") + print_dict_of_lists(passwords_by_username, truncate=10, limit = 20) print_percentage_stats() -- cgit v1.2.3