JARM

Active TLS fingerprinting using JARM.

Data Enrichment & Threat Intelligence · JARM

Details

IDJARM
ProviderSalesforce
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/py3-tools:1.0.0.10120494
Supported ModulesAgentix XSIAM

README

Active TLS fingerprinting using JARM

Configure JARM in Cortex

Parameter Required
Use system proxy settings False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

jarm-fingerprint


Calculate JARM fingerprint by scanning host with multiple TLS packets.

Base Command

jarm-fingerprint

Input

Argument Name Description Required
host FQDN or IP address to fingerprint. Also supports [https://fqdn:port] format. Required
port Port to fingerprint. If provided overrides the port specified in the host parameter. Default is 443. Optional

Context Output

Path Type Description
JARM.FQDN String FQDN of the host.
JARM.IP String IP Address of the host.
JARM.Port Number TCP port
JARM.Target String The host in the format [IP or FQDN]:Port
JARM.Fingerprint String JARM fingerprint of the host.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.

Command Example

!jarm-fingerprint host="google.com" port=443

Context Example

{
    "DBotScore": [
        {
            "Indicator": "27d40d40d29d40d1dc42d43d00041d4689ee210389f4f6b4b5b1b93f92252d",
            "Score": 0,
            "Type": "jarm",
            "Vendor": "JARM"
        }
    ],
    "JARM": {
        "FQDN": "google.com",
        "Fingerprint": "27d40d40d29d40d1dc42d43d00041d4689ee210389f4f6b4b5b1b93f92252d",
        "Port": 443,
        "Target": "google.com:443"
    }
}

Human Readable Output

Results

FQDN Fingerprint Port Target
google.com 27d40d40d29d40d1dc42d43d00041d4689ee210389f4f6b4b5b1b93f92252d 443 google.com:443

Configuration parameters

  • proxy — Use system proxy settings

Commands (1)

  • jarm-fingerprint

    Calculate JARM fingerprint by scanning host with multiple TLS packets.

import asyncio
import traceback
from ipaddress import ip_address
from typing import Any
from urllib.parse import urlparse

import demistomock as demisto
import urllib3
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import
from jarm.scanner.scanner import Scanner  # pylint: disable=E0401,E0611

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()  # pylint: disable=no-member

DEFAULT_PORT = 443

""" CLIENT CLASS """


class Client:
    def jarm_fingerprint(self, host: str, port: int) -> tuple[str, str, int]:
        return asyncio.run(Scanner.scan_async(host, port, suppress=True))


""" HELPER FUNCTIONS """


def parse_hostname(hostname: str, port: Optional[int]) -> dict[str, Any]:
    """
    Parses a target hostname. Supports multiple ipv4/fqdn with and without port formats.
    """
    target: dict[str, Any] = {}
    if not hostname.startswith("https://"):
        hostname = "https://" + hostname

    parsed_url = urlparse(hostname)
    if port:
        target["port"] = port
    elif parsed_url.port:
        target["port"] = parsed_url.port
    else:
        target["port"] = DEFAULT_PORT

    try:
        ip = ip_address(parsed_url.hostname)  # type: ignore[arg-type]
        target["target_host"] = str(ip)
        target["target_type"] = "ip"
    except ValueError:
        target["target_host"] = parsed_url.hostname
        target["target_type"] = "fqdn"

    return target


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> str:
    return "ok"


def jarm_fingerprint_command(client: Client, args: dict[str, Any]) -> CommandResults:
    class JARMDBotScore(Common.Indicator):
        def __init__(self, output: dict[str, Any]):
            self._jarm = output.get("Fingerprint")

        def to_context(self) -> dict[str, Any]:
            return {
                "DBotScore": {
                    "Indicator": self._jarm,
                    "Type": "jarm",
                    "Vendor": "JARM",
                    "Score": Common.DBotScore.NONE,
                }
            }

        def to_minimum_context(self) -> dict[str, Any]:
            return {
                "Indicator": self._jarm,
                "Type": "jarm",
            }

    host = args.get("host")
    if not host:
        raise ValueError("Host name (IP or domain) not specified")

    port = arg_to_number(args.get("port"))

    target = parse_hostname(host, port)

    target_type = target.get("target_type")
    if not target_type:
        raise ValueError("Cannot determine scan target")

    target_host = target.get("target_host")
    if not target_host:
        raise ValueError("Cannot determine scan target")

    port = target.get("port")
    if not port:
        raise ValueError("Invalid port provided")

    result = client.jarm_fingerprint(target_host, port)

    output = {}
    output["Fingerprint"] = result[0]
    output["Target"] = f"{target_host}:{port}"
    output["Port"] = port
    if target_type == "ip":
        output["IP"] = target_host
    elif target_type == "fqdn":
        output["FQDN"] = target_host

    return CommandResults(
        outputs_prefix="JARM", outputs_key_field=["FQDN", "IP", "Port"], outputs=output, indicator=JARMDBotScore(output=output)
    )


""" MAIN FUNCTION """


def main() -> None:
    command = demisto.command()
    demisto.debug(f"Command being called is {command}")
    try:
        handle_proxy()
        client = Client()

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            return_results(test_module(client))

        elif command == "jarm-fingerprint":
            return_results(jarm_fingerprint_command(client, demisto.args()))
        else:
            raise NotImplementedError(f"Command {command} is not implemented.")

    # Log exceptions and return errors
    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """

if __name__ in ("__main__", "__builtin__", "builtins"):
    main()