TeamCymru
Team Cymru provides various service options dedicated to mapping IP numbers to BGP prefixes and ASNs. Each of the services is based on the same BGP feeds from 50+ BGP peers and is updated at 4-hour intervals.
Data Enrichment & Threat Intelligence · Team Cymru
Details
| ID | TeamCymru |
|---|---|
| Provider | Audax Private Equity |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.5.0 |
| Docker Image | demisto/vendors-sdk:1.0.0.10120494 |
| Supported Modules | Agentix XSIAM |
README
Team Cymru provides various service options dedicated to mapping IP numbers to BGP prefixes and ASNs. Each of the services is based on the same BGP feeds from 50+ BGP peers and is updated at 4-hour intervals.
This integration was integrated and tested with version 1.0 of TeamCymru
Configure Team Cymru in Cortex
| Parameter | Required | |
|---|---|---|
| Use system proxy settings | False | |
| Proxy URL | Supports socks4/socks5/http connect proxies (e.g., socks5h://host:1080). | False |
| Source Reliability | Reliability of the source providing the intelligence data. | 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.
ip
Checks the reputation of an IP address.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | An IPv4 address to query, e.g., 1.1.1.1. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | String | IP address. |
| IP.ASN | String | The autonomous system name for the IP address, for example: “AS8948”. |
| IP.ASOwner | String | The autonomous system owner of the IP address. |
| IP.Geo.Country | String | The country in which the IP address is located. |
| IP.Registrar.Abuse.Network | String | The network of the contact for reporting abuse. |
| 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. |
| DBotScore.Reliability | String | Reliability of the source providing the intelligence data. |
| TeamCymru.IP.Address | String | The IP address. |
| TeamCymru.IP.ASN | String | The IP ASN. |
| TeamCymru.IP.ASOwner | String | The IP AS owner. |
| TeamCymru.IP.Geo.Country | String | The IP country. |
| TeamCymru.IP.Registrar.Abuse.Network | String | The IP range relevant for abuse inquiries provided for the IP. |
Command example
!ip ip=1.1.1.1
Context Example
{
"DBotScore": {
"Indicator": "1.1.1.1",
"Score": 0,
"Type": "ip",
"Vendor": "TeamCymru"
},
"IP": {
"ASN": "13335",
"ASOwner": "CLOUDFLARENET, US",
"Address": "1.1.1.1",
"Geo": {
"Country": "AU"
},
"Registrar": {
"Abuse": {
"Network": "1.1.1.0/24"
}
}
},
"TeamCymru": {
"IP": {
"ASN": "13335",
"ASOwner": "CLOUDFLARENET, US",
"Address": "1.1.1.1",
"Geo": {
"Country": "AU"
},
"Registrar": {
"Abuse": {
"Network": "1.1.1.0/24"
}
}
}
}
}
Human Readable Output
Team Cymru results for 1.1.1.1
IP ASN Organization Country Range 1.1.1.1 13335 CLOUDFLARENET, US AU 1.1.1.0/24
cymru-bulk-whois
Checks the reputation of a CSV list of IPv4 addresses within a file.
Note: Results for queries exceeding 10,000 IPs may take more than a minute given a moderately sized Internet link.
Base Command
cymru-bulk-whois
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The file’s War Room entry ID. | Required |
| delimiter | Delimiter by which the content of the file is separated. Eg: “ , “ , “ : “, “ ; “. Default is ,. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IP.Address | String | IP address. |
| IP.ASN | String | The autonomous system name for the IP address, for example: “AS8948”. |
| IP.ASOwner | String | The autonomous system owner of the IP address. |
| IP.Geo.Country | String | The country in which the IP address is located. |
| IP.Registrar.Abuse.Network | String | The network of the contact for reporting abuse. |
| 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. |
| DBotScore.Reliability | String | Reliability of the source providing the intelligence data. |
| TeamCymru.IP.Address | String | The IP address. |
| TeamCymru.IP.ASN | String | The IP ASN. |
| TeamCymru.IP.ASOwner | String | The IP AS owner. |
| TeamCymru.IP.Geo.Country | String | The IP country. |
| TeamCymru.IP.Registrar.Abuse.Network | String | The IP range relevant for abuse inquiries provided for the IP. |
Command example
!cymru-bulk-whois entry_id=${File.EntryID}
Troubleshooting
- In case of a problem with the proxy configuration, validate that the given proxy is working with the Whois content pack.
Configuration parameters
insecure— Trust any certificate (not secure)proxy— Use system proxy settingsproxy_url— Proxy URLintegration_reliability— Source Reliability
Commands (2)
-
cymru-bulk-whoisChecks the reputation of a CSV list of IPv4 addresses within a file. Note: Results for queries exceeding 10,000 IPs may take more than a minute given a moderately sized Internet link.
-
ipChecks the reputation of an IP address.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """IMPORTS""" import csv import socket import socks import urllib3 from cymruwhois import Client # Python interface to whois.cymru.com # Disable insecure warnings urllib3.disable_warnings() """GLOBALS""" HEADERS = ["ip", "asn", "owner", "cc", "prefix"] MAPPING = {"ip": "IP", "asn": "ASN", "owner": "Organization", "cc": "Country", "prefix": "Range"} """ CLIENT COMMANDS """ class CymruClient(Client): def _connect(self): # pragma: no coverage demisto.debug("Start connecting...") self.socket = socks.socksocket() self.socket.settimeout(30.0) self.socket.connect((self.host, self.port)) self.socket.settimeout(60.0) self.file = self.socket.makefile("rw") def lookup(self, ip: str) -> dict[str, Any] | None: """Perform lookups by ip address and return ASN, Country Code, and Network Owner. :type ip: ``str`` :param ip: string to add in the dummy dict that is returned :return: Dictionary contains the results of the lookup API call if succeeded, else None :rtype: Dict[str, Any] or None """ raw_result = super().lookup(ip) return vars(raw_result) if raw_result else None def lookupmany_dict(self, bulk: list[str]) -> Optional[dict[str, Any]]: """Perform lookups by bulk of ip addresses, returning a dictionary of ip -> record (ASN, Country Code, and Netblock Owner.) :type bulk: ``list`` :param bulk: list of ip addresses :return: Dictionary contains the results of the lookupmany API call if succeeded, else None :rtype: Dict[str, Dict[str, str]] or None """ raw_result = super().lookupmany_dict(bulk) return {k: vars(raw_result[k]) for k in raw_result} if raw_result else None """ HELPER FUNCTIONS """ def parse_ip_result(ip: str, ip_data: dict[str, str], reliability: str) -> CommandResults: """ Arranges the IP's result from the API to the context format. :param ip: ip address :param ip_data: the ip given data (as returned from the API call) :param reliability: reliability of the source providing the intelligence. :return: commandResult of the given IP """ asn = demisto.get(ip_data, "asn") owner = demisto.get(ip_data, "owner") country = demisto.get(ip_data, "cc") prefix = demisto.get(ip_data, "prefix") entry_context = { "Address": ip, "ASN": asn, "ASOwner": owner, "Geo": {"Country": country}, "Registrar": {"Abuse": {"Network": prefix}}, } indicator = Common.IP( ip=ip, asn=asn, as_owner=owner, geo_country=country, registrar_abuse_network=prefix, dbot_score=Common.DBotScore( indicator=ip, indicator_type=DBotScoreType.IP, score=Common.DBotScore.NONE, reliability=DBotScoreReliability.get_dbot_score_reliability_from_str(reliability), ), ) human_readable = tableToMarkdown( f"Team Cymru results for {ip}", ip_data, HEADERS, headerTransform=lambda header: MAPPING.get(header, header) ) outputs_key_field = "ip" # marks the ip address return CommandResults( readable_output=human_readable, raw_response=ip_data, outputs_prefix="TeamCymru.IP", outputs_key_field=outputs_key_field, indicator=indicator, outputs=entry_context, ) def validate_ip_addresses(ips_list: list[str]) -> tuple[list[str], list[str]]: """ Given a list of IP addresses, returns the invalid and valid ips. :param ips_list: list of ip addresses :return: invalid_ip_addresses, valid_ip_addresses """ invalid_ip_addresses = [] valid_ip_addresses = [] for ip in ips_list: ip = ip.strip().strip('"') if ip: if is_ip_valid(ip): valid_ip_addresses.append(ip) else: invalid_ip_addresses.append(ip) return invalid_ip_addresses, valid_ip_addresses def parse_file(file_path_res: dict[str, str], delimiter: str = ",") -> List[str]: """ Parses the given file line by line to list. :param delimiter: delimiter by which the content of the list is seperated. :param file_path_res: Object contains file ID, path and name :return: bulk list of the elements in the file """ bulk_list = [] with open(file_path_res["path"]) as file: reader = csv.reader(file, delimiter=delimiter, skipinitialspace=True) for row in reader: for col in row: bulk_list += col.split() return bulk_list def parse_ips_list(client: CymruClient, ips_list: list[str], reliability: str) -> list[CommandResults]: """ Creates a commandResults array based on a list of IP addresses, this by calling the relevant functions. :param client: client to use :param ips_list: list of IP addresses :return: CommandResults object """ command_results: list[CommandResults] = [] invalid_ips, valid_ips = validate_ip_addresses(ips_list) if invalid_ips: return_warning( "The following IP Addresses were found invalid: {}".format(", ".join(invalid_ips)), exit=len(invalid_ips) == len(ips_list), ) results = client.lookupmany_dict(valid_ips) if results: for ip, ip_data in results.items(): command_results.append(parse_ip_result(ip, ip_data, reliability)) return command_results """ COMMAND FUNCTIONS """ def test_module(client: CymruClient) -> str: """Tests API connectivity Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises exceptions if something goes wrong. :type client: ``Client`` :param Client: client to use :return: 'ok' if test passed, anything else will fail the test. :rtype: ``str`` """ message: str = "" try: result = client.lookup("8.8.8.8") if result and result.get("owner") == "GOOGLE - Google LLC, US": demisto.info("ok") message = "ok" except DemistoException as e: if "Forbidden" in str(e) or "Authorization" in str(e): message = "Authorization Error: make sure API Key is correctly set" else: raise e return message def ip_command(client: CymruClient, args: dict[str, Any], reliability: str) -> list[CommandResults]: """ Returns the results of 'ip' command :type client: ``Client`` :param Client: client to use :type args: ``Dict[str, Any]`` :param args: All command arguments, the field 'ip' :return: CommandResults object containing the results of the lookup action as returned from the API and its readable output. """ command_results: list[CommandResults] = [] ip = argToList(args.get("ip")) if not ip: raise ValueError("IP not specified") if len(ip) > 1: return parse_ips_list(client, ip, reliability) if len(ip) == 1 and not is_ip_valid(ip[0]): raise ValueError(f"The given IP address: {ip[0]} is not valid") # Call the Client function and get the raw response result = client.lookup(ip[0]) if result: command_results.append(parse_ip_result(ip[0], result, reliability)) return command_results def cymru_bulk_whois_command(client: CymruClient, args: dict[str, Any], reliability: str) -> list[CommandResults]: """ Returns results of 'cymru-bulk-whois' command :type client: ``Client`` :param Client: client to use :type args: ``Dict[str, Any]`` :param args: All command arguments - 'entry_id', 'delimiter' :return: CommandResults object containing the results of the lookup action as returned from the API and its readable output. """ if args.get("entry_id"): demisto.debug("Using the entry_id to find the file's path") file_path = demisto.getFilePath(args.get("entry_id")) if not file_path: raise ValueError("No file was found for given entry_id") ips_list = parse_file(file_path, args.get("delimiter", ",")) else: raise ValueError("No entry_id specified.") return parse_ips_list(client, ips_list, reliability) def setup_proxy(): # pragma: no coverage """ The function is based on setup_proxy() from 'Whois' pack """ scheme_to_proxy_type = { "socks5": [socks.PROXY_TYPE_SOCKS5, False], "socks5h": [socks.PROXY_TYPE_SOCKS5, True], "socks4": [socks.PROXY_TYPE_SOCKS4, False], "socks4a": [socks.PROXY_TYPE_SOCKS4, True], "http": [socks.PROXY_TYPE_HTTP, True], } proxy_url = demisto.params().get("proxy_url") def_scheme = "socks5h" if proxy_url == "system_http" or (not proxy_url and demisto.params().get("proxy")): system_proxy = handle_proxy("proxy") # use system proxy. Prefer https and fallback to http proxy_url = system_proxy.get("https") if system_proxy.get("https") else system_proxy.get("http") def_scheme = "http" if not proxy_url and not demisto.params().get("proxy"): return scheme, host = (def_scheme, proxy_url) if "://" not in proxy_url else proxy_url.split("://") host, port = (host, None) if ":" not in host else host.split(":") if port: port = int(port) proxy_type = scheme_to_proxy_type.get(scheme) if not proxy_type: raise ValueError(f"Un supported proxy scheme: {scheme}") socks.set_default_proxy(proxy_type[0], host, port, proxy_type[1]) socket.socket = socks.socksocket # type: ignore demisto.info("Proxy setup completed successfully.") """ MAIN FUNCTION """ def main() -> None: """ main function, parses params and runs command functions """ demisto.debug(f"Command being called is {demisto.command()}") org_socket = None try: org_socket = socket.socket setup_proxy() client = CymruClient() reliability = demisto.params().get("integration_reliability", "") if demisto.command() == "test-module": result = test_module(client) return_results(result) elif demisto.command() == "ip": return_results(ip_command(client, demisto.args(), reliability)) elif demisto.command() == "cymru-bulk-whois": return_results(cymru_bulk_whois_command(client, demisto.args(), reliability)) else: raise NotImplementedError(f"command {demisto.command()} is not implemented.") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") finally: socks.set_default_proxy() # clear proxy settings socket.socket = org_socket # type: ignore """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()