Esempio n. 1
0
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))
Esempio n. 2
0
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)
Esempio n. 3
0
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()
Esempio n. 4
0
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()
Esempio n. 5
0
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()
Esempio n. 6
0
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
Esempio n. 7
0
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