Files
Anthropic-Cybersecurity-Skills/skills/hunting-advanced-persistent-threats/scripts/agent.py
T
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

165 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""APT threat hunting agent using MITRE ATT&CK, attackcti, and osquery."""
import json
import sys
import argparse
from datetime import datetime
try:
from attackcti import attack_client
except ImportError:
print("Install attackcti: pip install attackcti")
sys.exit(1)
def get_apt_group_ttps(group_name):
"""Retrieve TTPs for a specific APT group from MITRE ATT&CK."""
client = attack_client()
groups = client.get_groups()
target = None
for g in groups:
aliases = [a.lower() for a in g.get("aliases", [])]
if group_name.lower() in g["name"].lower() or group_name.lower() in aliases:
target = g
break
if not target:
print(f"[!] Group '{group_name}' not found in ATT&CK")
return None
techniques = client.get_techniques_used_by_group(target)
return {"group": target["name"], "id": target["external_references"][0]["external_id"],
"techniques": [{"id": t["external_references"][0]["external_id"],
"name": t["name"],
"tactic": [p["phase_name"] for p in t.get("kill_chain_phases", [])]}
for t in techniques]}
def generate_osquery_hunts(techniques):
"""Generate osquery hunt queries for detected ATT&CK techniques."""
query_map = {
"T1059": ("Process execution (Command and Scripting)",
"SELECT pid, name, cmdline, path, parent FROM processes "
"WHERE name IN ('powershell.exe','cmd.exe','wscript.exe','cscript.exe','bash','python');"),
"T1053": ("Scheduled Task/Job persistence",
"SELECT name, action, path, enabled, last_run_time FROM scheduled_tasks "
"WHERE enabled=1 AND action NOT LIKE '%System32%';"),
"T1547": ("Boot/Logon autostart execution",
"SELECT name, path, source FROM autoexec;"),
"T1071": ("Application layer protocol C2",
"SELECT pid, remote_address, remote_port, local_port FROM process_open_sockets "
"WHERE remote_port IN (443, 8443, 8080, 4443) AND family=2;"),
"T1055": ("Process injection",
"SELECT pid, name, cmdline FROM processes WHERE on_disk=0;"),
"T1003": ("OS credential dumping",
"SELECT pid, name, cmdline FROM processes "
"WHERE name IN ('mimikatz.exe','procdump.exe','ntdsutil.exe') "
"OR cmdline LIKE '%sekurlsa%' OR cmdline LIKE '%lsass%';"),
"T1021": ("Remote services lateral movement",
"SELECT pid, name, cmdline FROM processes "
"WHERE name IN ('psexec.exe','wmic.exe','winrm.cmd') "
"OR cmdline LIKE '%invoke-command%';"),
"T1027": ("Obfuscated files or information",
"SELECT pid, name, cmdline FROM processes "
"WHERE cmdline LIKE '%-enc%' OR cmdline LIKE '%-encodedcommand%';"),
"T1566": ("Phishing initial access",
"SELECT path, filename, size FROM file "
"WHERE directory LIKE '%Downloads%' "
"AND (filename LIKE '%.iso' OR filename LIKE '%.img' OR filename LIKE '%.lnk');"),
"T1218": ("Signed binary proxy execution",
"SELECT pid, name, cmdline, parent FROM processes "
"WHERE name IN ('mshta.exe','rundll32.exe','regsvr32.exe','certutil.exe');"),
}
hunts = []
for tech in techniques:
tech_id = tech["id"].split(".")[0]
if tech_id in query_map:
desc, query = query_map[tech_id]
hunts.append({"technique": tech["id"], "name": tech["name"],
"description": desc, "osquery": query})
return hunts
def generate_sigma_rule(technique_id, technique_name, tactic):
"""Generate a Sigma detection rule for a given technique."""
return {
"title": f"Detect {technique_name} ({technique_id})",
"status": "experimental",
"description": f"Detects potential {technique_name} activity mapped to {technique_id}",
"references": [f"https://attack.mitre.org/techniques/{technique_id.replace('.','/')}/"],
"tags": [f"attack.{t}" for t in tactic] + [f"attack.{technique_id.lower()}"],
"logsource": {"category": "process_creation", "product": "windows"},
"detection": {"selection": {"technique_id": technique_id}, "condition": "selection"},
"level": "medium",
}
def build_hunt_report(group_name):
"""Build a complete threat hunt report for an APT group."""
print(f"\n{'='*70}")
print(f" APT THREAT HUNT REPORT")
print(f" Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC")
print(f"{'='*70}\n")
print(f"[*] Querying MITRE ATT&CK for group: {group_name}")
group_data = get_apt_group_ttps(group_name)
if not group_data:
return
print(f"[+] Found: {group_data['group']} ({group_data['id']})")
print(f"[+] Techniques mapped: {len(group_data['techniques'])}\n")
print(f"--- TECHNIQUE COVERAGE ---")
tactic_counts = {}
for t in group_data["techniques"]:
print(f" [{t['id']}] {t['name']} -> {', '.join(t['tactic'])}")
for tac in t["tactic"]:
tactic_counts[tac] = tactic_counts.get(tac, 0) + 1
print(f"\n--- TACTIC DISTRIBUTION ---")
for tac, count in sorted(tactic_counts.items(), key=lambda x: -x[1]):
bar = "#" * count
print(f" {tac:<30} {bar} ({count})")
print(f"\n--- OSQUERY HUNT QUERIES ---")
hunts = generate_osquery_hunts(group_data["techniques"])
if hunts:
for h in hunts:
print(f"\n Technique: {h['technique']} - {h['description']}")
print(f" Query: {h['osquery']}")
else:
print(" No matching osquery hunts for this group's techniques.")
print(f"\n--- SIGMA RULES ---")
for t in group_data["techniques"][:5]:
rule = generate_sigma_rule(t["id"], t["name"], t["tactic"])
print(f"\n Rule: {rule['title']}")
print(f" Tags: {', '.join(rule['tags'])}")
print(f" Level: {rule['level']}")
print(f"\n--- HUNT RECOMMENDATIONS ---")
print(f" 1. Execute osquery hunts across all endpoints via fleet manager")
print(f" 2. Search SIEM for technique indicators over past 90 days")
print(f" 3. Validate EDR telemetry covers all {len(group_data['techniques'])} techniques")
print(f" 4. Cross-reference with network logs (Zeek/Suricata) for C2 patterns")
print(f" 5. Document findings using Diamond Model analysis framework")
print(f"\n{'='*70}\n")
return group_data
def main():
parser = argparse.ArgumentParser(description="APT Threat Hunting Agent")
parser.add_argument("--group", default="APT29", help="APT group name (e.g., APT29, APT28, Lazarus)")
parser.add_argument("--output", help="Save report to JSON file")
args = parser.parse_args()
report = build_hunt_report(args.group)
if report and args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[+] JSON report saved to {args.output}")
if __name__ == "__main__":
main()