def _process_packet(session: Session, packet: Packet, must_inspect_strings: bool): """ Processes a single packet within its context thanks to the `Session` instance. Parameters ---------- session : Session The session the packet belongs to. packet : Packet To packet to be analysed. must_inspect_strings : bool Whether strings in the packet should be inspected or not. Can be pretty heavy on the CPU. """ if len(packet.layers ) > 3: # == tshark parsed something else than ETH, IP, TCP for layer in packet.layers[3:]: layer_name = layer.layer_name if hasattr(layer, "_ws_malformed_expert"): raise MalformedPacketException( "[{}] session contains malformed packet in layer '{}'". format(session, layer_name)) # Not based on layer name, can be found in different layers if hasattr(layer, "nt_status") or ( hasattr(layer, "ntlmssp_identifier") and layer.ntlmssp_identifier == "NTLMSSP"): session.protocol = layer_name.upper() ntlmssp.analyse(session, layer) # Analyse the layer with the appropriate parser if layer_name in parsers: session.protocol = layer_name.upper() parsers[layer_name].analyse(session, layer) if must_inspect_strings: strings = utils.extract_strings_splitted_on_end_of_line_from(packet) emails_found = extract.extract_emails(strings) credit_cards_found = extract.extract_credit_cards(strings) for email in emails_found: logger.info(session, "Found email address: " + email) for credit_card in credit_cards_found: logger.info( session, "Credit card '{}' found: '{}'".format(credit_card.name, credit_card.number))
def active_processing(interface: str, must_inspect_strings=False, tshark_filter=None, debug=False, decode_as=None, pcap_output=None, creds_found_callback=None): """ Initialize packets capturing on a given interface file. This is one of the main entry points most people will want to use. Parameters ---------- interface : str The network interface to listen to. must_inspect_strings : bool Whether strings in the packet should be inspected or not. Can be pretty heavy on the CPU. tshark_filter : string Capture filter passed to tshark. Example : "host 192.168.1.42" See : https://wiki.wireshark.org/CaptureFilters debug : bool Toggle the debug mode of tshark, useful to track down bugs. decode_as : Dict[str, str] Associate a protocol to a port so that tshark processes packets correctly. pcap_output : str Captured packets will be output to that file path. creds_found_callback : Callable[[Credentials], None] The function to call every time new credentials are found. Credentials are passed as parameter. """ logger.DEBUG_MODE = debug sessions = SessionsManager(remove_outdated=True) Session.creds_found_callback = creds_found_callback signal.signal(signal.SIGINT, signal_handler) with pyshark.LiveCapture(interface=interface, bpf_filter=tshark_filter, debug=debug, decode_as=decode_as, output_file=pcap_output) as live: logger.info("Listening on {}...".format(interface)) _process_packets_from(live.sniff_continuously(), sessions, must_inspect_strings)
def analyse(session: Session, layer: Layer): current_creds = session.credentials_being_built if hasattr(layer, "server_greeting"): if hasattr(layer, "version"): logger.info(session, "MySQL version: " + layer.version) if hasattr(layer, "salt"): current_creds.context["salt"] = layer.salt if hasattr(layer, "salt2"): current_creds.context["salt2"] = layer.salt2 if hasattr(layer, "client_auth_plugin"): logger.info(session, "MySQL auth plugin: " + layer.client_auth_plugin) if hasattr(layer, "user"): current_creds.username = layer.user current_creds.hash = "".join(layer.passwd.split(":")) if hasattr(layer, "response_code") or hasattr(layer, "query"): # Yes this try except is ugly, but there's a bug before tshark 3.0 which prevents us to use response_code # See https://www.wireshark.org/docs/dfref/i/imap.html try: response_code = int(layer.response_code, 16) auth_successful = response_code == 0 except AttributeError: auth_successful = hasattr(layer, "query") if current_creds.username and auth_successful: for item in current_creds.context: logger.found(session, "{} found: {}".format(item, current_creds.context[item])) logger.found(session, "credentials found: {} -- {}".format(current_creds.username, current_creds.hash)) session.validate_credentials()
def main(): parser = build_argument_parser() args = parser.parse_args() if args.listen: if args.pcapfiles: parser.error( "You cannot specify pcap files to analyse and listen at the same time" ) else: if not args.pcapfiles: parser.error("Nothing to do...") if args.listen_output: parser.error( "Cannot specify --listen-output/-lo if not in listening mode") string_inspection = None if args.string_inspection == "enable": string_inspection = True elif args.string_inspection == "disable": string_inspection = False ip_filter = None if args.filter: # tshark display filter try: socket.inet_aton(args.filter) ip_filter = "ip.src == {0} or ip.dst == {0}".format(args.filter) except socket.error: try: socket.inet_pton(socket.AF_INET6, args.filter) ip_filter = "ipv6.src == {0} or ipv6.dst == {0}".format( args.filter) except socket.error: parser.error("Invalid IP address filter") # dumpcap capture filter if args.listen: ip_filter = "host " + args.filter decode_map = None if args.map: decode_map = {} for m in args.map: tokens = m.split(":") if len(tokens) != 2: parser.error("Invalid port mapping") decode_map["tcp.port==" + tokens[0]] = tokens[1] logger.info( "CredSLayer will decode traffic on '{}' as '{}'".format( *tokens)) if args.output: if os.path.isfile(args.output): parser.error(args.output + " already exists") logger.OUTPUT_FILE = open(args.output, "w") if args.listen: if os.geteuid() != 0: print("You must be root to listen on an interface.") exit(1) if args.listen_output and os.path.isfile(args.listen_output): parser.error(args.listen_output + " already exists") manager.active_processing(args.listen, must_inspect_strings=string_inspection, tshark_filter=ip_filter, debug=args.debug, decode_as=decode_map, pcap_output=args.listen_output) exit(0) for pcap in args.pcapfiles: try: manager.process_pcap(pcap, must_inspect_strings=string_inspection, tshark_filter=ip_filter, debug=args.debug, decode_as=decode_map) except Exception as e: error_str = str(e) if error_str.startswith("[Errno"): # Clean error message errno_end_index = error_str.find("]") + 2 error_str = error_str[errno_end_index:] logger.error(error_str) else: traceback.print_exc() if logger.OUTPUT_FILE: logger.OUTPUT_FILE.close()
def analyse(session: Session, layer: Layer): current_creds = session.credentials_being_built if hasattr(layer, "request_uri"): extension = layer.request_uri.split(".")[-1] if extension in HTTP_IGNORED_EXTENSIONS: return # Ignore Certificate Status Protocol if hasattr(layer, "request_full_uri" ) and layer.request_full_uri.startswith("http://ocsp."): return if hasattr(layer, "authorization"): tokens = layer.authorization.split(" ") if len(tokens) == 2 and tokens[0] == "Basic": try: credentials = base64.b64decode(tokens[1]).decode() colon_index = credentials.find(":") current_creds.username = credentials[:colon_index] current_creds.password = credentials[colon_index + 1:] session[ "authorization_header_uri"] = layer.request_full_uri except UnicodeDecodeError: logger.error("HTTP Basic auth failed: " + tokens) elif len(tokens) == 2 and tokens[0] == "NTLM": pass # Already handled by the NTLMSSP module else: logger.info( session, "Authorization header found: '{}'".format( layer.authorization)) # POST parameters if hasattr(layer, "file_data"): post_content = layer.file_data if len(post_content) <= HTTP_AUTH_MAX_LOGIN_POST_LENGTH: logger.info(session, "POST data found: '{}'".format(post_content)) post_parameters = parse_qs(post_content) # We don't want to interfere with the Authorization header potentially being built credentials = Credentials() credentials.context["Method"] = "POST" credentials.context["URL"] = layer.request_full_uri logger.info(session, "context: " + str(credentials.context)) for parameter in post_parameters: if parameter in HTTP_AUTH_POTENTIAL_USERNAMES: credentials.username = post_parameters[parameter][0] elif parameter in HTTP_AUTH_POTENTIAL_PASSWORDS: credentials.password = post_parameters[parameter][0] if credentials.username: logger.found( session, "credentials found: {} -- {}".format( credentials.username, credentials.password)) session.credentials_list.append( credentials) # Don't validate those credentials return # GET parameters elif hasattr(layer, "request_uri_query"): get_parameters = parse_qs(layer.request_uri_query) # We don't want to interfere with the Authorization header potentially being built credentials = Credentials() credentials.context["Method"] = "GET" credentials.context["URL"] = layer.request_full_uri for parameter in get_parameters: if parameter in HTTP_AUTH_POTENTIAL_USERNAMES: credentials.username = get_parameters[parameter][0] elif parameter in HTTP_AUTH_POTENTIAL_PASSWORDS: credentials.password = get_parameters[parameter][0] if credentials.username: logger.found( session, "credentials found: {} -- {}".format( credentials.username, credentials.password)) logger.info(session, "context: " + str(credentials.context)) session.credentials_list.append( credentials) # Don't validate those credentials return elif hasattr(layer, "response_for_uri"): if session["authorization_header_uri"] == layer.response_for_uri: # If auth failed + prevent duplicates if layer.response_code == "401" or current_creds in session.credentials_list: session.invalidate_credentials_and_clear_session() else: logger.found( session, "basic auth credentials found: {} -- {}".format( current_creds.username, current_creds.password)) session.validate_credentials()
def process_pcap(filename: str, must_inspect_strings=False, tshark_filter=None, debug=False, decode_as=None, creds_found_callback=None) -> SessionsManager: """ Initialize the processing of a pcap file and retrieve results of the analysis. This is one of the main entry points most people will want to use. Parameters ---------- filename : str Path to the pcap to process. must_inspect_strings : bool Whether strings in the packet should be inspected or not. Can be pretty heavy on the CPU. tshark_filter : string Display filter passed to tshark. Example : "ip.src == 192.168.1.42 or ip.dst == 192.168.1.42" See : https://wiki.wireshark.org/DisplayFilters debug : bool Toggle the debug mode of tshark, useful to track down bugs. decode_as : Dict[str, str] Associate a protocol to a port so that tshark processes packets correctly. creds_found_callback : Callable[[Credentials], None] The function to call every time new credentials are found. Credentials are passed as parameter. Returns ------- A `SessionsManager` instance which gives to ability to the user of that function to retrieve what has been found in the pcap. """ logger.DEBUG_MODE = debug sessions_manager = SessionsManager() Session.creds_found_callback = creds_found_callback with pyshark.FileCapture(filename, display_filter=tshark_filter, decode_as=decode_as, debug=debug) as pcap: logger.info("Processing packets in '{}'".format(filename)) start_time = time.time() _process_packets_from(pcap, sessions_manager, must_inspect_strings) remaining_credentials = sessions_manager.get_remaining_content() if remaining_credentials: logger.info( "Interesting things have been found but the CredSLayer wasn't able validate them: " ) # List things that haven't been reported (sometimes the success indicator has # not been captured and credentials stay in the session without being logged) for session, remaining in remaining_credentials: logger.info(session, str(remaining)) logger.info("Processed in {0:.3f} seconds.".format(time.time() - start_time)) return sessions_manager
def analyse(session: Session, layer: Layer): current_creds = session.credentials_being_built if hasattr(layer, "authtype"): # values signification can be found here https://www.postgresql.org/docs/8.2/protocol-message-formats.html auth_type = int(layer.authtype) if auth_type == 5: current_creds.context["auth_type"] = "md5" elif auth_type == 4: current_creds.context["auth_type"] = "crypt" elif auth_type == 3: current_creds.context["auth_type"] = "cleartext" elif auth_type == 10: current_creds.context["auth_type"] = "sasl" elif auth_type == 0 and current_creds.username: if current_creds.hash: logger.found( session, "credentials found ! Username: {} | Hash: {} | Salt: {}". format(current_creds.username, current_creds.hash, current_creds.context["salt"])) elif current_creds.password: logger.found( session, "credentials found ! Username: {} | Password: {}".format( current_creds.username, current_creds.password)) else: logger.found( session, "it seems that '{}' authenticated without password".format( current_creds.username)) if "database" in current_creds.context: logger.info("Targeting database '{}'".format( current_creds.context["database"])) session.validate_credentials() return if hasattr(layer, "parameter_name"): # Sometimes tshark returns multiple fields with the same name parameter_names = layer.parameter_name.all_fields parameter_values = layer.parameter_value.all_fields for i in range(len(parameter_names)): parameter_name = parameter_names[i].show parameter_value = parameter_values[i].show if parameter_name == "user": current_creds.username = parameter_value elif parameter_name == "database": current_creds.context["database"] = parameter_value elif parameter_name == "server_version": logger.info(session, "PostgreSQL version: " + parameter_value) if hasattr(layer, "salt"): current_creds.context["salt"] = layer.salt.replace(":", "") elif hasattr(layer, "password"): if "auth_type" in current_creds.context and current_creds.context[ "auth_type"] != "cleartext": current_creds.hash = layer.password auth_type = current_creds.context["auth_type"] # Remove the hash type from the hash string if current_creds.hash.startswith(auth_type): current_creds.hash = current_creds.hash[len(auth_type):] else: current_creds.password = layer.password