Files
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

228 lines
8.7 KiB
Python

#!/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()