From c21ad820902c69b1bffa4c6453c0e1a15f07fe8c Mon Sep 17 00:00:00 2001 From: Ada Isopods Date: Fri, 16 May 2025 01:40:11 -0600 Subject: cleaned up some 14-hours-without-sleep-after-a-shift-at-work-ass logic and added the ability to redact IPs --- cowrieder.py | 73 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/cowrieder.py b/cowrieder.py index 419dd45..f022d4c 100644 --- a/cowrieder.py +++ b/cowrieder.py @@ -3,6 +3,7 @@ import sys import random import os import argparse +import hashlib def ccgen(seed=""): # Color code generator if(not os.isatty(1)): # Do not output color code at all if output is not a TTY that can accept them @@ -35,10 +36,13 @@ def ttylog(mystring): #mystring if(os.isatty(1)): print(mystring) -def ppip(addr, spacing = True): # Pretty-print an address with colour and, if it's IPv4, a consistent width - if(len(addr) <= 16 and spacing): - return ccgen(addr) + addr + ccgen() + (" " * (15 - len(addr))) - return ccgen(addr) + addr + ccgen() +def ppip(addr, spacing = False, ttycolor = False): # Pretty-print an address with colour and, if it's IPv4, a consistent width + rs = addr + if(ttycolor): + rs = ccgen(rs) + rs + ccgen() + if(len(rs) <= 16 and spacing): + rs = rs + (" " * (15 - len(addr))) + return rs parser = argparse.ArgumentParser( description="Script to parse JSON logs from popular SSH honeypot Cowrie and print them in a more readable format" @@ -50,14 +54,14 @@ parser.add_argument('-v', '--verbose', action='store_true', help="Make the outpu parser.add_argument('-vv', '--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('-vvv', '--verbosest', action='store_true', help="Make the output even more verboserer (just group the input log by session id and dump it to pretty-printed JSON)") -parser.add_argument('-B', '--blank', action='store_true', help="Print nothing whatsoever. Override this with other arguments to filter for only the stuff you care about") -parser.add_argument('-P', '--pings', action='store_true', help='| With blank mode active, print "empty pings"') -parser.add_argument('-F', '--fails', action='store_true', help='| With blank mode active, print failed logins') -parser.add_argument('-L', '--logins', action='store_true', help='| With blank mode active, print "login pings"') -parser.add_argument('-C', '--commands', action='store_true', help='| With blank mode active, print "command pings"') -parser.add_argument('-S', '--sessions', action='store_true', help='* With blank mode active, print the cool interesting sessions that aren\'t any of that other stuff.') +parser.add_argument('-P', '--pings', action='store_true', help='Print only "empty pings"') +parser.add_argument('-F', '--fails', action='store_true', help='Print only failed logins') +parser.add_argument('-L', '--logins', action='store_true', help='Print only "login pings"') +parser.add_argument('-C', '--commands', action='store_true', help='Print only "command pings"') +parser.add_argument('-S', '--sessions', action='store_true', help='Print only the cool interesting sessions that aren\'t any of that other stuff.') -parser.add_argument('-i', '--filter-ip', help='Filter data to a specific IP') +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 or censored hash thereof') #parser.add_argument('-a', '--stats', action='store_true', help='Print aggregated statistics') #parser.add_argument('-j', '--json-stats', action='store_true', help='Dump aggregated statistics as JSON') @@ -67,17 +71,13 @@ args = parser.parse_args() filepath = args.filepath -print_emptypings = True -print_fails = True -print_loginpings = True -print_cmdpings = True -print_sessions = True -if(args.blank): - print_emptypings = args.pings - print_fails = args.fails - print_loginpings = args.logins - print_cmdpings = args.commands - print_sessions = args.sessions +blankmode = (args.pings or args.fails or args.logins or args.commands or args.sessions) + +print_emptypings = (not blankmode or args.pings) +print_fails = (not blankmode or args.fails) +print_loginpings = (not blankmode or args.logins) +print_cmdpings = (not blankmode or args.commands) +print_sessions = (not blankmode or args.sessions) entries = [] sessions = {} # Dict - session_id : list of events in session @@ -105,7 +105,10 @@ with open(filepath, "r") as logfile: if(session_id not in sessions): sessions.update({session_id: [entry]}) - session_ips.update({session_id: entry['src_ip']}) + if(args.redact_ip): + session_ips.update({session_id: hashlib.shake_256(entry['src_ip'].encode("utf-8")).hexdigest(8)}) + else: + session_ips.update({session_id: entry['src_ip']}) else: sessions[session_id].append(entry) @@ -149,11 +152,11 @@ if(args.blank): for session_id in sessions: if( - (not print_emptypings and session_id not in session_successful_login) or # Is empty ping and don't print those - (not print_fails and session_id in session_successful_login and not session_successful_login[session_id]) or # Is failed login and don't print those - (not print_loginpings and session_id in session_successful_login and session_successful_login[session_id] and session_id not in session_ran_command) or # is login ping and don't print those - (not print_cmdpings and session_id in session_ran_command and session_id not in session_not_cmdpings) or # is command ping and don't print those - (not print_sessions and session_id in session_ran_command and session_id in session_not_cmdpings) # is session and don't print those + (not print_emptypings and session_id not in session_successful_login) # Is empty ping and don't print those + or (not print_fails and session_id in session_successful_login and not session_successful_login[session_id]) # Is failed login and don't print those + or (not print_loginpings and session_id in session_successful_login and session_successful_login[session_id] and session_id not in session_ran_command) # is login ping and don't print those + or (not print_cmdpings and session_id in session_ran_command and session_id not in session_not_cmdpings) # is command ping and don't print those + or (not print_sessions and session_id in session_ran_command and session_id in session_not_cmdpings) # is session and don't print those ): newsessions.pop(session_id) @@ -164,7 +167,7 @@ if(args.blank): if(args.verboser): # High verbosity mode: print timestamp, event type and message (if applicable) for every event. I could probably do better but I'm lazy for session_id in sessions: ip = session_ips[session_id] - print("\n--- Session " + ccgen(session_id) + session_id + ccgen() + " from " + ppip(ip, False) +" ---") + print("\n--- Session " + ccgen(session_id) + session_id + ccgen() + " from " + ppip(ip, ttycolor=True) +" ---") for entry in sessions[session_id]: if(entry['message']): eventid = entry['eventid'] @@ -183,9 +186,9 @@ else: # Default (low) verbosity: just the good stuff if(session_id not in session_successful_login): # Printing logic for empty pings if(session_id in session_durations): - print(timestamp + " > " + ppip(ip) + " > " + ccgen("PURPLE") + "Empty ping" + ccgen() + " lasting " + session_durations[session_id] + "s") + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("PURPLE") + "Empty ping" + ccgen() + " lasting " + session_durations[session_id] + "s") else: - print(timestamp + " > " + ppip(ip) + " > " + ccgen("PURPLE") + "Empty ping" + ccgen() + " active as of processing") + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("PURPLE") + "Empty ping" + ccgen() + " active as of processing") elif(not session_successful_login[session_id]): # Printing logic for failed logins cred_combos = [] @@ -195,7 +198,7 @@ else: # Default (low) verbosity: just the good stuff if("password" in entry): pw = entry["password"] cred_combos.append((entry["username"], pw)) - print(timestamp + " > " + ppip(ip) + " > " + ccgen("RED") +"Failed login" + ccgen() + ": " + str(cred_combos)) + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("RED") +"Failed login" + ccgen() + ": " + str(cred_combos)) elif(session_id in session_ran_command): # Printing logic for sessions in which a command was run @@ -204,10 +207,10 @@ else: # Default (low) verbosity: just the good stuff for entry in sessions[session_id]: if(entry["eventid"] == "cowrie.login.failed" or entry["eventid"] == "cowrie.login.success"): cred_combos.append((entry["username"], entry["password"])) - print(timestamp + " > " + ppip(ip) + " > " + ccgen("BLUE") + "Command ping" + ccgen() + ": " + str(cred_combos) + " > " + str(session_cmdping_commands[session_id])) + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("BLUE") + "Command ping" + ccgen() + ": " + str(cred_combos) + " > " + str(session_cmdping_commands[session_id])) else: # Printing logic for un-collapsed sessions - print(timestamp + " > " + ppip(ip) + " > " + ccgen("CYAN") + "Session" + ccgen() + ":") + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("CYAN") + "Session" + ccgen() + ":") for entry in sessions[session_id]: match(entry["eventid"]): case "cowrie.login.failed": @@ -233,4 +236,4 @@ else: # Default (low) verbosity: just the good stuff for entry in sessions[session_id]: if(entry["eventid"] == "cowrie.login.failed" or entry["eventid"] == "cowrie.login.success"): cred_combos.append((entry["username"], entry["password"])) - print(timestamp + " > " + ppip(ip) + " > " + ccgen("GREEN") + "Login ping" + ccgen() + ": " + str(cred_combos)) + print(timestamp + " > " + ppip(ip, True, True) + " > " + ccgen("GREEN") + "Login ping" + ccgen() + ": " + str(cred_combos)) -- cgit v1.2.3