Skip to content

Commit 8bc03a4

Browse files
committed
Merge branch 'DT-pivot_analyzer' of https://github.com/DomainTools/Cortex-Analyzers into release/2.4.0
2 parents ce31785 + e5e645e commit 8bc03a4

7 files changed

Lines changed: 433 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "DomainToolsIris_Pivot",
3+
"version": "1.0",
4+
"author": "DomainTools",
5+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
6+
"license": "AGPL-V3",
7+
"description": "Use DomainTools Iris API to pivot on ssl_hash, ip, or email.",
8+
"dataTypeList": ["hash", "ip", "mail"],
9+
"command": "DomainToolsIris/domaintoolsiris_analyzer.py",
10+
"baseConfig": "DomainToolsIris",
11+
"config": {
12+
"service": "pivot"
13+
},
14+
"configurationItems": [
15+
{
16+
"name": "username",
17+
"description": "DomainTools Iris API credentials",
18+
"type": "string",
19+
"multi": false,
20+
"required": true
21+
},
22+
{
23+
"name": "key",
24+
"description": "DomainTools Iris API credentials",
25+
"type": "string",
26+
"multi": false,
27+
"required": true
28+
},
29+
{
30+
"name": "pivot_count_threshold",
31+
"description": "Pivot count threshold.",
32+
"type": "number",
33+
"multi": false,
34+
"required": false,
35+
"defaultValue": 500
36+
}
37+
]
38+
}
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
#!/usr/bin/env python3
2+
# encoding: utf-8
3+
4+
from domaintools.exceptions import NotFoundException
5+
from domaintools.exceptions import NotAuthorizedException
6+
from domaintools.exceptions import ServiceUnavailableException
7+
8+
from domaintools import API
9+
10+
from cortexutils.analyzer import Analyzer
11+
from datetime import datetime
12+
13+
14+
class DomainToolsAnalyzer(Analyzer):
15+
def __init__(self):
16+
Analyzer.__init__(self)
17+
self.service = self.get_param(
18+
"config.service", None, "Service parameter is missing"
19+
)
20+
self.raw = ""
21+
self.pivot_count_threshold = int(self.get_param("config.pivot_count_threshold"))
22+
23+
@staticmethod
24+
def get_domain_age(create_date):
25+
"""
26+
Finds how many days old a domain is given a start date.
27+
Args:
28+
create_date: Date in the form of %Y-%m-%d'
29+
30+
Returns: Number of days
31+
"""
32+
time_diff = datetime.now() - datetime.strptime(create_date, "%Y-%m-%d")
33+
return time_diff.days
34+
35+
@staticmethod
36+
def get_threat_level(risk_score):
37+
level = "info"
38+
if risk_score <= 65:
39+
level = "safe"
40+
elif 65 < risk_score <= 80:
41+
level = "suspicious"
42+
elif risk_score > 80:
43+
level = "malicious"
44+
return level
45+
46+
@staticmethod
47+
def get_threat_level_class(risk_score):
48+
level = ""
49+
if risk_score <= 65:
50+
level = "label-success"
51+
elif 65 < risk_score <= 80:
52+
level = "label-warning"
53+
elif risk_score > 80:
54+
level = "label-danger"
55+
return level
56+
57+
def add_pivot_class(self, data_obj):
58+
"""
59+
Does a deep dive through a data object to check count vs pivot threshold to add class to DOM element.
60+
Args:
61+
data_obj: Either a list or dict that needs to check pivot count
62+
"""
63+
if isinstance(data_obj, dict) and len(data_obj):
64+
for k, v in data_obj.items():
65+
if isinstance(data_obj[k], dict) or isinstance(data_obj[k], list):
66+
self.add_pivot_class(data_obj[k])
67+
if "count" in data_obj and (
68+
0 < data_obj["count"] < self.pivot_count_threshold
69+
):
70+
data_obj["class"] = "label-danger"
71+
elif "count" in data_obj and data_obj["count"] == 0:
72+
del data_obj["count"]
73+
elif (
74+
"count" in data_obj
75+
and "class" not in data_obj
76+
and data_obj["count"] != 0
77+
):
78+
data_obj["class"] = "label-info"
79+
80+
elif isinstance(data_obj, list) and len(data_obj):
81+
for index, item in enumerate(data_obj):
82+
self.add_pivot_class(item)
83+
84+
@staticmethod
85+
def get_threat_component(components, threat_type):
86+
"""
87+
Gets a certain threat component out a list of components
88+
Args:
89+
components: List of threat components
90+
threat_type: Type of threat we are looking for
91+
92+
Returns: Either the component that we asked for or None
93+
"""
94+
for component in components:
95+
if component.get("name") == threat_type:
96+
return component
97+
else:
98+
return None
99+
100+
def format_single_domain(self, domain_data):
101+
domain_data["last_enriched"] = datetime.now().date().strftime("%m-%d-%Y")
102+
if isinstance(domain_data["website_response"], dict):
103+
domain_data["website_response"] = domain_data["website_response"].get(
104+
"value", ""
105+
)
106+
# Threat Components Flatten
107+
domain_risk = domain_data.get("domain_risk", {})
108+
109+
overall_risk_score = domain_risk.get("risk_score", 0)
110+
domain_risk["overall"] = {
111+
"value": overall_risk_score,
112+
"class": DomainToolsAnalyzer.get_threat_level_class(overall_risk_score),
113+
}
114+
risk_components = domain_risk.get("components", {})
115+
if risk_components:
116+
proximity_data = DomainToolsAnalyzer.get_threat_component(
117+
risk_components, "proximity"
118+
)
119+
blacklist_data = DomainToolsAnalyzer.get_threat_component(
120+
risk_components, "blacklist"
121+
)
122+
domain_risk["proximity"] = {"value": 0}
123+
if proximity_data:
124+
domain_risk["proximity"]["value"] = proximity_data.get("risk_score", 0)
125+
elif blacklist_data:
126+
domain_risk["proximity"]["value"] = blacklist_data.get("risk_score", 0)
127+
domain_risk["proximity"][
128+
"class"
129+
] = DomainToolsAnalyzer.get_threat_level_class(
130+
domain_risk["proximity"]["value"]
131+
)
132+
threat_profile_data = DomainToolsAnalyzer.get_threat_component(
133+
risk_components, "threat_profile"
134+
)
135+
if threat_profile_data:
136+
domain_risk["tp"] = {}
137+
domain_risk["tp"]["value"] = threat_profile_data.get("risk_score", 0)
138+
domain_risk["tp"]["class"] = DomainToolsAnalyzer.get_threat_level_class(
139+
domain_risk["tp"]["value"]
140+
)
141+
domain_risk["tp"]["threats"] = threat_profile_data.get("threats", [])
142+
domain_risk["tp"]["evidence"] = threat_profile_data.get("evidence", [])
143+
threat_profile_malware_data = DomainToolsAnalyzer.get_threat_component(
144+
risk_components, "threat_profile_malware"
145+
)
146+
if threat_profile_malware_data:
147+
domain_risk["tpm"] = {}
148+
domain_risk["tpm"]["value"] = threat_profile_malware_data.get(
149+
"risk_score", 0
150+
)
151+
domain_risk["tpm"][
152+
"class"
153+
] = DomainToolsAnalyzer.get_threat_level_class(
154+
domain_risk["tpm"]["value"]
155+
)
156+
threat_profile_phshing_data = DomainToolsAnalyzer.get_threat_component(
157+
risk_components, "threat_profile_phishing"
158+
)
159+
if threat_profile_phshing_data:
160+
domain_risk["tpp"] = {}
161+
domain_risk["tpp"]["value"] = threat_profile_malware_data.get(
162+
"risk_score", 0
163+
)
164+
domain_risk["tpp"][
165+
"class"
166+
] = DomainToolsAnalyzer.get_threat_level_class(
167+
domain_risk["tpp"]["value"]
168+
)
169+
threat_profile_spam_data = DomainToolsAnalyzer.get_threat_component(
170+
risk_components, "threat_profile_spam"
171+
)
172+
if threat_profile_spam_data:
173+
domain_risk["tps"] = {}
174+
domain_risk["tps"]["value"] = threat_profile_malware_data.get(
175+
"risk_score", 0
176+
)
177+
domain_risk["tps"][
178+
"class"
179+
] = DomainToolsAnalyzer.get_threat_level_class(
180+
domain_risk["tps"]["value"]
181+
)
182+
183+
# Contacts Flatten
184+
domain_data["types"] = [
185+
"registrant_contact",
186+
"admin_contact",
187+
"technical_contact",
188+
"billing_contact",
189+
]
190+
domain_data["contacts"] = []
191+
for c in domain_data["types"]:
192+
split_type = c.split("_")
193+
domain_data[c]["type"] = "{} Contact".format(split_type[0].capitalize())
194+
domain_data["contacts"].append(domain_data[c])
195+
196+
self.add_pivot_class(domain_data)
197+
return domain_data
198+
199+
@staticmethod
200+
def format_pivot_domains(domains, artifact_type, artifact_data):
201+
result = {
202+
"last_enriched": datetime.now().date().strftime("%m-%d-%Y"),
203+
"pivot_artifact": "{} = {}".format(artifact_type.upper(), artifact_data),
204+
"average_risk_score": 0,
205+
}
206+
total_risk_score = 0
207+
sorted_domains = sorted(
208+
domains,
209+
key=lambda d: (
210+
d.get("domain_risk", 0).get("risk_score", 0),
211+
d.get("domain"),
212+
),
213+
reverse=True,
214+
)
215+
result_domains = []
216+
for domain in sorted_domains:
217+
total_risk_score += domain.get("domain_risk", 0).get("risk_score", 0)
218+
temp_dict = {"domain": domain.get("domain")}
219+
risk_score = domain.get("domain_risk", 0).get("risk_score", 0)
220+
temp_dict["domain_risk"] = {
221+
"class": DomainToolsAnalyzer.get_threat_level_class(risk_score),
222+
"risk_score": risk_score,
223+
}
224+
create_date = domain.get("create_date", {}).get("value", "")
225+
if create_date:
226+
temp_dict["domain_age"] = DomainToolsAnalyzer.get_domain_age(
227+
create_date
228+
)
229+
else:
230+
temp_dict["domain_age"] = 0
231+
result_domains.append(temp_dict)
232+
result["results"] = result_domains
233+
if len(result_domains):
234+
result["average_risk_score"] = total_risk_score // len(result_domains)
235+
return result
236+
237+
def domaintools(self, data):
238+
response = None
239+
api = API(self.get_param("config.username"), self.get_param("config.key"))
240+
241+
APP_PARAMETERS = {"app_partner": "cortex", "app_name": "Iris", "app_version": 1}
242+
243+
if self.service == "investigate-domain" and self.data_type in ["domain"]:
244+
response = api.iris_investigate(data, **APP_PARAMETERS).response()
245+
if response["results_count"]:
246+
response = self.format_single_domain(response.get("results")[0])
247+
248+
elif self.service == "pivot" and self.data_type in ["hash", "ip", "mail"]:
249+
iris_investigate_args_map = {
250+
"ip": "ip",
251+
"mail": "email",
252+
"hash": "ssl_hash",
253+
}
254+
APP_PARAMETERS[iris_investigate_args_map[self.data_type]] = data
255+
response = api.iris_investigate(**APP_PARAMETERS).response()
256+
response = DomainToolsAnalyzer.format_pivot_domains(
257+
response.get("results"), iris_investigate_args_map[self.data_type], data
258+
)
259+
260+
return response
261+
262+
def summary(self, raw):
263+
self.raw = raw
264+
r = {"service": self.service, "dataType": self.data_type}
265+
266+
taxonomies = []
267+
268+
# Prepare predicate and value for each service
269+
if r["service"] == "investigate-domain":
270+
if "risk_score" in raw["domain_risk"]:
271+
risk_score = raw["domain_risk"]["overall"]["value"]
272+
level = self.get_threat_level(risk_score)
273+
taxonomies.append(
274+
self.build_taxonomy(
275+
level, "DT", "Risk Score", "{}".format(risk_score)
276+
)
277+
)
278+
else:
279+
taxonomies.append(
280+
self.build_taxonomy("info", "DT", "Risk Score", "No Risk Score")
281+
)
282+
283+
if "tp" in raw["domain_risk"]:
284+
risk_score = raw["domain_risk"]["tp"]["value"]
285+
level = self.get_threat_level(risk_score)
286+
taxonomies.append(
287+
self.build_taxonomy(
288+
level, "DT", "Threat Profile Score", "{}".format(risk_score)
289+
)
290+
)
291+
evidence = ",".join(raw["domain_risk"]["tp"]["evidence"])
292+
if evidence:
293+
taxonomies.append(
294+
self.build_taxonomy(
295+
"info", "DT", "Evidence", "{}".format(evidence)
296+
)
297+
)
298+
else:
299+
taxonomies.append(
300+
self.build_taxonomy(
301+
"info", "DT", "Threat Profile Score", "No Threat Profile Score"
302+
)
303+
)
304+
tags = ",".join([t["label"] for t in raw.get("tags", [])])
305+
if tags:
306+
taxonomies.append(
307+
self.build_taxonomy("info", "DT", "IrisTags", "{}".format(tags))
308+
)
309+
310+
elif r["service"] == "pivot":
311+
taxonomies.append(self.build_taxonomy("info", "DT", "Pivots", "Pivots"))
312+
313+
result = {"taxonomies": taxonomies}
314+
return result
315+
316+
def run(self):
317+
data = self.get_data()
318+
319+
try:
320+
r = self.domaintools(data)
321+
322+
if "error" in r and "message" in r["error"]:
323+
# noinspection PyTypeChecker
324+
self.error(r["error"]["message"])
325+
else:
326+
self.report(r)
327+
328+
except NotFoundException:
329+
self.error(self.data_type.capitalize() + " not found")
330+
except NotAuthorizedException:
331+
self.error("An authorization error occurred")
332+
except ServiceUnavailableException:
333+
self.error("DomainTools Service is currenlty unavailable")
334+
except Exception as e:
335+
self.unexpectedError(e)
336+
337+
338+
if __name__ == "__main__":
339+
DomainToolsAnalyzer().run()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cortexutils
2+
domaintools_api ; python_version < '3.5'
3+
git+https://github.com/DomainTools/python_api.git ; python_version >= '3.5'
205 KB
Loading
16 KB
Loading

0 commit comments

Comments
 (0)