mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 06:34:57 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
245 lines
8.9 KiB
Python
245 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""APT group analysis agent using MITRE ATT&CK Navigator layers.
|
|
|
|
Queries ATT&CK data, maps APT techniques to Navigator layers,
|
|
performs detection gap analysis, and generates threat-informed reports.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from collections import Counter
|
|
|
|
try:
|
|
import requests
|
|
HAS_REQUESTS = True
|
|
except ImportError:
|
|
HAS_REQUESTS = False
|
|
|
|
ATTACK_ENTERPRISE_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"
|
|
|
|
NAVIGATOR_LAYER_TEMPLATE = {
|
|
"name": "",
|
|
"versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
|
|
"domain": "enterprise-attack",
|
|
"description": "",
|
|
"filters": {"platforms": ["Windows", "Linux", "macOS", "Cloud"]},
|
|
"sorting": 0,
|
|
"layout": {"layout": "side", "aggregateFunction": "average", "showID": False,
|
|
"showName": True, "showAggregateScores": False, "countUnscored": False},
|
|
"hideDisabled": False,
|
|
"techniques": [],
|
|
"gradient": {"colors": ["#ffffff", "#ff6666"], "minValue": 0, "maxValue": 100},
|
|
"legendItems": [],
|
|
"metadata": [],
|
|
"links": [],
|
|
"showTacticRowBackground": False,
|
|
"tacticRowBackground": "#dddddd",
|
|
"selectTechniquesAcrossTactics": True,
|
|
"selectSubtechniquesWithParent": False,
|
|
"selectVisibleTechniques": False,
|
|
}
|
|
|
|
|
|
def load_attack_data(filepath=None):
|
|
"""Load ATT&CK STIX bundle from file or download."""
|
|
if filepath and os.path.exists(filepath):
|
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
if HAS_REQUESTS:
|
|
print("[*] Downloading ATT&CK Enterprise data...")
|
|
resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
return None
|
|
|
|
|
|
def extract_groups(bundle):
|
|
"""Extract intrusion-set (APT group) objects from STIX bundle."""
|
|
groups = {}
|
|
for obj in bundle.get("objects", []):
|
|
if obj.get("type") == "intrusion-set":
|
|
name = obj.get("name", "Unknown")
|
|
aliases = obj.get("aliases", [])
|
|
ext_refs = obj.get("external_references", [])
|
|
attack_id = ""
|
|
for ref in ext_refs:
|
|
if ref.get("source_name") == "mitre-attack":
|
|
attack_id = ref.get("external_id", "")
|
|
break
|
|
groups[obj["id"]] = {
|
|
"name": name, "id": attack_id, "aliases": aliases,
|
|
"description": obj.get("description", "")[:200],
|
|
}
|
|
return groups
|
|
|
|
|
|
def extract_techniques(bundle):
|
|
"""Extract attack-pattern (technique) objects from STIX bundle."""
|
|
techniques = {}
|
|
for obj in bundle.get("objects", []):
|
|
if obj.get("type") == "attack-pattern" and not obj.get("revoked", False):
|
|
ext_refs = obj.get("external_references", [])
|
|
attack_id = ""
|
|
for ref in ext_refs:
|
|
if ref.get("source_name") == "mitre-attack":
|
|
attack_id = ref.get("external_id", "")
|
|
break
|
|
if attack_id:
|
|
tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])]
|
|
techniques[obj["id"]] = {
|
|
"id": attack_id, "name": obj.get("name", ""),
|
|
"tactics": tactics, "platforms": obj.get("x_mitre_platforms", []),
|
|
}
|
|
return techniques
|
|
|
|
|
|
def map_group_techniques(bundle, group_stix_id, techniques):
|
|
"""Map techniques used by a specific group via relationship objects."""
|
|
group_techniques = []
|
|
for obj in bundle.get("objects", []):
|
|
if (obj.get("type") == "relationship" and
|
|
obj.get("relationship_type") == "uses" and
|
|
obj.get("source_ref") == group_stix_id and
|
|
obj.get("target_ref", "").startswith("attack-pattern--")):
|
|
tech_id = obj["target_ref"]
|
|
if tech_id in techniques:
|
|
group_techniques.append(techniques[tech_id])
|
|
return group_techniques
|
|
|
|
|
|
def build_navigator_layer(group_name, group_techniques, color="#ff6666", score=100):
|
|
"""Build ATT&CK Navigator JSON layer for a group's techniques."""
|
|
layer = json.loads(json.dumps(NAVIGATOR_LAYER_TEMPLATE))
|
|
layer["name"] = f"{group_name} - TTPs"
|
|
layer["description"] = f"ATT&CK techniques attributed to {group_name}"
|
|
for tech in group_techniques:
|
|
entry = {
|
|
"techniqueID": tech["id"],
|
|
"tactic": tech["tactics"][0] if tech["tactics"] else "",
|
|
"color": color,
|
|
"comment": f"Used by {group_name}",
|
|
"enabled": True,
|
|
"metadata": [],
|
|
"links": [],
|
|
"showSubtechniques": False,
|
|
"score": score,
|
|
}
|
|
layer["techniques"].append(entry)
|
|
return layer
|
|
|
|
|
|
def detection_gap_analysis(group_techniques, detection_rules):
|
|
"""Compare group TTPs against existing detection rules to find gaps."""
|
|
covered = set()
|
|
for rule in detection_rules:
|
|
tech_id = rule.get("technique_id", "")
|
|
if tech_id:
|
|
covered.add(tech_id)
|
|
gaps = []
|
|
for tech in group_techniques:
|
|
if tech["id"] not in covered:
|
|
gaps.append({
|
|
"technique_id": tech["id"],
|
|
"technique_name": tech["name"],
|
|
"tactics": tech["tactics"],
|
|
"status": "NO DETECTION",
|
|
})
|
|
coverage_pct = (len(covered & {t["id"] for t in group_techniques}) /
|
|
len(group_techniques) * 100) if group_techniques else 0
|
|
return gaps, round(coverage_pct, 1)
|
|
|
|
|
|
def tactic_heatmap(group_techniques):
|
|
"""Generate tactic-level heatmap showing technique distribution."""
|
|
tactic_counts = Counter()
|
|
for tech in group_techniques:
|
|
for tactic in tech["tactics"]:
|
|
tactic_counts[tactic] += 1
|
|
return dict(tactic_counts.most_common())
|
|
|
|
|
|
def compare_groups(group_a_techs, group_b_techs):
|
|
"""Compare two groups' technique sets for overlap analysis."""
|
|
set_a = {t["id"] for t in group_a_techs}
|
|
set_b = {t["id"] for t in group_b_techs}
|
|
overlap = set_a & set_b
|
|
only_a = set_a - set_b
|
|
only_b = set_b - set_a
|
|
jaccard = len(overlap) / len(set_a | set_b) if (set_a | set_b) else 0
|
|
return {
|
|
"overlap_count": len(overlap), "overlap_ids": sorted(overlap),
|
|
"only_group_a": len(only_a), "only_group_b": len(only_b),
|
|
"jaccard_similarity": round(jaccard, 4),
|
|
}
|
|
|
|
|
|
def save_layer(layer, output_path):
|
|
"""Save Navigator layer to JSON file."""
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
json.dump(layer, f, indent=2)
|
|
print(f"[+] Layer saved: {output_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("APT Group Analysis Agent - MITRE ATT&CK Navigator")
|
|
print("TTP mapping, detection gap analysis, group comparison")
|
|
print("=" * 60)
|
|
|
|
group_name = sys.argv[1] if len(sys.argv) > 1 else None
|
|
attack_file = sys.argv[2] if len(sys.argv) > 2 else None
|
|
|
|
bundle = load_attack_data(attack_file)
|
|
if not bundle:
|
|
print("\n[!] Cannot load ATT&CK data. Provide STIX bundle path or install requests.")
|
|
print("[DEMO] Usage:")
|
|
print(" python agent.py APT29 enterprise-attack.json")
|
|
print(" python agent.py APT28 # downloads from GitHub")
|
|
sys.exit(1)
|
|
|
|
groups = extract_groups(bundle)
|
|
techniques = extract_techniques(bundle)
|
|
print(f"[*] Loaded {len(groups)} groups, {len(techniques)} techniques")
|
|
|
|
if not group_name:
|
|
print("\n--- Available APT Groups (sample) ---")
|
|
for gid, g in list(groups.items())[:20]:
|
|
print(f" {g['id']:8s} {g['name']:30s} aliases={g['aliases'][:3]}")
|
|
sys.exit(0)
|
|
|
|
target_group = None
|
|
for gid, g in groups.items():
|
|
if (g["name"].lower() == group_name.lower() or
|
|
g["id"].lower() == group_name.lower() or
|
|
group_name.lower() in [a.lower() for a in g["aliases"]]):
|
|
target_group = (gid, g)
|
|
break
|
|
|
|
if not target_group:
|
|
print(f"[!] Group '{group_name}' not found")
|
|
sys.exit(1)
|
|
|
|
gid, ginfo = target_group
|
|
print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})")
|
|
print(f" Aliases: {', '.join(ginfo['aliases'][:5])}")
|
|
|
|
group_techs = map_group_techniques(bundle, gid, techniques)
|
|
print(f" Techniques: {len(group_techs)}")
|
|
|
|
heatmap = tactic_heatmap(group_techs)
|
|
print("\n--- Tactic Heatmap ---")
|
|
for tactic, count in heatmap.items():
|
|
bar = "#" * count
|
|
print(f" {tactic:35s} {count:3d} {bar}")
|
|
|
|
layer = build_navigator_layer(ginfo["name"], group_techs)
|
|
out_file = f"{ginfo['name'].replace(' ', '_')}_layer.json"
|
|
save_layer(layer, out_file)
|
|
|
|
sample_rules = [{"technique_id": t["id"]} for t in group_techs[:len(group_techs)//2]]
|
|
gaps, coverage = detection_gap_analysis(group_techs, sample_rules)
|
|
print(f"\n--- Detection Gap Analysis (demo: {coverage}% coverage) ---")
|
|
for gap in gaps[:10]:
|
|
print(f" [GAP] {gap['technique_id']:12s} {gap['technique_name']}")
|