Skip to content

Commit 5d6df7a

Browse files
Merge pull request #1397 from TheHive-Project/csfalcon-ti-analyzer
Add CrowdStrike Falcon Threat Intelligence analyzer
2 parents 2f08e7d + 56215d9 commit 5d6df7a

File tree

5 files changed

+562
-0
lines changed

5 files changed

+562
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"name": "CrowdstrikeFalcon_ThreatIntel",
3+
"version": "1.0",
4+
"author": "Fabien Bloume, StrangeBee",
5+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
6+
"license": "AGPL-V3",
7+
"baseConfig": "CrowdstrikeFalcon",
8+
"config": {
9+
"check_tlp": false,
10+
"max_tlp": 3,
11+
"service": ""
12+
},
13+
"description": "Query threat intelligence indicators from Crowdstrike Falcon Intelligence",
14+
"dataTypeList": [
15+
"hash",
16+
"domain",
17+
"ip",
18+
"url"
19+
],
20+
"command": "CrowdstrikeFalcon/CrowdstrikeFalcon_ThreatIntel.py",
21+
"configurationItems": [
22+
{
23+
"name": "client_id",
24+
"description": "Crowdstrike client ID key",
25+
"type": "string",
26+
"multi": false,
27+
"required": true,
28+
"defaultValue": ""
29+
},
30+
{
31+
"name": "client_secret",
32+
"description": "Crowdstrike client secret key",
33+
"type": "string",
34+
"multi": false,
35+
"required": true,
36+
"defaultValue": ""
37+
},
38+
{
39+
"name": "base_url",
40+
"description": "Crowdstrike base URL. Also supports US-1, US-2, EU-1, US-GOV-1 values",
41+
"type": "string",
42+
"multi": false,
43+
"required": true,
44+
"defaultValue": "https://api.crowdstrike.com"
45+
},
46+
{
47+
"name": "include_deleted",
48+
"description": "Include both published and deleted indicators in the response",
49+
"type": "boolean",
50+
"multi": false,
51+
"required": false,
52+
"defaultValue": false
53+
},
54+
{
55+
"name": "limit",
56+
"description": "Maximum number of indicators to return (Max: 5000)",
57+
"type": "number",
58+
"multi": false,
59+
"required": false,
60+
"defaultValue": 100
61+
}
62+
],
63+
"registration_required": true,
64+
"subscription_required": true,
65+
"free_subscription": false,
66+
"service_homepage": "https://www.crowdstrike.com",
67+
"service_logo": {
68+
"path": "assets/crowdstrike.png",
69+
"caption": "Crowdstrike logo"
70+
},
71+
"screenshots": [
72+
{
73+
"path": "assets/short-report-threatintel.png",
74+
"caption": "Crowdstrike: Short report template"
75+
},
76+
{
77+
"path": "assets/long-report-threatintel.png",
78+
"caption": "Crowdstrike: Long report template"
79+
}
80+
]
81+
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#!/usr/bin/env python3
2+
# encoding: utf-8
3+
from cortexutils.analyzer import Analyzer
4+
from falconpy import OAuth2
5+
from falconpy import Intel
6+
7+
8+
class CrowdstrikeFalcon_ThreatIntel(Analyzer):
9+
def __init__(self):
10+
Analyzer.__init__(self)
11+
self.client_id = self.get_param("config.client_id")
12+
self.client_secret = self.get_param("config.client_secret")
13+
self.base_url = self.get_param("config.base_url", "https://api.crowdstrike.com")
14+
self.include_deleted = self.get_param("config.include_deleted", False)
15+
self.limit = self.get_param("config.limit", 100)
16+
17+
def _detect_hash_type(self, hash_value):
18+
"""Detect hash type based on length"""
19+
hash_len = len(hash_value)
20+
if hash_len == 32:
21+
return "hash_md5"
22+
elif hash_len == 40:
23+
return "hash_sha1"
24+
elif hash_len == 64:
25+
return "hash_sha256"
26+
elif hash_len == 128:
27+
return "hash_sha512"
28+
else:
29+
return None
30+
31+
def _build_filter(self, data_type, observable):
32+
"""Build FQL filter based on data type and observable"""
33+
if data_type == 'hash':
34+
hash_type = self._detect_hash_type(observable)
35+
if hash_type:
36+
return f"type:'{hash_type}'+indicator:'{observable.upper()}'"
37+
else:
38+
# Search across all hash types if we can't determine
39+
return f"indicator:'{observable.upper()}'"
40+
elif data_type == 'domain':
41+
return f"type:'domain'+indicator:'{observable}'"
42+
elif data_type == 'ip':
43+
return f"type:'ip_address'+indicator:'{observable}'"
44+
elif data_type == 'url':
45+
return f"type:'url'+indicator:'{observable}'"
46+
else:
47+
return f"indicator:'{observable}'"
48+
49+
def run(self):
50+
Analyzer.run(self)
51+
52+
try:
53+
observable = self.get_data()
54+
55+
auth = OAuth2(client_id=self.client_id, client_secret=self.client_secret, base_url=self.base_url)
56+
extra_headers = {
57+
"User-Agent": "strangebee-thehive/1.0"
58+
}
59+
intel = Intel(auth_object=auth, ext_headers=extra_headers)
60+
fql_filter = self._build_filter(self.data_type, observable)
61+
response = intel.query_indicator_entities(
62+
filter=fql_filter,
63+
limit=self.limit,
64+
include_deleted=self.include_deleted,
65+
sort="published_date|desc"
66+
)
67+
if 200 <= response["status_code"] < 300:
68+
indicators = response["body"].get("resources", [])
69+
result = {
70+
"observable": observable,
71+
"indicator_count": len(indicators),
72+
"indicators": indicators
73+
}
74+
return self.report(result)
75+
else:
76+
errors = response["body"].get("errors", [])
77+
return self.error(f"Error querying threat intelligence: {errors}")
78+
79+
except Exception as e:
80+
self.unexpectedError(e)
81+
82+
def summary(self, raw):
83+
taxonomies = []
84+
namespace = "CSFalcon"
85+
predicate = "TI"
86+
87+
indicator_count = raw.get("indicator_count", 0)
88+
89+
if indicator_count == 0:
90+
level = "safe"
91+
value = "No indicators found"
92+
else:
93+
# Determine level based on malicious confidence
94+
max_confidence = "unknown"
95+
threat_types = set()
96+
actors = set()
97+
98+
for indicator in raw.get("indicators", []):
99+
confidence = indicator.get("malicious_confidence", "unknown")
100+
101+
# Track the highest confidence level
102+
if confidence == "high":
103+
max_confidence = "high"
104+
elif confidence == "medium" and max_confidence not in ["high"]:
105+
max_confidence = "medium"
106+
elif confidence == "low" and max_confidence not in ["high", "medium"]:
107+
max_confidence = "low"
108+
109+
# Collect threat types and actors
110+
threat_types.update(indicator.get("threat_types", []))
111+
actors.update(indicator.get("actors", []))
112+
113+
# Determine taxonomy level
114+
if max_confidence == "high":
115+
level = "malicious"
116+
elif max_confidence == "medium":
117+
level = "suspicious"
118+
elif max_confidence == "low":
119+
level = "suspicious"
120+
else:
121+
level = "info"
122+
123+
value = f"{indicator_count} indicator(s) | Confidence: {max_confidence}"
124+
125+
taxonomies.append(
126+
self.build_taxonomy(level, namespace, predicate, value)
127+
)
128+
129+
# Add threat types if present
130+
if threat_types:
131+
predicate_threat = "ThreatTypes"
132+
value_threat = ", ".join(list(threat_types)[:3]) # Limit to 3
133+
taxonomies.append(
134+
self.build_taxonomy(level, namespace, predicate_threat, value_threat)
135+
)
136+
137+
# Add actors if present
138+
if actors:
139+
predicate_actor = "Actors"
140+
value_actor = ", ".join(list(actors)[:3]) # Limit to 3
141+
taxonomies.append(
142+
self.build_taxonomy("info", namespace, predicate_actor, value_actor)
143+
)
144+
145+
if not taxonomies:
146+
taxonomies.append(
147+
self.build_taxonomy(level, namespace, predicate, value)
148+
)
149+
150+
return {"taxonomies": taxonomies}
151+
152+
def artifacts(self, raw):
153+
artifacts = []
154+
155+
for indicator in raw.get("indicators", []):
156+
# Collect malware families and actors to use as tags
157+
malware_families = indicator.get("malware_families", [])
158+
actors = indicator.get("actors", [])
159+
160+
# Build tags for related observables
161+
context_tags = ["crowdstrike-ti", "related"]
162+
if malware_families:
163+
context_tags.extend([f"malware:{mf}" for mf in malware_families])
164+
if actors:
165+
context_tags.extend([f"actor:{actor}" for actor in actors])
166+
167+
# Extract related domains, IPs, and hashes with context tags
168+
relations = indicator.get("relations", [])
169+
for relation in relations:
170+
if relation.get("type") == "domain":
171+
artifacts.append(
172+
self.build_artifact(
173+
"domain",
174+
relation.get("indicator"),
175+
tags=context_tags
176+
)
177+
)
178+
elif relation.get("type") == "ip_address":
179+
artifacts.append(
180+
self.build_artifact(
181+
"ip",
182+
relation.get("indicator"),
183+
tags=context_tags
184+
)
185+
)
186+
elif relation.get("type") in ["hash_md5", "hash_sha1", "hash_sha256"]:
187+
artifacts.append(
188+
self.build_artifact(
189+
"hash",
190+
relation.get("indicator"),
191+
tags=context_tags
192+
)
193+
)
194+
195+
# Extract CVEs from vulnerabilities
196+
vulnerabilities = indicator.get("vulnerabilities", [])
197+
for vuln in vulnerabilities:
198+
if vuln.startswith("CVE-"):
199+
artifacts.append(
200+
self.build_artifact(
201+
"other",
202+
vuln,
203+
tags=["crowdstrike-ti", "vulnerability", "cve"]
204+
)
205+
)
206+
207+
return artifacts
208+
209+
def operations(self, raw):
210+
operations = []
211+
212+
# Collect all unique malware families and actors across indicators
213+
malware_families = set()
214+
actors = set()
215+
216+
for indicator in raw.get("indicators", []):
217+
malware_families.update(indicator.get("malware_families", []))
218+
actors.update(indicator.get("actors", []))
219+
220+
# Add malware families as tags to artifacts
221+
for malware in malware_families:
222+
operations.append(
223+
self.build_operation("AddTagToArtifact", tag=f"malware:{malware}")
224+
)
225+
226+
# Add actors as tags to artifacts
227+
for actor in actors:
228+
operations.append(
229+
self.build_operation("AddTagToArtifact", tag=f"actor:{actor}")
230+
)
231+
232+
return operations
233+
234+
235+
if __name__ == "__main__":
236+
CrowdstrikeFalcon_ThreatIntel().run()
272 KB
Loading
75.5 KB
Loading

0 commit comments

Comments
 (0)