mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
354 lines
12 KiB
Python
354 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MITRE ATT&CK Threat Actor TTP Analysis Script
|
|
|
|
Queries the MITRE ATT&CK STIX data to:
|
|
- Map threat actor groups to their known techniques
|
|
- Generate ATT&CK Navigator layers for visualization
|
|
- Perform detection gap analysis
|
|
- Compare TTPs across multiple threat groups
|
|
- Identify high-priority detection opportunities
|
|
|
|
Requirements:
|
|
pip install attackcti mitreattack-python stix2 requests
|
|
|
|
Usage:
|
|
python process.py --group APT29 --output apt29_layer.json
|
|
python process.py --compare APT28 APT29 "Lazarus Group"
|
|
python process.py --gap-analysis --detections detections.json --group APT29
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from collections import defaultdict
|
|
from typing import Optional
|
|
|
|
try:
|
|
from attackcti import attack_client
|
|
except ImportError:
|
|
print("ERROR: attackcti not installed. Run: pip install attackcti")
|
|
sys.exit(1)
|
|
|
|
|
|
class ATTACKAnalyzer:
|
|
"""Analyze threat actor TTPs using MITRE ATT&CK."""
|
|
|
|
def __init__(self):
|
|
print("[*] Initializing ATT&CK client (querying MITRE TAXII server)...")
|
|
self.lift = attack_client()
|
|
self.groups_cache = None
|
|
self.techniques_cache = None
|
|
|
|
def _get_groups(self):
|
|
if self.groups_cache is None:
|
|
self.groups_cache = self.lift.get_groups()
|
|
return self.groups_cache
|
|
|
|
def _get_techniques(self):
|
|
if self.techniques_cache is None:
|
|
self.techniques_cache = self.lift.get_enterprise_techniques()
|
|
return self.techniques_cache
|
|
|
|
def find_group(self, name: str) -> Optional[dict]:
|
|
"""Find a threat group by name or alias."""
|
|
groups = self._get_groups()
|
|
for group in groups:
|
|
if name.lower() == group.get("name", "").lower():
|
|
return group
|
|
aliases = group.get("aliases", [])
|
|
if any(name.lower() == a.lower() for a in aliases):
|
|
return group
|
|
return None
|
|
|
|
def get_group_techniques(self, group_name: str) -> dict:
|
|
"""Get all techniques used by a threat group."""
|
|
group = self.find_group(group_name)
|
|
if not group:
|
|
print(f"[-] Group '{group_name}' not found")
|
|
return {}
|
|
|
|
group_id = ""
|
|
for ref in group.get("external_references", []):
|
|
if ref.get("source_name") == "mitre-attack":
|
|
group_id = ref.get("external_id", "")
|
|
break
|
|
|
|
if not group_id:
|
|
print(f"[-] No ATT&CK ID found for {group_name}")
|
|
return {}
|
|
|
|
techniques = self.lift.get_techniques_used_by_group(group_id)
|
|
technique_map = {}
|
|
|
|
for tech in techniques:
|
|
tech_id = ""
|
|
for ref in tech.get("external_references", []):
|
|
if ref.get("source_name") == "mitre-attack":
|
|
tech_id = ref.get("external_id", "")
|
|
break
|
|
|
|
if not tech_id:
|
|
continue
|
|
|
|
tactics = [
|
|
phase.get("phase_name", "")
|
|
for phase in tech.get("kill_chain_phases", [])
|
|
]
|
|
|
|
technique_map[tech_id] = {
|
|
"name": tech.get("name", ""),
|
|
"tactics": tactics,
|
|
"description": tech.get("description", "")[:500],
|
|
"platforms": tech.get("x_mitre_platforms", []),
|
|
"data_sources": tech.get("x_mitre_data_sources", []),
|
|
}
|
|
|
|
print(f"[+] {group_name} ({group_id}): {len(technique_map)} techniques")
|
|
return technique_map
|
|
|
|
def create_navigator_layer(self, group_name: str, technique_map: dict,
|
|
color: str = "#ff6666") -> dict:
|
|
"""Generate ATT&CK Navigator layer JSON."""
|
|
techniques_list = []
|
|
for tech_id, info in technique_map.items():
|
|
for tactic in info["tactics"]:
|
|
techniques_list.append({
|
|
"techniqueID": tech_id,
|
|
"tactic": tactic,
|
|
"color": color,
|
|
"comment": info["name"],
|
|
"enabled": True,
|
|
"score": 100,
|
|
"metadata": [
|
|
{"name": "group", "value": group_name},
|
|
{"name": "platforms", "value": ", ".join(info["platforms"])},
|
|
],
|
|
})
|
|
|
|
layer = {
|
|
"name": f"{group_name} TTP Coverage",
|
|
"versions": {
|
|
"attack": "16.1",
|
|
"navigator": "5.1.0",
|
|
"layer": "4.5",
|
|
},
|
|
"domain": "enterprise-attack",
|
|
"description": f"Techniques attributed to {group_name}",
|
|
"filters": {
|
|
"platforms": [
|
|
"Linux", "macOS", "Windows", "Cloud",
|
|
"Azure AD", "Office 365", "SaaS", "Google Workspace",
|
|
]
|
|
},
|
|
"sorting": 0,
|
|
"layout": {
|
|
"layout": "side",
|
|
"aggregateFunction": "average",
|
|
"showID": True,
|
|
"showName": True,
|
|
"showAggregateScores": False,
|
|
"countUnscored": False,
|
|
},
|
|
"hideDisabled": False,
|
|
"techniques": techniques_list,
|
|
"gradient": {
|
|
"colors": ["#ffffff", color],
|
|
"minValue": 0,
|
|
"maxValue": 100,
|
|
},
|
|
"legendItems": [
|
|
{"label": f"Used by {group_name}", "color": color},
|
|
{"label": "Not observed", "color": "#ffffff"},
|
|
],
|
|
"showTacticRowBackground": True,
|
|
"tacticRowBackground": "#dddddd",
|
|
"selectTechniquesAcrossTactics": True,
|
|
"selectSubtechniquesWithParent": False,
|
|
"selectVisibleTechniques": False,
|
|
}
|
|
|
|
return layer
|
|
|
|
def compare_groups(self, group_names: list) -> dict:
|
|
"""Compare TTPs across multiple threat groups."""
|
|
group_techs = {}
|
|
for name in group_names:
|
|
techs = self.get_group_techniques(name)
|
|
group_techs[name] = set(techs.keys())
|
|
|
|
if len(group_techs) < 2:
|
|
print("[-] Need at least 2 groups for comparison")
|
|
return {}
|
|
|
|
all_techniques = set.union(*group_techs.values())
|
|
common_to_all = set.intersection(*group_techs.values())
|
|
|
|
comparison = {
|
|
"groups": group_names,
|
|
"total_unique_techniques": len(all_techniques),
|
|
"common_to_all": sorted(common_to_all),
|
|
"common_count": len(common_to_all),
|
|
"per_group": {},
|
|
}
|
|
|
|
for name, techs in group_techs.items():
|
|
others = set.union(*[t for n, t in group_techs.items() if n != name])
|
|
unique = techs - others
|
|
|
|
comparison["per_group"][name] = {
|
|
"total": len(techs),
|
|
"unique": sorted(unique),
|
|
"unique_count": len(unique),
|
|
"overlap_percentage": round(
|
|
len(techs.intersection(others)) / len(techs) * 100, 1
|
|
) if techs else 0,
|
|
}
|
|
|
|
# Technique frequency across groups
|
|
tech_freq = defaultdict(list)
|
|
for name, techs in group_techs.items():
|
|
for t in techs:
|
|
tech_freq[t].append(name)
|
|
|
|
comparison["technique_frequency"] = {
|
|
t: {"count": len(g), "groups": g}
|
|
for t, g in sorted(tech_freq.items(), key=lambda x: -len(x[1]))
|
|
}
|
|
|
|
return comparison
|
|
|
|
def gap_analysis(self, group_name: str,
|
|
detected_techniques: set) -> dict:
|
|
"""Analyze detection gaps for a specific threat group."""
|
|
actor_techs = self.get_group_techniques(group_name)
|
|
actor_tech_ids = set(actor_techs.keys())
|
|
|
|
covered = actor_tech_ids.intersection(detected_techniques)
|
|
gaps = actor_tech_ids - detected_techniques
|
|
|
|
gap_details = []
|
|
for tech_id in sorted(gaps):
|
|
info = actor_techs.get(tech_id, {})
|
|
gap_details.append({
|
|
"technique_id": tech_id,
|
|
"name": info.get("name", ""),
|
|
"tactics": info.get("tactics", []),
|
|
"data_sources": info.get("data_sources", []),
|
|
"platforms": info.get("platforms", []),
|
|
})
|
|
|
|
analysis = {
|
|
"group": group_name,
|
|
"total_actor_techniques": len(actor_tech_ids),
|
|
"detected": len(covered),
|
|
"gaps": len(gaps),
|
|
"coverage_percentage": round(
|
|
len(covered) / len(actor_tech_ids) * 100, 1
|
|
) if actor_tech_ids else 0,
|
|
"detected_techniques": sorted(covered),
|
|
"gap_details": gap_details,
|
|
"recommended_data_sources": self._recommend_data_sources(gap_details),
|
|
}
|
|
|
|
return analysis
|
|
|
|
def _recommend_data_sources(self, gaps: list) -> list:
|
|
"""Recommend data sources that would close the most gaps."""
|
|
ds_coverage = defaultdict(list)
|
|
for gap in gaps:
|
|
for ds in gap.get("data_sources", []):
|
|
ds_coverage[ds].append(gap["technique_id"])
|
|
|
|
recommendations = [
|
|
{"data_source": ds, "covers_techniques": techs, "count": len(techs)}
|
|
for ds, techs in sorted(ds_coverage.items(), key=lambda x: -len(x[1]))
|
|
]
|
|
|
|
return recommendations[:10]
|
|
|
|
def tactic_breakdown(self, group_name: str) -> dict:
|
|
"""Break down threat actor techniques by tactic."""
|
|
techs = self.get_group_techniques(group_name)
|
|
tactic_map = defaultdict(list)
|
|
|
|
for tech_id, info in techs.items():
|
|
for tactic in info["tactics"]:
|
|
tactic_map[tactic].append({
|
|
"id": tech_id,
|
|
"name": info["name"],
|
|
})
|
|
|
|
tactic_order = [
|
|
"reconnaissance", "resource-development", "initial-access",
|
|
"execution", "persistence", "privilege-escalation",
|
|
"defense-evasion", "credential-access", "discovery",
|
|
"lateral-movement", "collection", "command-and-control",
|
|
"exfiltration", "impact",
|
|
]
|
|
|
|
breakdown = {}
|
|
for tactic in tactic_order:
|
|
if tactic in tactic_map:
|
|
breakdown[tactic] = {
|
|
"count": len(tactic_map[tactic]),
|
|
"techniques": tactic_map[tactic],
|
|
}
|
|
|
|
return breakdown
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="MITRE ATT&CK Threat Actor TTP Analyzer"
|
|
)
|
|
parser.add_argument("--group", help="Threat group name (e.g., APT29)")
|
|
parser.add_argument("--compare", nargs="+", help="Compare multiple groups")
|
|
parser.add_argument(
|
|
"--gap-analysis", action="store_true", help="Perform detection gap analysis"
|
|
)
|
|
parser.add_argument(
|
|
"--detections",
|
|
help="JSON file with detected technique IDs",
|
|
)
|
|
parser.add_argument("--breakdown", action="store_true", help="Tactic breakdown")
|
|
parser.add_argument("--output", default="attack_layer.json", help="Output file")
|
|
|
|
args = parser.parse_args()
|
|
analyzer = ATTACKAnalyzer()
|
|
|
|
if args.compare:
|
|
comparison = analyzer.compare_groups(args.compare)
|
|
print(json.dumps(comparison, indent=2))
|
|
with open(args.output, "w") as f:
|
|
json.dump(comparison, f, indent=2)
|
|
|
|
elif args.group and args.gap_analysis:
|
|
detected = set()
|
|
if args.detections:
|
|
with open(args.detections) as f:
|
|
detected = set(json.load(f))
|
|
|
|
analysis = analyzer.gap_analysis(args.group, detected)
|
|
print(json.dumps(analysis, indent=2))
|
|
with open(args.output, "w") as f:
|
|
json.dump(analysis, f, indent=2)
|
|
|
|
elif args.group and args.breakdown:
|
|
breakdown = analyzer.tactic_breakdown(args.group)
|
|
print(json.dumps(breakdown, indent=2))
|
|
|
|
elif args.group:
|
|
tech_map = analyzer.get_group_techniques(args.group)
|
|
layer = analyzer.create_navigator_layer(args.group, tech_map)
|
|
with open(args.output, "w") as f:
|
|
json.dump(layer, f, indent=2)
|
|
print(f"[+] Navigator layer saved to {args.output}")
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|