|
| 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() |
0 commit comments