JARM fingerprint investigations with Censys, Shodan and MISP¶
Introduction¶
- UUID: 2e8acabf-c0a8-4d7c-803b-5d2aa0ea4359
- Started from issue 19
- State: Published : demo version with output
- Purpose: This playbook enables the investigation of JARM fingerprints which you can then use for threat actor infrastructure tracking. It verifies the existence of these fingerprints in MISP events and active OSINT feeds. The playbook then queries Censys and Shodan to identify hosts with services that match the fingerprints. The results are added to a MISP event as MISP objects and event reports. At the conclusion of the playbook, a summary is displayed and shared via Mattermost.
- Tags: [ "jarm", "fingerprint", "tls", "certificate", "censys", "shodan", "investigation", "infrastructure"]
- External resources: Mattermost, Censys, Shodan
- Target audience: CTI
Playbook¶
- JARM fingerprint investigations with Censys, Shodan and MISP
- Introduction
- Preparation
- PR:1 Initialise environment
- PR:2 Set helper variables
- PR:3 What are JARM fingerprints?
- PR:4 Create your own JARM fingerprint
- PR:5 Pivot points
- PR:6 Define JARM fingerprints
- PR:7 Create MISP event
- PR:8 Add JARM fingerprints to MISP
- Correlation
- CR:1 Correlation with MISP events
- CR:2 Correlation with MISP feeds
- Investigation
- IN:1 Investigate with aggregated information from Censys
- IN:2 Investigate with detailed information from Censys
- IN:3 Investigate with Shodan
- IN:4 Convert to MISP objects and build MISP event reports
- IN:5 Create MISP reports
- IN:6 Get the MISP indicators
- Closure
- EN:1 Create the summary of the playbook
- EN:2 Print the summary
- EN:3 Send a summary to Mattermost
- EN:4 End of the playbook
- External references
- Technical details
Preparation¶
PR:1 Initialise environment¶
This section initialises the playbook environment and loads the required Python libraries.
The credentials for MISP (API key) and other services are loaded from the file keys.py
in the directory vault. A PyMISP object is created to interact with MISP and the active MISP server is displayed. By printing out the server name you know that it's possible to connect to MISP. In case of a problem PyMISP will indicate the error with PyMISPError: Unable to connect to MISP
.
The contents of the keys.py
file should contain at least :
misp_url="<MISP URL>" # The URL to our MISP server
misp_key="<MISP API KEY>" # The MISP API key
misp_verifycert=<True or False> # Ignore certificate errors
mattermost_playbook_user="<MATTERMOST USER>"
mattermost_hook="<MATTERMOST WEBHOOK>"
censys_api_id = "<CENSYS_API_ID>" # Censys API information
censys_api_secret = "<CENSYS_API_SECRET>" #
shodan_apikey = "<SHODAN_API_KEY>" # Shodan API information
# Initialise Python environment
import urllib3
import sys
import json
from pyfaup.faup import Faup
from prettytable import PrettyTable, MARKDOWN
from IPython.display import Image, display, display_markdown, HTML
from datetime import date
import requests
import uuid
from uuid import uuid4
from pymisp import *
from pymisp.tools import GenericObjectGenerator
from censys.search import CensysHosts, SearchClient
import shodan
import re
import time
from datetime import datetime
# Load the credentials
sys.path.insert(0, "../vault/")
from keys import *
if misp_verifycert is False:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
print("The \033[92mPython libraries\033[90m are loaded and the \033[92mcredentials\033[90m are read from the keys file.")
# Create the PyMISP object
misp = PyMISP(misp_url, misp_key, misp_verifycert)
misp_headers = {"Authorization": misp_key, "Content-Type": "application/json", "Accept": "application/json"}
print("I will use the MISP server \033[92m{}\033[90m for this playbook.".format(misp_url))
censys_hosts = CensysHosts(censys_api_id, censys_api_secret)
censys_search = SearchClient(censys_api_id, censys_api_secret)
print("Created \033[92mCensys\033[90m host and search object.")
shodan_api = shodan.Shodan(shodan_apikey)
print("Created \033[92mShodan\033[90m object.")
The Python libraries are loaded and the credentials are read from the keys file.
The version of PyMISP recommended by the MISP instance (2.4.194) is newer than the one you're using now (2.4.184.3). Please upgrade PyMISP.
I will use the MISP server https://misp.demo.cudeso.be/ for this playbook. Created Censys host and search object. Created Shodan object.
PR:2 Set helper variables¶
This cell contains helper variables that are used in this playbook. Their usage is explained in the next steps of the playbook.
playbook_config
: the configuration of the playbookplaybook_results
: the results of the playbook
playbook_config = {"correlation_published": True,
"correlation_limit": 1000,
"limit_country_code": "RU",
"limit_asn": "49505", #False,
"censys_aggr_num_buckets": 5,
"censys_results_per_page": 10
}
playbook_results = {"event": None,
"eventid": 0,
}
PR:3 What are JARM fingerprints?¶
JARM is a method of actively fingerprinting TLS servers, developed by Salesforce Engineering. It works by sending 10 TLS Client Hello packets to a server and analyzing specific attributes of the responses. These attributes are then aggregated and hashed into a unique JARM fingerprint, which is a 62-character string:
- The first 30 characters represent the ciphers and TLS version chosen by the server.
- The remaining 32 characters are based on the extensions sent by the server.
It's important to understand that JARM does not identify whether a server is malicious or not; it simply identifies if a server's configuration matches a specific fingerprint. This makes JARM a powerful tool for tracking overlaps in infrastructure used by threat actors.
PR:4 Create your own JARM fingerprint¶
You can use JARM fingerprints shared by the community or create your own. Note that creating a JARM fingerprint involves an active scan of a system, which may or may not be desirable depending on the actor you're tracking. You can create your own fingerprint using PyJARM.
$ pyjarm google.com
Target: google.com:443
JARM: 27d40d40d29d40d1dc42d43d00041d4689ee210389f4f6b4b5b1b93f92252d
PR:5 Pivot points¶
JARM fingerprints serve as excellent pivot points when investigating and tracking adversary infrastructure. For additional useful pivot points in your investigation, consider exploring Pivot Atlas.
PR:6 Define JARM fingerprints¶
In this playbook you have to provide the fingerprints in the variable jarm_fingerprints
. It can either be a list or a single variable, the playbook will convert it to a list anyway.
jarm_fingerprints = ["20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6", # EvilGinx2
"28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4"] # SocGholish?
# Always make sure jarm_fingerprint is a list
if not isinstance(jarm_fingerprints, list):
jarm_fingerprints = [jarm_fingerprints]
# Initialise the variables
for jarm_fingerprint in jarm_fingerprints:
playbook_results[jarm_fingerprint] = {}
playbook_results[jarm_fingerprint]["related_events"] = []
playbook_results[jarm_fingerprint]["related_feeds"] = []
playbook_results[jarm_fingerprint]["censys_detailed_results"] = []
playbook_results[jarm_fingerprint]["shodan_detailed_results"] = []
playbook_results[jarm_fingerprint]["aggr_md_report"] = []
playbook_results[jarm_fingerprint]["md_report"] = []
playbook_results[jarm_fingerprint]["country_list"] = []
playbook_results[jarm_fingerprint]["asn_list"] = []
playbook_results[jarm_fingerprint]["ip"] = {}
print("Continue the playbook with the investigation for the JARM fingerprint(s): \033[92m{}\033[90m.".format(jarm_fingerprints))
Continue the playbook with the investigation for the JARM fingerprint(s): ['20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6', '28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4'].
PR:7 Create MISP event¶
The next cell creates the MISP event and stores the reference to the event in the variable playbook_results["event"]
. Note that instead of creating a new event, you can also reference an existing MISP event (with its UUID, and in this case, also update the eventid with the Event ID).
# Create the PyMISP object for an event
event = MISPEvent()
more_fingerprints = ""
if len(jarm_fingerprints) > 1:
more_fingerprints = " and more ..."
event.info = "JARM investigation - {}{}".format(jarm_fingerprints[0], more_fingerprints)
event.distribution = Distribution.your_organisation_only
event.threat_level_id = ThreatLevel.low
event.analysis = Analysis.ongoing
event.set_date(date.today())
# Create the MISP event on the server side
misp_event = misp.add_event(event, pythonify=True)
playbook_results["event"] = misp_event.uuid
playbook_results["eventid"] = misp_event.id
# Add default tags for the event
misp.tag(playbook_results["event"], "tlp:amber")
misp.tag(playbook_results["event"], "workflow:state=\"incomplete\"", local=True)
misp.tag(playbook_results["event"], "cycat:type=\"fingerprint\"", local=True)
print("Continue the playbook with the new MISP event ID \033[92m{}\033[90m with title \033[92m{}\033[90m and UUID \033[92m{}\033[90m.".format(misp_event.id, misp_event.info, playbook_results["event"]))
Continue the playbook with the new MISP event ID 3485 with title JARM investigation - 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 and more ... and UUID d63980f9-031a-43e0-93c4-b64f3dde0663.
print("Adding JARM fingerprints as MISP objects.")
for jarm_fingerprint in jarm_fingerprints:
jarm_object = MISPObject("jarm")
jarm_object.add_attribute("jarm", jarm_fingerprint, comment="From playbook")
playbook_results[jarm_fingerprint]["object"] = misp.add_object(playbook_results["event"], jarm_object, pythonify=True)
print(" Added JARM object with UUID \033[92m{}\033[90m".format(playbook_results[jarm_fingerprint]["object"].uuid))
Adding JARM fingerprints as MISP objects. Added JARM object with UUID 40d1e6a9-2d92-4c90-81e9-b08ec9dcd2be Added JARM object with UUID ad5549c1-0ba5-4162-9f3d-239e6dfca9e2
Correlation¶
CR:1 Correlation with MISP events¶
When the fingerprint is added to MISP it will immediately show the related events and OSINT feed matches in the web interface. We also want that information to be included in the playbook results and summary.
Only published events (correlation_published
) are take into account. There is a default limit of 1000 hits (correlation_limit
).
# Code block to query MISP and find the correlations
for jarm_fingerprint in jarm_fingerprints:
search_match = misp.search("events", value=jarm_fingerprint, published=playbook_config["correlation_published"],
limit=playbook_config["correlation_limit"], pythonify=True)
if len(search_match) > 0:
for event in search_match:
if event.uuid != playbook_results["event"]: # Skip the event we just created for this playbook
print("Found match for {} in \033[92m{}\033[90m in \033[92m{}\033[90m".format(jarm_fingerprint, event.id, event.info))
entry = {"source": "MISP", "org": event.org.name, "event_id": event.id, "event_info": event.info,
"date": event.date, "fingerprint": jarm_fingerprint}
playbook_results[jarm_fingerprint]["related_events"].append(entry)
else:
print("\033[93mNo correlating MISP events\033[90m found for {}.".format(jarm_fingerprint))
print("Finished correlating with MISP events.\n\n")
Found match for 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 in 2391 in C2-JARM - A list of JARM hashes for different ssl implementations used by some C2 tools. No correlating MISP events found for 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4. Finished correlating with MISP events.
MISP events correlation table¶
The correlation results are now stored in playbook_results
. Execute the next cell to display them in a table format. The table is also included in the summary for Mattermost.
# Put the correlations in a pretty table. We can use this table later also for the summary
table = PrettyTable()
table.field_names = ["Source", "Value", "Event", "Event ID"]
table.align["Value"] = "l"
table.align["Event"] = "l"
table.align["Event ID"] = "l"
table._max_width = {"Event": 50}
for jarm_fingerprint in jarm_fingerprints:
for match in playbook_results[jarm_fingerprint]["related_events"]:
table.add_row([match["source"], jarm_fingerprint, match["event_info"], match["event_id"]])
print(table.get_string(sortby="Value"))
table_mispevents = table
+--------+----------------------------------------------------------------+----------------------------------------------------+----------+ | Source | Value | Event | Event ID | +--------+----------------------------------------------------------------+----------------------------------------------------+----------+ | MISP | 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 | C2-JARM - A list of JARM hashes for different ssl | 2391 | | | | implementations used by some C2 tools. | | +--------+----------------------------------------------------------------+----------------------------------------------------+----------+
CR:2 Correlation with MISP feeds¶
Search the MISP feeds for events that match with one of the fingerprints you specified earlier. The results highly depend on the feeds you have enabled.
print("Search in MISP feeds.")
misp_cache_url = "{}/feeds/searchCaches/".format(misp_url)
match = False
for jarm_fingerprint in jarm_fingerprints:
# Instead of GET, use POST (https://github.com/MISP/MISP/issues/7478)
cache_results = requests.post(misp_cache_url, headers=misp_headers, verify=misp_verifycert, json={"value": jarm_fingerprint})
for result in cache_results.json():
if "Feed" in result:
match = True
print(" Found \033[92m{}\033[90m in \033[92m{}\033[90m.".format(jarm_fingerprint, result["Feed"]["name"]))
for match in result["Feed"]["direct_urls"]:
entry = {"source": "Feeds", "feed_name": result["Feed"]["name"], "match_url": match["url"], "fingerprint": jarm_fingerprint}
playbook_results[jarm_fingerprint]["related_feeds"].append(entry)
print("Finished searching in MISP feeds.")
if not match:
print("\033[93mNo correlating information found in MISP feeds.")
Search in MISP feeds. Found 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 in CIRCL OSINT Feed. Finished searching in MISP feeds.
MISP feed correlations table¶
The correlation results are now stored in playbook_results
. Execute the next cell to display them in a table format. The table is also included in the summary for Mattermost.
# Put the correlations in a pretty table. We can use this table later also for the summary
table = PrettyTable()
table.field_names = ["Source", "Value", "Feed", "Feed URL"]
table.align["Value"] = "l"
table.align["Feed"] = "l"
table.align["Feed URL"] = "l"
table._max_width = {"Event": 50}
for jarm_fingerprint in jarm_fingerprints:
for match in playbook_results[jarm_fingerprint]["related_feeds"]:
table.add_row([match["source"], jarm_fingerprint, match["feed_name"], match["match_url"]])
print(table.get_string(sortby="Value"))
table_mispfeeds = table
+--------+----------------------------------------------------------------+------------------+---------------------------------------------------------------------------------------+ | Source | Value | Feed | Feed URL | +--------+----------------------------------------------------------------+------------------+---------------------------------------------------------------------------------------+ | Feeds | 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 | CIRCL OSINT Feed | https://misp.demo.cudeso.be/feeds/previewEvent/1/4ce77fdd-19a8-4037-ac75-4ece0c05d63f | +--------+----------------------------------------------------------------+------------------+---------------------------------------------------------------------------------------+
Investigate¶
IN:1 Investigate with aggregated information from Censys¶
Next, we use Censys to identify hosts related to the JARM fingerprint.
Censys will return the hosts that have at least one service running with a corresponding JARM fingerprint. Unfortunately, the API does not immediately highlight which service has the corresponding fingerprint. Nevertheless, the exposed services, and especially the associated hostnames, are useful pivot points.
We first query Censys for aggregated information, and then we do a second query for more detailed information.
In this playbook we focus our investigation of the infrastructure on
- A specific country, defined in
playbook_config["limit_country_code"]
, and - A specific ASN network, defined in
playbook_config["limit_asn"]
.
print("Querying Censys for aggregated information.")
for jarm_fingerprint in jarm_fingerprints:
summary_iv = ""
print("Working with \033[92m{}\033[90m.".format(jarm_fingerprint))
summary_iv += "## {}\n\n".format(jarm_fingerprint)
# Get total results
playbook_results[jarm_fingerprint]["censys_aggr"] = censys_search.v2.hosts.aggregate(
f"services.jarm.fingerprint: {jarm_fingerprint}", "services.port", num_buckets=playbook_config["censys_aggr_num_buckets"])
if "total" in playbook_results[jarm_fingerprint]["censys_aggr"]:
print(" Got \033[92m{}\033[90m results.".format(playbook_results[jarm_fingerprint]["censys_aggr"]["total"]))
summary_iv += "Received **{}** results\n\n### Ports\n".format(playbook_results[jarm_fingerprint]["censys_aggr"]["total"])
for bucket in playbook_results[jarm_fingerprint]["censys_aggr"]["buckets"]:
print(" Port: {}: {}".format(bucket["key"], bucket["count"]))
summary_iv += "- **{}**: {} records\n".format(bucket["key"], bucket["count"])
summary_iv += "\n"
# Country code
if playbook_config["limit_country_code"]:
print(" Aggregating on country_code \033[92m{}\033[90m.".format(playbook_config["limit_country_code"]))
summary_iv += "## {} aggregated on country code {}\n\n".format(jarm_fingerprint, playbook_config["limit_country_code"])
censys_aggregate_filter = "(services.jarm.fingerprint: {}) AND (location.country_code: {})".format(jarm_fingerprint, playbook_config["limit_country_code"])
summary_iv += "Filter: `{}`\n\n".format(censys_aggregate_filter)
playbook_results[jarm_fingerprint]["censys_aggr_country"] = censys_search.v2.hosts.aggregate(
censys_aggregate_filter, "services.port", num_buckets=playbook_config["censys_aggr_num_buckets"])
print(" Got \033[92m{}\033[90m results.".format(playbook_results[jarm_fingerprint]["censys_aggr_country"]["total"]))
summary_iv += "Received **{}** results\n\n### Ports\n".format(playbook_results[jarm_fingerprint]["censys_aggr_country"]["total"])
for bucket in playbook_results[jarm_fingerprint]["censys_aggr_country"]["buckets"]:
print(" Port: {}: {}".format(bucket["key"], bucket["count"]))
summary_iv += "- **{}**: {} records\n".format(bucket["key"], bucket["count"])
summary_iv += "\n"
# ASN
if playbook_config["limit_asn"]:
print(" Aggregating on ASN \033[92m{}\033[90m.".format(playbook_config["limit_asn"]))
summary_iv += "## {} aggregated on ASN {}\n\n".format(jarm_fingerprint, playbook_config["limit_asn"])
censys_aggregate_filter = "(services.jarm.fingerprint: {}) AND (autonomous_system.asn: {})".format(jarm_fingerprint, playbook_config["limit_asn"])
summary_iv += "Filter: `{}`\n\n".format(censys_aggregate_filter)
playbook_results[jarm_fingerprint]["limit_asn"] = censys_search.v2.hosts.aggregate(
censys_aggregate_filter, "services.port", num_buckets=playbook_config["censys_aggr_num_buckets"])
print(" Got \033[92m{}\033[90m results.".format(playbook_results[jarm_fingerprint]["limit_asn"]["total"]))
summary_iv += "Received **{}** results\n\n### Ports\n".format(playbook_results[jarm_fingerprint]["limit_asn"]["total"])
for bucket in playbook_results[jarm_fingerprint]["limit_asn"]["buckets"]:
print(" Port: {}: {}".format(bucket["key"], bucket["count"]))
summary_iv += "- **{}**: {} records\n".format(bucket["key"], bucket["count"])
summary_iv += "\n"
playbook_results[jarm_fingerprint]["censys_aggr_md_report"] = summary_iv
print("Finished Censys.\n")
Querying Censys for aggregated information. Working with 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6. Got 21528 results. Port: 443: 948 Port: 80: 942 Port: 22: 760 Port: 33939: 471 Port: 18081: 463 Aggregating on country_code RU. Got 183 results. Port: 80: 13 Port: 443: 11 Port: 22: 5 Port: 8080: 4 Port: 2000: 3 Aggregating on ASN 49505. Got 67 results. Port: 80: 4 Port: 443: 4 Port: 22: 1 Port: 81: 1 Port: 88: 1 Working with 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4. Got 18548 results. Port: 443: 4068 Port: 80: 3656 Port: 22: 2208 Port: 25: 351 Port: 993: 288 Aggregating on country_code RU. Got 1165 results. Port: 443: 246 Port: 80: 202 Port: 22: 118 Port: 123: 39 Port: 25: 26 Aggregating on ASN 49505. Got 46 results. Port: 443: 8 Port: 80: 7 Port: 22: 2 Port: 123: 2 Port: 20779: 2 Finished Censys.
IN:2 Investigate with detailed information from Censys¶
The second step of the investigation involves querying detailed information for hosts identified by Censys as having the matching JARM fingerprint. Note that this detailed investigation returns all exposed services, regardless of their association with the JARM fingerprint. As an analyst, you will need to manually review which services match. The investigation results from Shodan can assist you in this step.
print("Querying Censys for detailed information.")
for jarm_fingerprint in jarm_fingerprints:
print("Working with \033[92m{}\033[90m.".format(jarm_fingerprint))
censys_filters = []
if playbook_config["limit_country_code"]:
censys_filters.append("(location.country_code: {})".format(playbook_config["limit_country_code"]))
if playbook_config["limit_asn"]:
censys_filters.append("(autonomous_system.asn: {})".format(playbook_config["limit_asn"]))
if censys_filters:
censys_detailed_filter = "(services.jarm.fingerprint: {}) AND ({})".format(
jarm_fingerprint, " AND ".join(censys_filters))
else:
censys_detailed_filter = "(services.jarm.fingerprint: {})".format(jarm_fingerprint)
print(" Censys JARM query: {}".format(censys_detailed_filter))
censys_query = censys_hosts.search(censys_detailed_filter, per_page=playbook_config["censys_results_per_page"])
for result in censys_query():
entry = {"ip": result.get("ip", ""),
"country_code": result.get("location", {}).get("country_code", ""),
"country": result.get("location", {}).get("country", ""),
"asn": result.get("autonomous_system", []).get("asn", 0),
"last_updated_at": result.get("last_updated_at", ""),
"dns": result.get("dns", {}).get("reverse_dns", []),
"services": result.get("services", {})}
playbook_results[jarm_fingerprint]["censys_detailed_results"].append(entry)
print(" Got entry at \033[92m{}\033[90m, last updated at {}".format(entry["ip"], entry["last_updated_at"]))
print("\nFinished Censys.\n")
Querying Censys for detailed information. Working with 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6. Censys JARM query: (services.jarm.fingerprint: 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6) AND ((location.country_code: RU) AND (autonomous_system.asn: 49505)) Got entry at 84.38.187.104, last updated at 2024-07-08T23:35:44.740Z Got entry at 95.213.131.34, last updated at 2024-07-04T13:43:08.256Z Got entry at 95.213.191.237, last updated at 2024-07-09T00:45:52.552Z Got entry at 188.68.210.86, last updated at 2024-07-08T23:39:11.196Z Working with 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4. Censys JARM query: (services.jarm.fingerprint: 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4) AND ((location.country_code: RU) AND (autonomous_system.asn: 49505)) Got entry at 92.53.65.163, last updated at 2024-07-09T03:24:23.926Z Got entry at 45.9.91.91, last updated at 2024-07-02T10:21:56.472Z Got entry at 188.124.50.172, last updated at 2024-07-09T02:06:09.781Z Got entry at 2a00:ab00:1103:23:0:0:0:51, last updated at 2024-07-08T19:07:12.300Z Got entry at 188.68.215.82, last updated at 2024-07-09T01:29:25.189Z Got entry at 45.9.91.87, last updated at 2024-07-04T15:33:43.519Z Got entry at 94.26.229.226, last updated at 2024-07-09T04:08:04.778Z Got entry at 188.68.218.199, last updated at 2024-07-08T17:50:13.374Z Finished Censys.
IN:3 Investigate with Shodan¶
We also query Shodan using a similar query. Unlike Censys, the Shodan API highlights the service associated with the JARM fingerprint. This information is included in the MISP objects and event report. However, the Shodan query only returns services matching the fingerprint, excluding any other exposed services on the host.
print("Query Shodan.")
for jarm_fingerprint in jarm_fingerprints:
shodan_country_filter = ""
shodan_asn_filter = ""
if playbook_config["limit_country_code"]:
shodan_country_filter = "+country:{}".format(playbook_config["limit_country_code"])
if playbook_config["limit_asn"]:
shodan_asn_filter = "+asn:as{}".format(playbook_config["limit_asn"])
shodan_filter = "ssl.jarm:{}{}{}".format(jarm_fingerprint, shodan_country_filter, shodan_asn_filter)
print("Working with \033[92m{}\033[90m.".format(jarm_fingerprint))
print(" Shodan JARM query: {}".format(shodan_filter))
results = shodan_api.search(shodan_filter)
observed_ips = []
observed_hostnames = []
for result in results['matches']:
dns = []
hostnames = result.get("hostnames", [])
for hostname in hostnames:
if hostname not in dns:
dns.append(hostname)
domains = result.get("hostnames", [])
for domain in domains:
if domain not in dns:
dns.append(domain)
entry = {"ip": result.get("ip_str", ""),
"last_update": result.get("last_update", ""),
"asn": result.get("asn", ""),
"country": result.get("country_name", ""),
"dns": dns,
"service": result.get("port", "")}
observed_ips.append(entry["ip"])
observed_hostnames = observed_hostnames + dns
playbook_results[jarm_fingerprint]["shodan_detailed_results"].append(entry)
print(" Got entry at \033[92m{}\033[90m".format(entry["ip"]))
summary_iv = playbook_results[jarm_fingerprint]["censys_aggr_md_report"]
summary_iv += "## Shodan investigation\n"
summary_iv += "Filter: `{}`\n\n".format(shodan_filter)
summary_iv += "- Received **{}** results.\n".format(len(results['matches']))
summary_iv += "- Observed IPs: **{}**\n".format(list(set(observed_ips)))
summary_iv += "- Observed hostnames: **{}**\n".format(list(set(observed_hostnames)))
summary_iv += "\n\n"
playbook_results[jarm_fingerprint]["aggr_md_report"] = summary_iv
print("\nFinished Shodan.\n")
Query Shodan. Working with 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6. Shodan JARM query: ssl.jarm:20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6+country:RU+asn:as49505 Got entry at 95.213.191.237 Working with 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4. Shodan JARM query: ssl.jarm:28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4+country:RU+asn:as49505 Got entry at 45.9.91.91 Got entry at 94.26.229.226 Got entry at 188.124.50.172 Got entry at 45.9.91.87 Got entry at 185.17.9.5 Got entry at 5.182.4.217 Got entry at 212.41.9.250 Got entry at 188.68.218.199 Got entry at 89.248.193.240 Got entry at 5.188.158.37 Got entry at 188.68.215.82 Got entry at 92.53.65.163 Got entry at 185.184.55.41 Got entry at 45.12.231.112 Finished Shodan.
IN:4 Convert to MISP objects and build MISP event reports¶
With all the information gathered from external providers, we convert it into MISP objects. These objects are then linked to the existing JARM MISP objects. Additionally, we build MISP event reports. These reports facilitate sharing clear and readable information with our peers.
print("Building MISP objects and event reports.")
for jarm_fingerprint in jarm_fingerprints:
summary_iv = "## {}\n\n".format(jarm_fingerprint)
censys_detailed_results = playbook_results[jarm_fingerprint]["censys_detailed_results"]
jarm_fingerprint_object = misp.get_object(playbook_results[jarm_fingerprint]["object"].uuid, pythonify=True)
for result in censys_detailed_results:
summary_iv += "### {}\n".format(result["ip"])
summary_iv += "- Last updated by Censys: {}\n".format(result["last_updated_at"])
summary_iv += "- Censys results are capped at {} entries.\n".format(playbook_config["censys_results_per_page"])
summary_iv += "- Location: **{}** {}\n".format(result["country"], result["country_code"])
summary_iv += "- ASN: **{}**\n".format(result["asn"])
local_hostnames = []
local_hostnames_md = ""
local_services = []
local_services_md = ""
ip_port = MISPObject("ip-port")
ip_port.add_attribute("ip", result["ip"])
for service in result["services"]:
ip_port.add_attribute("src-port", service["port"])
if service["port"] not in local_services:
local_services.append(service["port"])
local_services_md += " - **{}** ({}, from Censys)\n".format(service["port"], service["service_name"])
if "names" in result["dns"] and len(result["dns"]["names"]) > 0:
for name in result["dns"]["names"]:
ip_port.add_attribute("hostname", name)
if name not in local_hostnames:
local_hostnames.append(name)
local_hostnames_md += " - **{}** (from Censys)\n".format(name)
# Check if there are results from Shodan
for shodan in playbook_results[jarm_fingerprint]["shodan_detailed_results"]:
if shodan["ip"] == result["ip"]:
for name in shodan["dns"]:
if name not in local_hostnames:
ip_port.add_attribute("hostname", name, comment="From Shodan")
local_hostnames.append(name)
local_hostnames_md += " - **{}** (from Shodan)\n".format(name)
# Always add the service to MISP as this is where Shodan matches on
ip_port.add_attribute("src-port", shodan["service"], comment="From Shodan (matching JARM)")
if shodan["service"] not in local_services:
local_services.append(shodan["service"])
local_services_md += " - **{}** (matching JARM, from Shodan)\n".format(shodan["service"])
created_object = misp.add_object(playbook_results["event"], ip_port, pythonify=True)
playbook_results[jarm_fingerprint]["ip"][result["ip"]] = created_object.uuid
misp.add_object_reference(jarm_fingerprint_object.add_reference(ip_port.uuid, "related-to"))
print(" Added ip-port object for {}".format(result["ip"]))
summary_iv += "\n#### Hostnames\n"
summary_iv += local_hostnames_md
summary_iv += "\n\n"
summary_iv += "\n#### Ports\n"
summary_iv += local_services_md
summary_iv += "\n\n"
if result["asn"] > 0 and result["asn"] not in playbook_results[jarm_fingerprint]["asn_list"]:
asn_object = MISPObject("asn")
asn_object.add_attribute("asn", result["asn"])
created_object = misp.add_object(playbook_results["event"], asn_object, pythonify=True)
misp.add_object_reference(jarm_fingerprint_object.add_reference(asn_object.uuid, "located-at"))
playbook_results[jarm_fingerprint]["asn_list"].append(result["asn"])
print(" Added ASN object for {}".format(result["asn"]))
if len(result["country"]) > 0 and len(result["country_code"]) and result["country"] not in playbook_results[jarm_fingerprint]["country_list"]:
geolocation_object = MISPObject("geolocation")
geolocation_object.add_attribute("country", result["country"])
geolocation_object.add_attribute("countrycode", result["country_code"])
created_object = misp.add_object(playbook_results["event"], geolocation_object, pythonify=True)
misp.add_object_reference(jarm_fingerprint_object.add_reference(geolocation_object.uuid, "located-at"))
playbook_results[jarm_fingerprint]["country_list"].append(result["country"])
print(" Added geolocation object for {}".format(result["country"]))
playbook_results[jarm_fingerprint]["md_report"] = summary_iv
print("Finished.\n")
Building MISP objects and event reports. Added ip-port object for 84.38.187.104 Added ASN object for 49505 Added geolocation object for Russia Added ip-port object for 95.213.131.34 Added ip-port object for 95.213.191.237 Added ip-port object for 188.68.210.86 Added ip-port object for 92.53.65.163 Added ASN object for 49505 Added geolocation object for Russia Added ip-port object for 45.9.91.91 Added ip-port object for 188.124.50.172 Added ip-port object for 2a00:ab00:1103:23:0:0:0:51 Added ip-port object for 188.68.215.82 Added ip-port object for 45.9.91.87 Added ip-port object for 94.26.229.226 Added ip-port object for 188.68.218.199 Finished.
IN:5 Create MISP reports¶
After creating the MISP reports, it's time to add them to our MISP event.
print("Creating MISP event reports.")
for jarm_fingerprint in jarm_fingerprints:
summary_iv = playbook_results[jarm_fingerprint]["aggr_md_report"]
event_title = "Aggregated investigation for {}".format(jarm_fingerprint)
print(" MISP report \033[92m{}\033[90m".format(event_title))
chunk_size = 61500
for i in range(0, len(summary_iv), chunk_size):
chunk = summary_iv[i:i + chunk_size]
event_report = MISPEventReport()
event_title_edit = event_title
if i > 0:
event_title_edit = "{} ({} > {})".format(event_title, i, i + chunk_size)
event_report.name = event_title_edit
event_report.content = chunk
result = misp.add_event_report(playbook_results["eventid"], event_report)
if "EventReport" in result:
print(" Report ID: \033[92m{}\033[90m".format(result.get("EventReport", []).get("id", 0)))
else:
print(" Failed to create report for \033[91m{}\033[90m.".format(event_title))
summary_iv = playbook_results[jarm_fingerprint]["md_report"]
event_title = "Detailed investigation for {}".format(jarm_fingerprint)
print(" MISP report \033[92m{}\033[90m".format(event_title))
chunk_size = 61500
for i in range(0, len(summary_iv), chunk_size):
chunk = summary_iv[i:i + chunk_size]
event_report = MISPEventReport()
event_title_edit = event_title
if i > 0:
event_title_edit = "{} ({} > {})".format(event_title, i, i + chunk_size)
event_report.name = event_title_edit
event_report.content = chunk
result = misp.add_event_report(playbook_results["eventid"], event_report)
if "EventReport" in result:
print(" Report ID: \033[92m{}\033[90m".format(result.get("EventReport", []).get("id", 0)))
else:
print(" Failed to create report for \033[91m{}\033[90m.".format(event_title))
print("Finished.\n")
Creating MISP event reports. MISP report Aggregated investigation for 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 Report ID: 891 MISP report Detailed investigation for 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 Report ID: 892 MISP report Aggregated investigation for 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 Report ID: 893 MISP report Detailed investigation for 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 Report ID: 894 Finished.
IN:6 Get the MISP indicators¶
As one of the final steps in our investigation, we list the indicators obtained during the JARM fingerprint analysis. Note that these indicators should not be used immediately for blocking or logging purposes but serve as excellent sources for further pivoting.
print("Searching for MISP indicators in event {} - {}.".format(playbook_results["event"], playbook_results["eventid"]))
misp_indicators = misp.search("attributes", eventid=playbook_results["eventid"], to_ids=1, pythonify=True)
result = {}
if len(misp_indicators) > 0:
for indicator in misp_indicators:
z = indicator.type
if indicator.type == "jarm-fingerprint":
continue
if not indicator.type in result:
result[indicator.type] = []
if indicator.value not in result[indicator.type]:
result[indicator.type].append(indicator.value)
print(" Indicator \033[92m{}\033[90m ({})".format(indicator.value, indicator.type))
for indicator_type in result:
result[indicator_type] = sorted(result[indicator_type])
print("Finished searching.\n")
table_mispindicators = ""
if len(result) > 0:
# Put the indicators in a pretty table. We can use this table later also for the summary
table = PrettyTable()
table.field_names = ["Event ID", "Value", "Type"]
table.align["Value"] = "l"
table.align["Type"] = "l"
for key in result:
for value in result[key]:
table.add_row([playbook_results["eventid"], value, key])
print(table.get_string(sortby="Type"))
table_mispindicators = table
Searching for MISP indicators in event d63980f9-031a-43e0-93c4-b64f3dde0663 - 3485. Indicator 84.38.187.104 (ip-dst) Indicator 95.213.131.34 (ip-dst) Indicator master.broker18.ru (hostname) Indicator 95.213.191.237 (ip-dst) Indicator 188.68.210.86 (ip-dst) Indicator teescustoms.com (hostname) Indicator 92.53.65.163 (ip-dst) Indicator api.afdev.ru (hostname) Indicator 45.9.91.91 (ip-dst) Indicator multigo.ru (hostname) Indicator 188.124.50.172 (ip-dst) Indicator scm.miningelement.com (hostname) Indicator 2a00:ab00:1103:23::51 (ip-dst) Indicator 188.68.215.82 (ip-dst) Indicator test.keng.ru (hostname) Indicator test-sale.keng.ru (hostname) Indicator dev-m.keng.ru (hostname) Indicator stage-m.keng.ru (hostname) Indicator stage-sale.keng.ru (hostname) Indicator stage.keng.ru (hostname) Indicator external-m.keng.ru (hostname) Indicator external-sale.keng.ru (hostname) Indicator dev.keng.ru (hostname) Indicator test-m.keng.ru (hostname) Indicator external.keng.ru (hostname) Indicator dev-sale.keng.ru (hostname) Indicator 45.9.91.87 (ip-dst) Indicator 94.26.229.226 (ip-dst) Indicator anticor.ooosis.ru (hostname) Indicator 188.68.218.199 (ip-dst) Indicator pages.core.fugatabula.com (hostname) Indicator core.fugatabula.com (hostname) Finished searching. +----------+---------------------------+----------+ | Event ID | Value | Type | +----------+---------------------------+----------+ | 3485 | anticor.ooosis.ru | hostname | | 3485 | api.afdev.ru | hostname | | 3485 | core.fugatabula.com | hostname | | 3485 | dev-m.keng.ru | hostname | | 3485 | dev-sale.keng.ru | hostname | | 3485 | dev.keng.ru | hostname | | 3485 | external-m.keng.ru | hostname | | 3485 | external-sale.keng.ru | hostname | | 3485 | external.keng.ru | hostname | | 3485 | master.broker18.ru | hostname | | 3485 | multigo.ru | hostname | | 3485 | pages.core.fugatabula.com | hostname | | 3485 | scm.miningelement.com | hostname | | 3485 | stage-m.keng.ru | hostname | | 3485 | stage-sale.keng.ru | hostname | | 3485 | stage.keng.ru | hostname | | 3485 | teescustoms.com | hostname | | 3485 | test-m.keng.ru | hostname | | 3485 | test-sale.keng.ru | hostname | | 3485 | test.keng.ru | hostname | | 3485 | 188.124.50.172 | ip-dst | | 3485 | 188.68.210.86 | ip-dst | | 3485 | 188.68.215.82 | ip-dst | | 3485 | 188.68.218.199 | ip-dst | | 3485 | 2a00:ab00:1103:23::51 | ip-dst | | 3485 | 45.9.91.87 | ip-dst | | 3485 | 45.9.91.91 | ip-dst | | 3485 | 84.38.187.104 | ip-dst | | 3485 | 92.53.65.163 | ip-dst | | 3485 | 94.26.229.226 | ip-dst | | 3485 | 95.213.131.34 | ip-dst | | 3485 | 95.213.191.237 | ip-dst | +----------+---------------------------+----------+
Closure¶
In this closure or end step we create a summary of the actions that were performed by the playbook. The summary is printed and can also be send to a chat channel.
EN:1 Create the summary of the playbook¶
The next section creates a summary and stores the output in the variable summary
in Markdown format. It also stores an intro text in the variable intro
. These variables can later be used when sending information to Mattermost or TheHive.
summary = "# MISP Playbook summary\nJARM fingerprint investigations with Censys, Shodan and MISP \n\n"
current_date = datetime.now()
formatted_date = current_date.strftime("%Y-%m-%d")
summary += "## Overview\n\n"
summary += "This concerned the investigation of JARM fingerprints **{}**\n\n".format(",".join(jarm_fingerprints))
summary += "- Date: **{}**\n".format(formatted_date)
summary += "- Event: **{}** - UUID: {}\n".format(playbook_results["eventid"], playbook_results["event"])
summary += "\n\n"
summary += "## Aggregated reports\n\n"
for jarm_fingerprint in jarm_fingerprints:
summary += "{}\n\n".format(playbook_results[jarm_fingerprint]["aggr_md_report"])
summary += "\n\n"
summary += "## Detailed investigation reports\n\n"
for jarm_fingerprint in jarm_fingerprints:
summary += "{}\n\n".format(playbook_results[jarm_fingerprint]["md_report"])
summary += "\n\n"
summary += "## MISP correlations\n\n"
summary += "### Events\n\n"
table_mispevents.set_style(MARKDOWN)
summary += table_mispevents.get_string(sortby="Value")
summary += "\n\n"
summary += "### OSINT feeds\n\n"
table_mispfeeds.set_style(MARKDOWN)
summary += table_mispfeeds.get_string(sortby="Value")
summary += "\n\n"
summary += "## MISP indicators\n\n"
table_mispindicators.set_style(MARKDOWN)
summary += table_mispindicators.get_string(sortby="Type")
summary += "\n\n"
print("The \033[92msummary\033[90m of the playbook is available.\n")
The summary of the playbook is available.
EN:2 Print the summary¶
print(summary)
# Or print with parsed markdown
#display_markdown(summary, raw=True)
# MISP Playbook summary JARM fingerprint investigations with Censys, Shodan and MISP ## Overview This concerned the investigation of JARM fingerprints **20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6,28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4** - Date: **2024-07-09** - Event: **3485** - UUID: d63980f9-031a-43e0-93c4-b64f3dde0663 ## Aggregated reports ## 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 Received **21528** results ### Ports - **443**: 948 records - **80**: 942 records - **22**: 760 records - **33939**: 471 records - **18081**: 463 records ## 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 aggregated on country code RU Filter: `(services.jarm.fingerprint: 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6) AND (location.country_code: RU)` Received **183** results ### Ports - **80**: 13 records - **443**: 11 records - **22**: 5 records - **8080**: 4 records - **2000**: 3 records ## 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 aggregated on ASN 49505 Filter: `(services.jarm.fingerprint: 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6) AND (autonomous_system.asn: 49505)` Received **67** results ### Ports - **80**: 4 records - **443**: 4 records - **22**: 1 records - **81**: 1 records - **88**: 1 records ## Shodan investigation Filter: `ssl.jarm:20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6+country:RU+asn:as49505` - Received **1** results. - Observed IPs: **['95.213.191.237']** - Observed hostnames: **[]** ## 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 Received **18548** results ### Ports - **443**: 4068 records - **80**: 3656 records - **22**: 2208 records - **25**: 351 records - **993**: 288 records ## 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 aggregated on country code RU Filter: `(services.jarm.fingerprint: 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4) AND (location.country_code: RU)` Received **1165** results ### Ports - **443**: 246 records - **80**: 202 records - **22**: 118 records - **123**: 39 records - **25**: 26 records ## 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 aggregated on ASN 49505 Filter: `(services.jarm.fingerprint: 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4) AND (autonomous_system.asn: 49505)` Received **46** results ### Ports - **443**: 8 records - **80**: 7 records - **22**: 2 records - **123**: 2 records - **20779**: 2 records ## Shodan investigation Filter: `ssl.jarm:28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4+country:RU+asn:as49505` - Received **14** results. - Observed IPs: **['188.68.215.82', '212.41.9.250', '188.68.218.199', '89.248.193.240', '5.188.158.37', '45.9.91.87', '185.17.9.5', '92.53.65.163', '94.26.229.226', '5.182.4.217', '45.12.231.112', '188.124.50.172', '185.184.55.41', '45.9.91.91']** - Observed hostnames: **['external.keng.ru', 'test-m.keng.ru', 'h10.twinscom.ru', 'portal.miningelement.com', 'test.keng.ru', 'agrocoder.ru', 'api.afdev.ru', 'stage.keng.ru', 'core.fugatabula.com', 'build.pragma.info', 'sentry.calypso.finance', 'test-sale.keng.ru', 'pages.core.fugatabula.com', 'stats.pragma.info', 'dev-sale.keng.ru', 'stage-sale.keng.ru', 'external-sale.keng.ru', 'stage-m.keng.ru', 'scm.miningelement.com', 'multigo.ru', 'anticor.ooosis.ru', 'dev.keng.ru', 'external-m.keng.ru', 'dev-m.keng.ru']** ## Detailed investigation reports ## 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 ### 84.38.187.104 - Last updated by Censys: 2024-07-08T23:35:44.740Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames #### Ports - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **4848** (UNKNOWN, from Censys) - **5222** (XMPP, from Censys) - **6262** (SSH, from Censys) - **6464** (SSH, from Censys) - **6465** (SSH, from Censys) - **8006** (HTTP, from Censys) - **8081** (HTTP, from Censys) - **8443** (HTTP, from Censys) - **15653** (XMPP, from Censys) - **15669** (HTTP, from Censys) - **15670** (HTTP, from Censys) ### 95.213.131.34 - Last updated by Censys: 2024-07-04T13:43:08.256Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **master.broker18.ru** (from Censys) #### Ports - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **4001** (UNKNOWN, from Censys) ### 95.213.191.237 - Last updated by Censys: 2024-07-09T00:45:52.552Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames #### Ports - **80** (HTTP, from Censys) - **81** (HTTP, from Censys) - **88** (HTTP, from Censys) - **443** (HTTP, from Censys) - **1443** (HTTP, from Censys) - **4443** (UNKNOWN, from Censys) - **5443** (HTTP, from Censys) - **6444** (HTTP, from Censys) - **6445** (HTTP, from Censys) - **6446** (HTTP, from Censys) - **6447** (HTTP, from Censys) - **7000** (UNKNOWN, from Censys) - **7453** (HTTP, from Censys) - **7500** (HTTP, from Censys) - **8000** (HTTP, from Censys) - **8001** (HTTP, from Censys) - **8088** (HTTP, from Censys) - **58221** (SSH, from Censys) - **4443** (matching JARM, from Shodan) ### 188.68.210.86 - Last updated by Censys: 2024-07-08T23:39:11.196Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **teescustoms.com** (from Censys) #### Ports - **22** (SSH, from Censys) - **80** (HTTP, from Censys) - **443** (UNKNOWN, from Censys) - **446** (HTTP, from Censys) - **447** (HTTP, from Censys) - **1119** (HTTP, from Censys) - **7020** (HTTP, from Censys) - **11500** (HTTP, from Censys) - **11501** (HTTP, from Censys) - **11502** (HTTP, from Censys) - **11503** (HTTP, from Censys) - **11504** (HTTP, from Censys) - **11505** (HTTP, from Censys) - **11506** (HTTP, from Censys) - **11507** (HTTP, from Censys) - **11508** (HTTP, from Censys) - **11509** (HTTP, from Censys) - **11510** (HTTP, from Censys) - **11511** (HTTP, from Censys) - **11512** (HTTP, from Censys) - **11513** (HTTP, from Censys) - **11514** (HTTP, from Censys) - **11515** (HTTP, from Censys) - **11516** (HTTP, from Censys) - **11517** (HTTP, from Censys) - **11518** (HTTP, from Censys) - **11519** (HTTP, from Censys) - **11520** (HTTP, from Censys) - **11521** (HTTP, from Censys) - **11522** (HTTP, from Censys) - **11523** (HTTP, from Censys) - **11524** (HTTP, from Censys) - **11525** (HTTP, from Censys) ## 28d28d28d00028d00028d28d28d28d19c87d867c7769d680e54b71c8f5e7c4 ### 92.53.65.163 - Last updated by Censys: 2024-07-09T03:24:23.926Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **api.afdev.ru** (from Shodan) #### Ports - **53** (DNS, from Censys) - **80** (HTTP, from Censys) - **123** (NTP, from Censys) - **443** (HTTP, from Censys) - **4369** (EPMD, from Censys) - **4434** (HTTP, from Censys) - **8765** (HTTP, from Censys) - **56900** (HTTP, from Censys) - **65317** (SSH, from Censys) - **65500** (HTTP, from Censys) - **4434** (matching JARM, from Shodan) ### 45.9.91.91 - Last updated by Censys: 2024-07-02T10:21:56.472Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **multigo.ru** (from Shodan) #### Ports - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **20779** (SSH, from Censys) - **443** (matching JARM, from Shodan) ### 188.124.50.172 - Last updated by Censys: 2024-07-09T02:06:09.781Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **scm.miningelement.com** (from Shodan) #### Ports - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **8893** (HTTP, from Censys) - **8894** (HTTP, from Censys) - **443** (matching JARM, from Shodan) ### 2a00:ab00:1103:23:0:0:0:51 - Last updated by Censys: 2024-07-08T19:07:12.300Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames #### Ports - **443** (UNKNOWN, from Censys) ### 188.68.215.82 - Last updated by Censys: 2024-07-09T01:29:25.189Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **test.keng.ru** (from Shodan) - **test-sale.keng.ru** (from Shodan) - **dev-m.keng.ru** (from Shodan) - **stage-m.keng.ru** (from Shodan) - **stage-sale.keng.ru** (from Shodan) - **stage.keng.ru** (from Shodan) - **external-m.keng.ru** (from Shodan) - **external-sale.keng.ru** (from Shodan) - **dev.keng.ru** (from Shodan) - **test-m.keng.ru** (from Shodan) - **external.keng.ru** (from Shodan) - **dev-sale.keng.ru** (from Shodan) #### Ports - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **3422** (SSH, from Censys) - **443** (matching JARM, from Shodan) ### 45.9.91.87 - Last updated by Censys: 2024-07-04T15:33:43.519Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **multigo.ru** (from Shodan) #### Ports - **80** (HTTP, from Censys) - **123** (NTP, from Censys) - **443** (HTTP, from Censys) - **9993** (HTTP, from Censys) - **20779** (SSH, from Censys) - **443** (matching JARM, from Shodan) ### 94.26.229.226 - Last updated by Censys: 2024-07-09T04:08:04.778Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **anticor.ooosis.ru** (from Shodan) #### Ports - **22** (SSH, from Censys) - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **3128** (HTTP, from Censys) - **8006** (HTTP, from Censys) - **22102** (SSH, from Censys) - **22103** (SSH, from Censys) - **22106** (SSH, from Censys) - **22108** (SSH, from Censys) - **22111** (SSH, from Censys) - **22112** (SSH, from Censys) - **22113** (SSH, from Censys) - **22114** (SSH, from Censys) - **22117** (SSH, from Censys) - **443** (matching JARM, from Shodan) ### 188.68.218.199 - Last updated by Censys: 2024-07-08T17:50:13.374Z - Censys results are capped at 10 entries. - Location: **Russia** RU - ASN: **49505** #### Hostnames - **pages.core.fugatabula.com** (from Shodan) - **core.fugatabula.com** (from Shodan) #### Ports - **22** (SSH, from Censys) - **80** (HTTP, from Censys) - **443** (HTTP, from Censys) - **1194** (OPENVPN, from Censys) - **8123** (HTTP, from Censys) - **11223** (HTTP, from Censys) - **443** (matching JARM, from Shodan) ## MISP correlations ### Events | Source | Value | Event | Event ID | |:------:|:---------------------------------------------------------------|:---------------------------------------------------|:---------| | MISP | 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 | C2-JARM - A list of JARM hashes for different ssl | 2391 | | | | implementations used by some C2 tools. | | ### OSINT feeds | Source | Value | Feed | Feed URL | |:------:|:---------------------------------------------------------------|:-----------------|:--------------------------------------------------------------------------------------| | Feeds | 20d14d20d21d20d20c20d14d20d20daddf8a68a1444c74b6dbe09910a511e6 | CIRCL OSINT Feed | https://misp.demo.cudeso.be/feeds/previewEvent/1/4ce77fdd-19a8-4037-ac75-4ece0c05d63f | ## MISP indicators | Event ID | Value | Type | |:--------:|:--------------------------|:---------| | 3485 | anticor.ooosis.ru | hostname | | 3485 | api.afdev.ru | hostname | | 3485 | core.fugatabula.com | hostname | | 3485 | dev-m.keng.ru | hostname | | 3485 | dev-sale.keng.ru | hostname | | 3485 | dev.keng.ru | hostname | | 3485 | external-m.keng.ru | hostname | | 3485 | external-sale.keng.ru | hostname | | 3485 | external.keng.ru | hostname | | 3485 | master.broker18.ru | hostname | | 3485 | multigo.ru | hostname | | 3485 | pages.core.fugatabula.com | hostname | | 3485 | scm.miningelement.com | hostname | | 3485 | stage-m.keng.ru | hostname | | 3485 | stage-sale.keng.ru | hostname | | 3485 | stage.keng.ru | hostname | | 3485 | teescustoms.com | hostname | | 3485 | test-m.keng.ru | hostname | | 3485 | test-sale.keng.ru | hostname | | 3485 | test.keng.ru | hostname | | 3485 | 188.124.50.172 | ip-dst | | 3485 | 188.68.210.86 | ip-dst | | 3485 | 188.68.215.82 | ip-dst | | 3485 | 188.68.218.199 | ip-dst | | 3485 | 2a00:ab00:1103:23::51 | ip-dst | | 3485 | 45.9.91.87 | ip-dst | | 3485 | 45.9.91.91 | ip-dst | | 3485 | 84.38.187.104 | ip-dst | | 3485 | 92.53.65.163 | ip-dst | | 3485 | 94.26.229.226 | ip-dst | | 3485 | 95.213.131.34 | ip-dst | | 3485 | 95.213.191.237 | ip-dst |
EN:3 Send a summary to Mattermost¶
Now you can send the summary to Mattermost. You can send the summary in two ways by selecting one of the options for the variable send_to_mattermost_option
in the next cell.
- The default option where the entire summary is in the chat, or
- a short intro and the summary in a card
For this playbook we rely on a webhook in Mattermost. You can add a webhook by choosing the gear icon in Mattermost, then choose Integrations and then Incoming Webhooks. Set a channel for the webhook and lock the webhook to this channel with "Lock to this channel".
send_to_mattermost_option = "via a chat message"
#send_to_mattermost_option = "via a chat message with card"
message = False
if send_to_mattermost_option == "via a chat message":
message = {"username": mattermost_playbook_user, "text": summary}
elif send_to_mattermost_option == "via a chat message with card":
message = {"username": mattermost_playbook_user, "text": intro, "props": {"card": summary}}
if message:
r = requests.post(mattermost_hook, data=json.dumps(message))
r.raise_for_status()
if message and r.status_code == 200:
print("Summary is \033[92msent to Mattermost.\n")
else:
print("\033[91mFailed to sent summary\033[90m to Mattermost.\n")
Summary is sent to Mattermost.
EN:4 End of the playbook¶
print("\033[92m End of the playbook")
End of the playbook
External references ¶
Technical details¶
Documentation¶
This playbook requires these Python libraries to exist in the environment where the playbook is executed. You can install them with pip install <library>
.
PrettyTable
ipywidgets
censys
shodan
Colour codes¶
The output from Python displays some text in different colours. These are the colour codes
Red = '\033[91m'
Green = '\033[92m'
Blue = '\033[94m'
Cyan = '\033[96m'
White = '\033[97m'
Yellow = '\033[93m'
Magenta = '\033[95m'
Grey = '\033[90m'
Black = '\033[90m'
Default = '\033[99m'