#!/usr/bin/env python3 """SIEM detection use case management agent with ATT&CK coverage mapping.""" import json import sys import argparse from datetime import datetime from collections import Counter try: from attackcti import attack_client except ImportError: print("Install attackcti: pip install attackcti") sys.exit(1) try: HAS_SPLUNK = True except ImportError: HAS_SPLUNK = False USE_CASE_TEMPLATES = { "brute_force_login": { "name": "Brute Force Authentication Attempt", "technique": "T1110", "tactic": "credential-access", "data_sources": ["Windows Security 4625", "Linux auth.log", "VPN logs"], "splunk_query": ('index=wineventlog EventCode=4625 ' '| stats count by src_ip, TargetUserName ' '| where count > 10'), "threshold": 10, "severity": "high", "sla_response": "15 minutes", }, "lateral_movement_psexec": { "name": "Lateral Movement via PsExec", "technique": "T1021.002", "tactic": "lateral-movement", "data_sources": ["Windows Security 7045", "Sysmon EventID 1"], "splunk_query": ('index=wineventlog EventCode=7045 ' 'ServiceFileName="*PSEXESVC*" ' '| stats count by ComputerName, ServiceName'), "threshold": 1, "severity": "critical", "sla_response": "5 minutes", }, "suspicious_powershell": { "name": "Suspicious PowerShell Execution", "technique": "T1059.001", "tactic": "execution", "data_sources": ["Sysmon EventID 1", "PowerShell 4104"], "splunk_query": ('index=sysmon EventCode=1 Image="*powershell.exe" ' '(CommandLine="*-enc*" OR CommandLine="*invoke-expression*" ' 'OR CommandLine="*downloadstring*")'), "threshold": 1, "severity": "high", "sla_response": "10 minutes", }, "data_exfiltration_dns": { "name": "DNS-Based Data Exfiltration", "technique": "T1048.003", "tactic": "exfiltration", "data_sources": ["DNS query logs", "Zeek dns.log"], "splunk_query": ('index=dns query_length>50 ' '| stats count dc(query) as unique_queries by src_ip ' '| where unique_queries > 100'), "threshold": 100, "severity": "high", "sla_response": "15 minutes", }, "privilege_escalation_new_admin": { "name": "Privilege Escalation - New Admin Account", "technique": "T1098", "tactic": "persistence", "data_sources": ["Windows Security 4728", "Windows Security 4732"], "splunk_query": ('index=wineventlog (EventCode=4728 OR EventCode=4732) ' 'TargetGroup="Administrators" ' '| stats count by SubjectUserName, MemberName, TargetGroup'), "threshold": 1, "severity": "critical", "sla_response": "5 minutes", }, "credential_dumping_lsass": { "name": "Credential Dumping - LSASS Access", "technique": "T1003.001", "tactic": "credential-access", "data_sources": ["Sysmon EventID 10"], "splunk_query": ('index=sysmon EventCode=10 TargetImage="*lsass.exe" ' 'NOT SourceImage IN ("*\\csrss.exe","*\\services.exe") ' '| stats count by SourceImage, SourceUser'), "threshold": 1, "severity": "critical", "sla_response": "5 minutes", }, "ransomware_file_encryption": { "name": "Ransomware File Encryption Activity", "technique": "T1486", "tactic": "impact", "data_sources": ["Sysmon EventID 11", "Windows Security 4663"], "splunk_query": ('index=sysmon EventCode=11 ' '| stats dc(TargetFilename) as file_count by Image ' '| where file_count > 100'), "threshold": 100, "severity": "critical", "sla_response": "immediate", }, } def get_attack_coverage(techniques_covered): """Calculate ATT&CK coverage percentage.""" client = attack_client() all_techniques = client.get_techniques() enterprise = [t for t in all_techniques if any("enterprise-attack" in ref.get("url", "") for ref in t.get("external_references", []))] total = len(enterprise) covered = len(set(techniques_covered)) return {"total_techniques": total, "covered": covered, "coverage_pct": round(covered / max(total, 1) * 100, 1)} def map_use_cases_to_attack(): """Map all use case templates to ATT&CK techniques and tactics.""" tactic_coverage = Counter() technique_list = [] for uc_id, uc in USE_CASE_TEMPLATES.items(): tactic_coverage[uc["tactic"]] += 1 technique_list.append(uc["technique"]) return {"tactics": dict(tactic_coverage), "techniques": technique_list, "total_use_cases": len(USE_CASE_TEMPLATES)} def validate_use_case_data_sources(use_case_id): """Validate that required data sources are available for a use case.""" uc = USE_CASE_TEMPLATES.get(use_case_id) if not uc: return {"error": f"Use case {use_case_id} not found"} return { "use_case": uc["name"], "required_data_sources": uc["data_sources"], "validation_note": "Verify these log sources are ingested into SIEM with correct parsing", } def generate_sigma_rule(use_case_id): """Generate a Sigma detection rule for a use case.""" uc = USE_CASE_TEMPLATES.get(use_case_id) if not uc: return None return { "title": uc["name"], "id": f"sigma-{use_case_id}", "status": "experimental", "description": f"Detects {uc['name']} mapped to ATT&CK {uc['technique']}", "references": [f"https://attack.mitre.org/techniques/{uc['technique'].replace('.', '/')}/"], "tags": [f"attack.{uc['tactic']}", f"attack.{uc['technique'].lower()}"], "logsource": {"product": "windows", "service": "security"}, "detection": {"condition": "selection"}, "level": uc["severity"], "falsepositives": ["Legitimate administrative activity"], } def run_detection_coverage_report(): """Generate SIEM detection coverage report.""" print(f"\n{'='*60}") print(f" SIEM DETECTION USE CASE REPORT") print(f" Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC") print(f"{'='*60}\n") mapping = map_use_cases_to_attack() print(f"--- USE CASE LIBRARY ({mapping['total_use_cases']} rules) ---") for uc_id, uc in USE_CASE_TEMPLATES.items(): print(f" [{uc['severity'].upper():>8}] {uc['name']}") print(f" ATT&CK: {uc['technique']} ({uc['tactic']}) | SLA: {uc['sla_response']}") print(f"\n--- TACTIC COVERAGE ---") for tactic, count in sorted(mapping["tactics"].items(), key=lambda x: -x[1]): bar = "#" * count print(f" {tactic:<25} {bar} ({count})") print(f"\n--- ATT&CK COVERAGE ---") try: coverage = get_attack_coverage(mapping["techniques"]) print(f" Total Enterprise Techniques: {coverage['total_techniques']}") print(f" Covered by Use Cases: {coverage['covered']}") print(f" Coverage Percentage: {coverage['coverage_pct']}%") except Exception as e: print(f" Could not calculate coverage: {e}") print(f"\n--- DATA SOURCE REQUIREMENTS ---") all_sources = set() for uc in USE_CASE_TEMPLATES.values(): all_sources.update(uc["data_sources"]) for src in sorted(all_sources): print(f" - {src}") print(f"\n{'='*60}\n") return {"use_cases": mapping, "data_sources": list(all_sources)} def main(): parser = argparse.ArgumentParser(description="SIEM Use Case Detection Agent") parser.add_argument("--report", action="store_true", help="Generate detection coverage report") parser.add_argument("--sigma", help="Generate Sigma rule for use case ID") parser.add_argument("--validate", help="Validate data sources for use case ID") parser.add_argument("--output", help="Save report to JSON") args = parser.parse_args() if args.report: report = run_detection_coverage_report() if args.output: with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) elif args.sigma: rule = generate_sigma_rule(args.sigma) print(json.dumps(rule, indent=2) if rule else f"Use case '{args.sigma}' not found") elif args.validate: result = validate_use_case_data_sources(args.validate) print(json.dumps(result, indent=2)) else: parser.print_help() if __name__ == "__main__": main()