diff options
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | cowrieder.py | 54 |
2 files changed, 47 insertions, 16 deletions
@@ -14,26 +14,27 @@ Options: ``` -h, --help show this help message and exit -v, --verbose Make the output more verbose (do less to collapse less-interesting information) --V, --verboser Make the output even more verbose (print timestamp, event id, and message (if applicable) for all events) +-V, --verboser Make the output even more verbose (one line per event) -W, --verbosest Make the output even more verboserer (dump as much information as possible as pretty-printed JSON) -P, --pings Include only connections where no authentication attempt was made -F, --fails Include only failed logins -L, --logins Include only successful logins where no commands were run -C, --commands Include only logins where only this list of basic test commands were run: ['uname', 'nproc'] -E, --eventful Include only the cool interesting sessions that aren't any of that other stuff --r, --redact-ip Redact IP addresses by converting them to 16-character hashes --i, --filter-ip FILTER_IP - Filter data to a specific IP, censored hash thereof, or netmask (127.0.0.1 / 0b39f7265d91e7c3 / 127.0.0.0/16) -u, --filter-username FILTER_USERNAME Filter data to uses of a specific username -p, --filter-password FILTER_PASSWORD Filter data to uses of a specific password. Pass "[SSH_PUBKEY_ATTEMPT]" to filter for attempts to log in using an SSH public key instead of a password, which does happen -z, --filter-sensor FILTER_SENSOR Filter data to a certain "sensor". Useful if Cowrie is running in proxy mode to multiple endpoints +-i, --filter-ip FILTER_IP + Filter data to a specific IP, censored hash thereof, or netmask (127.0.0.1 / 0b39f7265d91e7c3 / 127.0.0.0/16) +-r, --redact-ip Redact IP addresses by converting them to 16-character hashes -c, --no-color Don't color-code terminal output -s, --stats Print aggregated statistics -S, --only-stats Print only aggregated statistics ``` # TODO +- Filter by regex? - HTML output option
\ No newline at end of file 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() |
