aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--cowrieder.py54
2 files changed, 47 insertions, 16 deletions
diff --git a/README.md b/README.md
index f18bf9f..49896bc 100644
--- a/README.md
+++ b/README.md
@@ -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()