import json import sys import random import os import argparse 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 return "" match(seed): # Match color keywords to control codes case "RED": return "\x1b[31m" case "GREEN": return "\x1b[32m" case "YELLOW": return "\x1b[33m" case "BLUE": return "\x1b[34m" case "PURPLE": return "\x1b[35m" case "CYAN": return "\x1b[36m" case "GREY": return "\x1b[37m" case "RESET": return "\x1b[0m" if(seed): # If seed is provided, use it to generate a color random.seed(seed) return "\x1b[38;2;{r};{g};{b}m".format(r=random.randrange(50, 256), g=random.randrange(50, 256), b=random.randrange(50, 256)) return "\x1b[0m" 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() parser = argparse.ArgumentParser( description="Script to parse JSON logs from popular SSH honeypot Cowrie and print them in a more readable format" ) parser.add_argument('filepath') parser.add_argument('-v', '--verbose', action='store_true', help="Make the output more verbose (print abridged logs for all logins where commands are run, not just the interesting ones)") 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('-i', '--filter-ip', help='Filter data to a specific IP') #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') #print_stats = args.stats or args.json_stats 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 entries = [] sessions = {} # Dict - session_id : list of events in session session_ips = {} # Dict - session_id : source IP session_timestamps = {} # Dict - session_id : timestamp of initial connection session_durations = {} # Dict - session_id : duration session_ran_command = [] # List of sessions where a command was run session_successful_login = {} # Dict - session_id : bool: was the login attempt successful # Sessions where no login attempt was made will not be present cmdping_commands = ['uname', 'nproc', 'echo'] # List of commands that, if used exclusively, qualify a session as a "command ping" session_not_cmdpings = [] # List of sessions where a command not in cmdping_commands was run session_cmdping_commands = {} # Dict - session_id : list of "command ping" commands used in session with open(filepath, "r") as logfile: for line in logfile: entry = json.loads(line) if( (not args.filter_ip or entry["src_ip"] == args.filter_ip) # If it's not the IP we're filtering for don't even bother man ): entries.append(entry) session_id = entry['session'] if(session_id not in sessions): sessions.update({session_id: [entry]}) session_ips.update({session_id: entry['src_ip']}) else: sessions[session_id].append(entry) if(entry["eventid"] == "cowrie.session.connect" and session_id not in session_timestamps): # Save the timestamp of the session's initial connection session_timestamps.update({session_id: entry['timestamp']}) if(entry["eventid"] == "cowrie.session.closed" and session_id not in session_durations): # Save the session's duration session_durations.update({session_id: entry['duration']}) if(entry["eventid"] == "cowrie.command.input"): if(session_id not in session_ran_command): # Save whether any commands were run during the session session_ran_command.append(session_id) argv0 = entry["input"] # Isolate the name of the command if(" " in argv0): argv0 = entry["input"][:entry["input"].index(" ")] if(argv0 in cmdping_commands): # Determine whether this event qualifies the session as not a command ping if(session_id not in session_cmdping_commands): session_cmdping_commands.update({session_id: [argv0]}) else: session_cmdping_commands[session_id].append(argv0) else: if(session_id not in session_not_cmdpings): session_not_cmdpings.append(session_id) if(entry["eventid"] == "cowrie.login.success" and session_id not in session_successful_login): # Save if login was a success session_successful_login.update({session_id: True}) if(entry["eventid"] == "cowrie.login.failed" and session_id not in session_successful_login): # Save if login failed session_successful_login.update({session_id: False}) if(entry["eventid"] == "cowrie.login.success" and session_id in session_successful_login): # Save if login succeeded after failing session_successful_login[session_id] = True # Blank Mode logic if(args.blank): newsessions = sessions.copy() 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 ): newsessions.pop(session_id) sessions = newsessions # Printing logic coming up 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) +" ---") for entry in sessions[session_id]: if(entry['message']): eventid = entry['eventid'] print(entry["timestamp"] + " > " + eventid + (" " * (29 - len(eventid))) + ": " + entry['message']) else: print(entry["timestamp"] + " > " + entry['eventid']) elif(args.verbosest): # Maximum verbosity: literally just pretty print the now-sorted-by-session logs print(json.dumps(sessions, indent=4)) else: # Default (low) verbosity: just the good stuff for session_id in sessions: ip = session_ips[session_id] timestamp = session_timestamps[session_id][:22].replace("T", " ") # clean up the timestamp text all pretty like 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") else: print(timestamp + " > " + ppip(ip) + " > " + ccgen("PURPLE") + "Empty ping" + ccgen() + " active as of processing") elif(not session_successful_login[session_id]): # Printing logic for failed logins cred_combos = [] for entry in sessions[session_id]: if(entry["eventid"] == "cowrie.login.failed"): pw = [] 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)) elif(session_id in session_ran_command): # Printing logic for sessions in which a command was run if(not args.verbose and session_id not in session_not_cmdpings): # If -v is not enabled, collapse "command pings" to one-liners cred_combos = [] 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])) else: # Printing logic for un-collapsed sessions print(timestamp + " > " + ppip(ip) + " > " + ccgen("CYAN") + "Session" + ccgen() + ":") for entry in sessions[session_id]: match(entry["eventid"]): case "cowrie.login.failed": pw = [] if("password" in entry): pw = entry["password"] print(" | Failed login: " + entry["username"] + "/" + str(pw)) case "cowrie.login.success": print(" | Successful login: " + entry["username"] + "/" + entry["password"]) case "cowrie.command.input": print(" | CMD: " + entry["input"]) case "cowrie.session.file_download": df = "" if("url" in entry): df = entry["url"] elif("destfile" in entry): df = entry["destfile"] print(" | Saved file " + df + " to " + entry["outfile"][:40]) print(" X Disconnected after " + session_durations[session_id] + "s") else: # Printing logic for successful logins in which no commands were run cred_combos = [] 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))