8484""" HELPER FUNCTIONS """
8585
8686
87- def get_client (proxy_url : Optional [ str ] = None ):
87+ def get_client (proxy_url : str | None = None ):
8888 return API (
8989 USERNAME ,
9090 API_KEY ,
@@ -108,6 +108,7 @@ def http_request(method: str, params: dict = {}):
108108 Returns: request result
109109
110110 """
111+ response = None
111112 proxy_url = PROXIES .get ("https" ) if PROXIES .get ("https" ) != "" else PROXIES .get ("http" )
112113 if not (USERNAME and API_KEY ):
113114 raise DemistoException ("The 'API Username' and 'API Key' parameters are required." )
@@ -134,32 +135,53 @@ def http_request(method: str, params: dict = {}):
134135 response = api .reverse_ip (domain = params .get ("domain" ), limit = params .get ("limit" )).response ()
135136 elif method == "host-domains" :
136137 response = api .host_domains (ip = params .get ("ip" ), limit = params .get ("limit" )).response ()
138+ elif method == "risk" :
139+ response = api .risk (domain = params .get ("domain" )).response ()
137140 else :
138141 response = api .iris_investigate (** params ).response ()
139142 except Exception as e :
140- demisto .error (str (e ))
141- raise
143+ # exclude for risk endpoint as it throws 404 status by default if domain was not found.
144+ if method != "risk" :
145+ demisto .error (str (e ))
146+ raise
142147
143148 return response
144149
145150
146- def get_dbot_score (proximity_score , age , threat_profile_score ):
151+ def get_dbot_score (proximity_score , age = None , threat_profile_score = None , overall_risk_score = None ):
147152 """
148153 Gets the DBot score
154+ info:
155+ GOOD = 1
156+ SUSPICIOUS = 2
157+ BAD = 3
149158 Args:
150159 proximity_score: The proximity threat score deals with closeness to other malicious domains.
151160 age: The age of the domain.
152161 threat_profile_score: The threat profile score looking at things like phishing and spam.
162+ overall_risk_score: The overall riskscore. Defaults to None.
153163
154164 Returns: DBot Score
155165
156166 """
157- if proximity_score >= RISK_THRESHOLD or threat_profile_score >= RISK_THRESHOLD :
158- return 3
159- elif age < YOUNG_DOMAIN_TIMEFRAME and (proximity_score < RISK_THRESHOLD or threat_profile_score < RISK_THRESHOLD ):
160- return 2
161- else :
162- return 1
167+
168+ # check for the 'BAD' condition then return.
169+ if proximity_score and proximity_score >= RISK_THRESHOLD or (threat_profile_score and threat_profile_score >= RISK_THRESHOLD ):
170+ return Common .DBotScore .BAD
171+
172+ # check for 'SUSPICIOUS' conditions as we know both scores will be lower.
173+ # this means we used the risk score endpoint which we don't check for the domain age
174+ # and look for the overall risk score instead.
175+ if overall_risk_score is not None :
176+ if 50 <= overall_risk_score <= 69 :
177+ return Common .DBotScore .SUSPICIOUS
178+
179+ else : # otherwise, we look for the domain age.
180+ if not age or age < YOUNG_DOMAIN_TIMEFRAME :
181+ return Common .DBotScore .SUSPICIOUS
182+
183+ # If the domain is not BAD and not SUSPICIOUS, then return GOOD.
184+ return Common .DBotScore .GOOD
163185
164186
165187def prune_context_data (data_obj ):
@@ -459,6 +481,89 @@ def create_results(domain_result):
459481 return outputs
460482
461483
484+ def create_domain_risk_results (domain_result : dict ):
485+ """
486+ Creates all the context data necessary given a domain result
487+ Args:
488+ domain_result (dict): DomainTools domain data
489+
490+ Returns: dict {
491+ domain: <Common.Domain> - Domain indicator object with Iris results that map to Domain context
492+ domaintools: <dict> - DomainTools context with all Iris results
493+ }
494+
495+ """
496+ domain = f"{ domain_result .get ('domain' )} "
497+
498+ domain_risk_score_details = {
499+ "overall_risk_score" : domain_result .get ("risk_score" ),
500+ "proximity" : "" ,
501+ "threat_profile" : "" ,
502+ "threat_profile_phishing" : "" ,
503+ "threat_profile_malware" : "" ,
504+ "threat_profile_spam" : "" ,
505+ }
506+
507+ for rc in domain_result .get ("components" ) or []:
508+ component_name = rc .get ("name" )
509+ if component_name == "zerolist" : # ignore zero list
510+ continue
511+ component_riskscore = rc .get ("risk_score" ) or ""
512+ domain_risk_score_details [component_name ] = component_riskscore
513+
514+ domaintools_risk_context = {
515+ "Name" : domain ,
516+ "LastEnriched" : datetime .now ().strftime ("%Y-%m-%d" ),
517+ "Analytics" : {
518+ "OverallRiskScore" : domain_risk_score_details ["overall_risk_score" ],
519+ "ProximityRiskScore" : domain_risk_score_details ["proximity" ],
520+ "MalwareRiskScGore" : domain_risk_score_details ["threat_profile_malware" ],
521+ "PhishingRiskScore" : domain_risk_score_details ["threat_profile_phishing" ],
522+ "SpamRiskScore" : domain_risk_score_details ["threat_profile_spam" ],
523+ "ThreatProfileRiskScore" : {
524+ "RiskScore" : domain_risk_score_details ["threat_profile" ],
525+ },
526+ },
527+ }
528+
529+ # get the db score
530+ reliability = demisto .params ().get ("integrationReliability" )
531+ if domain_risk_score_details ["overall_risk_score" ] is None : # Score is Unknown
532+ dbot_score = Common .DBotScore (
533+ indicator = domain ,
534+ indicator_type = DBotScoreType .DOMAIN ,
535+ integration_name = "DomainTools Iris" ,
536+ score = Common .DBotScore .NONE ,
537+ reliability = reliability ,
538+ message = "No current risk score found for this domain." ,
539+ )
540+ else :
541+ dbot_score_value = get_dbot_score (
542+ proximity_score = domain_risk_score_details ["proximity" ],
543+ age = None ,
544+ threat_profile_score = domain_risk_score_details ["threat_profile" ],
545+ overall_risk_score = domain_risk_score_details ["overall_risk_score" ],
546+ )
547+
548+ malicious_description = None
549+ if dbot_score_value == Common .DBotScore .BAD :
550+ malicious_description = "This domain has been profiled as a threat."
551+
552+ dbot_score = Common .DBotScore (
553+ indicator = domain ,
554+ indicator_type = DBotScoreType .DOMAIN ,
555+ integration_name = "DomainTools Iris" ,
556+ score = dbot_score_value ,
557+ reliability = reliability ,
558+ malicious_description = malicious_description ,
559+ )
560+
561+ domain_indicator = Common .Domain (domain , dbot_score = dbot_score )
562+
563+ outputs = {"domain" : domain_indicator , "domaintools" : domaintools_risk_context }
564+ return outputs
565+
566+
462567def domain_investigate (domain ):
463568 """
464569 Profiles domain and gives back all relevant domain data
@@ -538,6 +643,13 @@ def host_domains(ip: str, limit: int | None = None) -> dict:
538643 return http_request ("host-domains" , params = {"ip" : ip , "limit" : limit })
539644
540645
646+ def get_domain_risk_score (domain : str ) -> dict | None :
647+ """
648+ Returns the domain risk score using /v1/risk endpoint instead using iris
649+ """
650+ return http_request ("risk" , params = {"domain" : domain })
651+
652+
541653def add_key_to_json (cur , to_add ):
542654 if not cur :
543655 return to_add
@@ -806,6 +918,34 @@ def format_investigate_output(result):
806918 return (human_readable , indicators )
807919
808920
921+ def format_risk_score_output (result ):
922+ RISK_SCORE_HEADERS = [
923+ "Name" ,
924+ "Last Enriched" ,
925+ "Overall Risk Score" ,
926+ "Proximity Risk Score" ,
927+ "Threat Profile Risk Score" ,
928+ ]
929+ domain = result .get ("domain" )
930+ result_copy = copy .deepcopy (result )
931+ indicators = create_domain_risk_results (result_copy )
932+
933+ domaintools_analytics_data = indicators .get ("domaintools" , {}).get ("Analytics" , {})
934+
935+ human_readable_data = {
936+ "Name" : f"[{ domain } ](https://domaintools.com)" ,
937+ "Last Enriched" : datetime .now ().strftime ("%Y-%m-%d" ),
938+ "Overall Risk Score" : domaintools_analytics_data .get ("OverallRiskScore" , "" ),
939+ "Proximity Risk Score" : domaintools_analytics_data .get ("ProximityRiskScore" , "" ),
940+ "Threat Profile Risk Score" : domaintools_analytics_data .get ("ThreatProfileRiskScore" , {}).get ("RiskScore" , "" ),
941+ }
942+
943+ demisto_title = f"DomainTools Risk Score for { domain } ."
944+ human_readable = tableToMarkdown (f"{ demisto_title } " , human_readable_data , headers = RISK_SCORE_HEADERS )
945+
946+ return human_readable , indicators
947+
948+
809949def get_domain_risk_score_details (domain_risk : dict [str , Any ]) -> dict [str , Any ]:
810950 """Get the domain risk score details on a given domain risk
811951
@@ -1073,34 +1213,57 @@ def domain_command():
10731213 e.g. !domain domain=domaintools.com
10741214 """
10751215 domain = demisto .args ()["domain" ]
1216+ domain_result_type = demisto .params ().get ("domain_result_type" ) or "Iris"
10761217 domain_list = domain .split ("," )
1077- domain_chunks = chunks (domain_list , 100 )
1078- include_context = argToBoolean (demisto .args ().get ("include_context" , True ))
1079- command_results_list : list [CommandResults ] = []
1080-
1081- for chunk in domain_chunks :
1082- response = domain_investigate ("," .join (chunk ))
1083- missing_domains = response .get ("missing_domains" )
1084- for result in response .get ("results" , []):
1085- human_readable_output , indicators = format_investigate_output (result )
1086-
1087- if len (missing_domains ) > 0 :
1088- human_readable_output += f"Missing Domains: { ',' .join (missing_domains )} "
10891218
1090- domain_indicator = indicators .get ("domain" ) if include_context else None
1091- domaintools_context = indicators .get ("domaintools" ) if include_context else None
1219+ command_results_list : list [CommandResults ] = []
10921220
1221+ human_readable_output = ""
1222+ if domain_result_type .lower () == "verdict" :
1223+ # get the risk score using DT risk endpoint
1224+ for domain in domain_list :
1225+ risk_score_result = get_domain_risk_score (domain = domain )
1226+ if risk_score_result is None :
1227+ risk_score_result = {"domain" : domain }
1228+
1229+ human_readable_output , indicators = format_risk_score_output (risk_score_result )
1230+ domain_indicator = indicators .get ("domain" )
1231+ # for risk result (Verdict result type), create a indicator and add the verdict, no context needed.
10931232 command_results_list .append (
10941233 CommandResults (
1095- outputs_prefix = "DomainTools" ,
1096- outputs_key_field = "Name" ,
10971234 indicator = domain_indicator ,
1098- outputs = domaintools_context ,
1235+ outputs = None ,
10991236 readable_output = human_readable_output ,
1100- raw_response = result ,
1101- ignore_auto_extract = True ,
1237+ raw_response = risk_score_result ,
1238+ ignore_auto_extract = False ,
11021239 )
11031240 )
1241+ else :
1242+ include_context = argToBoolean (demisto .args ().get ("include_context" , True ))
1243+ domain_chunks = chunks (domain_list , 100 )
1244+ for chunk in domain_chunks :
1245+ response = domain_investigate ("," .join (chunk ))
1246+ missing_domains = response .get ("missing_domains" )
1247+ for result in response .get ("results" , []):
1248+ human_readable_output , indicators = format_investigate_output (result )
1249+
1250+ if len (missing_domains ) > 0 :
1251+ human_readable_output += f"Missing Domains: { ',' .join (missing_domains )} "
1252+
1253+ domain_indicator = indicators .get ("domain" ) if include_context else None
1254+ domaintools_context = indicators .get ("domaintools" ) if include_context else None
1255+
1256+ command_results_list .append (
1257+ CommandResults (
1258+ outputs_prefix = "DomainTools" ,
1259+ outputs_key_field = "Name" ,
1260+ indicator = domain_indicator ,
1261+ outputs = domaintools_context ,
1262+ readable_output = human_readable_output ,
1263+ raw_response = result ,
1264+ ignore_auto_extract = True ,
1265+ )
1266+ )
11041267
11051268 if not command_results_list :
11061269 return_warning ("No results." , exit = True )
0 commit comments