Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- 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
2026-03-19 13:26:49 +01:00

152 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Threat actor TTP analysis agent using MITRE ATT&CK framework.
Maps threat actor behaviors to ATT&CK techniques, performs coverage analysis,
and generates detection gap reports.
"""
import os
import sys
import json
from collections import Counter, defaultdict
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"
def load_attack_bundle(filepath=None):
if filepath and os.path.exists(filepath):
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
if HAS_REQUESTS:
resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60)
resp.raise_for_status()
return resp.json()
return None
def get_techniques(bundle):
techniques = {}
for obj in bundle.get("objects", []):
if obj.get("type") == "attack-pattern" and not obj.get("revoked"):
ext_id = ""
for ref in obj.get("external_references", []):
if ref.get("source_name") == "mitre-attack":
ext_id = ref.get("external_id", "")
if ext_id:
tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])]
techniques[obj["id"]] = {
"id": ext_id, "name": obj.get("name", ""),
"tactics": tactics,
"platforms": obj.get("x_mitre_platforms", []),
"detection": obj.get("x_mitre_detection", "")[:200],
}
return techniques
def get_groups(bundle):
groups = {}
for obj in bundle.get("objects", []):
if obj.get("type") == "intrusion-set":
ext_id = ""
for ref in obj.get("external_references", []):
if ref.get("source_name") == "mitre-attack":
ext_id = ref.get("external_id", "")
groups[obj["id"]] = {
"id": ext_id, "name": obj.get("name", ""),
"aliases": obj.get("aliases", []),
"description": obj.get("description", "")[:300],
}
return groups
def map_group_techniques(bundle, group_id, techniques):
ttps = []
for obj in bundle.get("objects", []):
if (obj.get("type") == "relationship" and
obj.get("relationship_type") == "uses" and
obj.get("source_ref") == group_id):
target = obj.get("target_ref", "")
if target in techniques:
ttps.append(techniques[target])
return ttps
def tactic_coverage(ttps):
tactic_map = defaultdict(list)
for t in ttps:
for tactic in t["tactics"]:
tactic_map[tactic].append(t["id"])
return {k: {"count": len(v), "techniques": v} for k, v in tactic_map.items()}
def detection_gaps(ttps, existing_detections):
covered = set(existing_detections)
gaps = [t for t in ttps if t["id"] not in covered]
coverage = 1 - (len(gaps) / len(ttps)) if ttps else 0
return gaps, round(coverage * 100, 1)
def find_group(groups, query):
query_lower = query.lower()
for gid, g in groups.items():
if (g["name"].lower() == query_lower or
g["id"].lower() == query_lower or
query_lower in [a.lower() for a in g["aliases"]]):
return gid, g
return None, None
if __name__ == "__main__":
print("=" * 60)
print("Threat Actor TTP Analysis Agent (MITRE ATT&CK)")
print("TTP mapping, tactic coverage, detection gap analysis")
print("=" * 60)
group_query = sys.argv[1] if len(sys.argv) > 1 else None
bundle_path = sys.argv[2] if len(sys.argv) > 2 else None
bundle = load_attack_bundle(bundle_path)
if not bundle:
print("[!] Cannot load ATT&CK data")
print("[DEMO] Usage: python agent.py APT29 [enterprise-attack.json]")
sys.exit(1)
techniques = get_techniques(bundle)
groups = get_groups(bundle)
print(f"[*] Loaded {len(techniques)} techniques, {len(groups)} groups")
if not group_query:
print("\n--- Available Groups (sample) ---")
for gid, g in list(groups.items())[:15]:
print(f" {g['id']:8s} {g['name']}")
sys.exit(0)
gid, ginfo = find_group(groups, group_query)
if not ginfo:
print(f"[!] Group not found: {group_query}")
sys.exit(1)
print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})")
print(f" Aliases: {', '.join(ginfo['aliases'][:5])}")
ttps = map_group_techniques(bundle, gid, techniques)
print(f" Techniques: {len(ttps)}")
coverage = tactic_coverage(ttps)
print("\n--- Tactic Coverage ---")
for tactic, info in sorted(coverage.items(), key=lambda x: -x[1]["count"]):
bar = "#" * info["count"]
print(f" {tactic:35s} {info['count']:3d} {bar}")
sample_detections = [t["id"] for t in ttps[:len(ttps)//2]]
gaps, pct = detection_gaps(ttps, sample_detections)
print(f"\n--- Detection Gaps (demo: {pct}% coverage) ---")
for g in gaps[:10]:
print(f" [GAP] {g['id']:12s} {g['name']}")