mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +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
198 lines
7.7 KiB
Python
198 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for performing purple team exercises.
|
|
|
|
Coordinates red team technique execution with blue team detection
|
|
validation, tracks ATT&CK-mapped test results, and generates
|
|
detection coverage reports.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
|
|
class PurpleTeamAgent:
|
|
"""Manages purple team exercise execution and tracking."""
|
|
|
|
def __init__(self, exercise_id, output_dir):
|
|
self.exercise_id = exercise_id
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.test_plan = []
|
|
self.results = []
|
|
|
|
def add_technique(self, attack_id, name, tool, expected_detection):
|
|
"""Add a technique to the test plan."""
|
|
self.test_plan.append({
|
|
"attack_id": attack_id,
|
|
"name": name,
|
|
"tool": tool,
|
|
"expected_detection": expected_detection,
|
|
"status": "pending",
|
|
})
|
|
|
|
def load_test_plan(self, plan_path):
|
|
"""Load test plan from a JSON file."""
|
|
with open(plan_path, "r") as f:
|
|
data = json.load(f)
|
|
self.test_plan = data.get("techniques", [])
|
|
|
|
def build_default_test_plan(self):
|
|
"""Build a default FIN7-style purple team test plan."""
|
|
techniques = [
|
|
("T1059.001", "PowerShell Execution", "Atomic Red Team", "PowerShell alert"),
|
|
("T1053.005", "Scheduled Task", "Atomic Red Team", "Task creation alert"),
|
|
("T1547.001", "Registry Run Keys", "Atomic Red Team", "Registry modification alert"),
|
|
("T1003.001", "LSASS Memory Access", "Mimikatz", "Credential dumping alert"),
|
|
("T1550.002", "Pass-the-Hash", "Mimikatz", "NTLM anomaly detection"),
|
|
("T1021.002", "PsExec", "PsExec.exe", "PsExec service creation alert"),
|
|
("T1047", "WMI Execution", "wmic", "WMI remote execution alert"),
|
|
("T1021.001", "RDP Lateral Movement", "xfreerdp", "RDP lateral movement alert"),
|
|
("T1071.001", "Web C2 Channel", "C2 framework", "C2 beacon detection"),
|
|
("T1041", "Exfiltration over C2", "rclone", "Data exfiltration alert"),
|
|
("T1490", "Inhibit Recovery", "vssadmin", "Shadow copy deletion alert"),
|
|
("T1070.001", "Clear Event Logs", "wevtutil", "Log clearing detection"),
|
|
]
|
|
for attack_id, name, tool, detection in techniques:
|
|
self.add_technique(attack_id, name, tool, detection)
|
|
|
|
def record_execution(self, attack_id, execution_time=None):
|
|
"""Record that a red team technique has been executed."""
|
|
if execution_time is None:
|
|
execution_time = datetime.utcnow().isoformat()
|
|
for technique in self.test_plan:
|
|
if technique["attack_id"] == attack_id:
|
|
technique["execution_time"] = execution_time
|
|
technique["status"] = "executed"
|
|
break
|
|
|
|
def record_detection(self, attack_id, detected, alert_name=None,
|
|
detection_time=None, notes=""):
|
|
"""Record blue team detection result for a technique."""
|
|
if detection_time is None and detected:
|
|
detection_time = datetime.utcnow().isoformat()
|
|
|
|
for technique in self.test_plan:
|
|
if technique["attack_id"] == attack_id:
|
|
exec_time = technique.get("execution_time", "")
|
|
latency = None
|
|
if detected and exec_time and detection_time:
|
|
try:
|
|
t1 = datetime.fromisoformat(exec_time)
|
|
t2 = datetime.fromisoformat(detection_time)
|
|
latency = (t2 - t1).total_seconds()
|
|
except ValueError:
|
|
pass
|
|
|
|
result = {
|
|
"attack_id": attack_id,
|
|
"name": technique["name"],
|
|
"detected": detected,
|
|
"alert_name": alert_name,
|
|
"execution_time": exec_time,
|
|
"detection_time": detection_time,
|
|
"latency_seconds": latency,
|
|
"notes": notes,
|
|
"status": "PASS" if detected else "FAIL",
|
|
}
|
|
self.results.append(result)
|
|
technique["status"] = "detected" if detected else "gap"
|
|
return result
|
|
return None
|
|
|
|
def get_coverage_metrics(self):
|
|
"""Calculate detection coverage metrics."""
|
|
if not self.results:
|
|
return {}
|
|
|
|
total = len(self.results)
|
|
detected = sum(1 for r in self.results if r["detected"])
|
|
gaps = total - detected
|
|
latencies = [r["latency_seconds"] for r in self.results
|
|
if r["latency_seconds"] is not None]
|
|
avg_latency = sum(latencies) / len(latencies) if latencies else 0
|
|
|
|
return {
|
|
"total_techniques": total,
|
|
"detected": detected,
|
|
"gaps": gaps,
|
|
"coverage_pct": round(detected / total * 100, 1) if total else 0,
|
|
"avg_latency_seconds": round(avg_latency, 1),
|
|
"min_latency": round(min(latencies), 1) if latencies else None,
|
|
"max_latency": round(max(latencies), 1) if latencies else None,
|
|
}
|
|
|
|
def get_gap_analysis(self):
|
|
"""Identify detection gaps requiring remediation."""
|
|
return [
|
|
{
|
|
"attack_id": r["attack_id"],
|
|
"name": r["name"],
|
|
"notes": r["notes"],
|
|
"remediation": f"Create detection rule for {r['name']}",
|
|
}
|
|
for r in self.results if not r["detected"]
|
|
]
|
|
|
|
def generate_report(self):
|
|
"""Generate comprehensive purple team exercise report."""
|
|
metrics = self.get_coverage_metrics()
|
|
gaps = self.get_gap_analysis()
|
|
|
|
report = {
|
|
"exercise_id": self.exercise_id,
|
|
"report_date": datetime.utcnow().isoformat(),
|
|
"coverage_metrics": metrics,
|
|
"detailed_results": self.results,
|
|
"detection_gaps": gaps,
|
|
"test_plan": self.test_plan,
|
|
}
|
|
|
|
report_path = self.output_dir / f"{self.exercise_id}_report.json"
|
|
with open(report_path, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print(f"PURPLE TEAM EXERCISE REPORT - {self.exercise_id}")
|
|
print("=" * 50)
|
|
print(f"Techniques Tested: {metrics.get('total_techniques', 0)}")
|
|
print(f"Detected: {metrics.get('detected', 0)} ({metrics.get('coverage_pct', 0)}%)")
|
|
print(f"Gaps: {metrics.get('gaps', 0)}")
|
|
print(f"Avg Detection Latency: {metrics.get('avg_latency_seconds', 0)}s")
|
|
print(f"\nDetailed Results:")
|
|
for r in self.results:
|
|
status = "PASS" if r["detected"] else "FAIL"
|
|
latency = f"{r['latency_seconds']}s" if r["latency_seconds"] else "N/A"
|
|
print(f" [{status}] {r['attack_id']} {r['name']} (Latency: {latency})")
|
|
|
|
if gaps:
|
|
print(f"\nDetection Gaps:")
|
|
for g in gaps:
|
|
print(f" - {g['attack_id']} {g['name']}: {g['notes']}")
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: agent.py <exercise_id> [output_dir] [plan_file]")
|
|
sys.exit(1)
|
|
|
|
exercise_id = sys.argv[1]
|
|
output_dir = sys.argv[2] if len(sys.argv) > 2 else "./purple_team_output"
|
|
|
|
agent = PurpleTeamAgent(exercise_id, output_dir)
|
|
|
|
if len(sys.argv) > 3:
|
|
agent.load_test_plan(sys.argv[3])
|
|
else:
|
|
agent.build_default_test_plan()
|
|
|
|
print(json.dumps({"test_plan": agent.test_plan}, indent=2))
|
|
print(f"\nTest plan created with {len(agent.test_plan)} techniques")
|
|
print(f"Output directory: {output_dir}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|