mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-15 23:44: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.8 KiB
Python
198 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for threat emulation with Atomic Red Team test execution."""
|
|
|
|
import json
|
|
import yaml
|
|
import argparse
|
|
import shlex
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
|
|
def load_atomic_tests(atomics_path, technique_id):
|
|
"""Load Atomic Red Team test definitions for a technique."""
|
|
technique_dir = Path(atomics_path) / technique_id
|
|
yaml_path = technique_dir / f"{technique_id}.yaml"
|
|
if not yaml_path.exists():
|
|
return None
|
|
with open(yaml_path) as f:
|
|
return yaml.safe_load(f)
|
|
|
|
|
|
def list_available_techniques(atomics_path):
|
|
"""List all available Atomic Red Team techniques."""
|
|
techniques = []
|
|
atomics_dir = Path(atomics_path)
|
|
for technique_dir in sorted(atomics_dir.iterdir()):
|
|
if technique_dir.is_dir() and technique_dir.name.startswith("T"):
|
|
yaml_file = technique_dir / f"{technique_dir.name}.yaml"
|
|
if yaml_file.exists():
|
|
with open(yaml_file) as f:
|
|
data = yaml.safe_load(f)
|
|
techniques.append({
|
|
"technique_id": technique_dir.name,
|
|
"name": data.get("display_name", ""),
|
|
"test_count": len(data.get("atomic_tests", [])),
|
|
"platforms": list(set(
|
|
p for t in data.get("atomic_tests", [])
|
|
for p in t.get("supported_platforms", [])
|
|
)),
|
|
})
|
|
return techniques
|
|
|
|
|
|
def get_test_details(atomics_path, technique_id):
|
|
"""Get detailed information about tests for a technique."""
|
|
data = load_atomic_tests(atomics_path, technique_id)
|
|
if not data:
|
|
return []
|
|
tests = []
|
|
for i, test in enumerate(data.get("atomic_tests", [])):
|
|
tests.append({
|
|
"test_number": i + 1,
|
|
"name": test.get("name", ""),
|
|
"description": test.get("description", ""),
|
|
"platforms": test.get("supported_platforms", []),
|
|
"executor": test.get("executor", {}).get("name", ""),
|
|
"command": test.get("executor", {}).get("command", "")[:200],
|
|
"cleanup": test.get("executor", {}).get("cleanup_command", "")[:200],
|
|
"input_arguments": list(test.get("input_arguments", {}).keys()),
|
|
})
|
|
return tests
|
|
|
|
|
|
def execute_atomic_test(atomics_path, technique_id, test_number=1, platform="linux"):
|
|
"""Execute an Atomic Red Team test using atomic-operator."""
|
|
try:
|
|
from atomic_operator import AtomicOperator
|
|
operator = AtomicOperator()
|
|
result = operator.run(
|
|
technique=technique_id,
|
|
atomics_path=str(atomics_path),
|
|
test_numbers=[test_number],
|
|
)
|
|
return {"status": "executed", "technique": technique_id,
|
|
"test_number": test_number, "result": str(result)}
|
|
except ImportError:
|
|
return execute_atomic_manual(atomics_path, technique_id, test_number, platform)
|
|
|
|
|
|
def execute_atomic_manual(atomics_path, technique_id, test_number, platform):
|
|
"""Execute atomic test manually by parsing YAML and running commands."""
|
|
data = load_atomic_tests(atomics_path, technique_id)
|
|
if not data:
|
|
return {"status": "error", "message": f"Technique {technique_id} not found"}
|
|
tests = data.get("atomic_tests", [])
|
|
if test_number > len(tests):
|
|
return {"status": "error", "message": f"Test {test_number} not found"}
|
|
test = tests[test_number - 1]
|
|
executor = test.get("executor", {})
|
|
command = executor.get("command", "")
|
|
if not command:
|
|
return {"status": "error", "message": "No command defined"}
|
|
for arg_name, arg_def in test.get("input_arguments", {}).items():
|
|
default = str(arg_def.get("default", ""))
|
|
command = command.replace(f"#{{{arg_name}}}", shlex.quote(default))
|
|
try:
|
|
# shell=True required: Atomic Red Team commands are shell scripts by design
|
|
result = subprocess.run(
|
|
command, shell=True, capture_output=True, text=True, timeout=60,
|
|
)
|
|
return {
|
|
"status": "executed",
|
|
"technique": technique_id,
|
|
"test_name": test.get("name", ""),
|
|
"return_code": result.returncode,
|
|
"stdout": result.stdout[:500],
|
|
"stderr": result.stderr[:500],
|
|
}
|
|
except subprocess.TimeoutExpired:
|
|
return {"status": "timeout", "technique": technique_id}
|
|
|
|
|
|
def run_cleanup(atomics_path, technique_id, test_number=1):
|
|
"""Run cleanup commands for an atomic test."""
|
|
data = load_atomic_tests(atomics_path, technique_id)
|
|
if not data:
|
|
return {"status": "error"}
|
|
tests = data.get("atomic_tests", [])
|
|
if test_number > len(tests):
|
|
return {"status": "error"}
|
|
test = tests[test_number - 1]
|
|
cleanup_cmd = test.get("executor", {}).get("cleanup_command", "")
|
|
if not cleanup_cmd:
|
|
return {"status": "no_cleanup_defined"}
|
|
for arg_name, arg_def in test.get("input_arguments", {}).items():
|
|
default = str(arg_def.get("default", ""))
|
|
cleanup_cmd = cleanup_cmd.replace(f"#{{{arg_name}}}", shlex.quote(default))
|
|
try:
|
|
# shell=True required: Atomic Red Team cleanup commands are shell scripts by design
|
|
subprocess.run(cleanup_cmd, shell=True, capture_output=True, timeout=30)
|
|
return {"status": "cleaned_up", "technique": technique_id}
|
|
except subprocess.TimeoutExpired:
|
|
return {"status": "cleanup_timeout"}
|
|
|
|
|
|
def build_coverage_matrix(atomics_path, detection_rules):
|
|
"""Compare available atomic tests against detection rules for gap analysis."""
|
|
techniques = list_available_techniques(atomics_path)
|
|
covered = set()
|
|
for rule in detection_rules:
|
|
for tag in rule.get("tags", []):
|
|
if tag.startswith("attack.t"):
|
|
covered.add(tag.replace("attack.", "").upper())
|
|
matrix = []
|
|
for t in techniques:
|
|
tid = t["technique_id"]
|
|
matrix.append({
|
|
"technique_id": tid,
|
|
"name": t["name"],
|
|
"has_atomic_test": True,
|
|
"has_detection_rule": tid in covered,
|
|
"gap": tid not in covered,
|
|
})
|
|
return matrix
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Atomic Red Team Threat Emulation Agent")
|
|
parser.add_argument("--atomics-path", default="./atomic-red-team/atomics")
|
|
parser.add_argument("--technique", help="ATT&CK technique ID (e.g., T1059.001)")
|
|
parser.add_argument("--test-number", type=int, default=1)
|
|
parser.add_argument("--output", default="atomic_report.json")
|
|
parser.add_argument("--action", choices=[
|
|
"list", "details", "execute", "cleanup", "coverage"
|
|
], default="list")
|
|
args = parser.parse_args()
|
|
|
|
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
|
|
|
|
if args.action == "list":
|
|
techniques = list_available_techniques(args.atomics_path)
|
|
report["results"]["techniques"] = techniques
|
|
print(f"[+] Available techniques: {len(techniques)}")
|
|
|
|
if args.action == "details" and args.technique:
|
|
tests = get_test_details(args.atomics_path, args.technique)
|
|
report["results"]["tests"] = tests
|
|
print(f"[+] Tests for {args.technique}: {len(tests)}")
|
|
|
|
if args.action == "execute" and args.technique:
|
|
result = execute_atomic_test(args.atomics_path, args.technique, args.test_number)
|
|
report["results"]["execution"] = result
|
|
print(f"[+] Executed {args.technique} test #{args.test_number}: {result['status']}")
|
|
|
|
if args.action == "cleanup" and args.technique:
|
|
result = run_cleanup(args.atomics_path, args.technique, args.test_number)
|
|
report["results"]["cleanup"] = result
|
|
print(f"[+] Cleanup: {result['status']}")
|
|
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|