soc-automation
SOC automation — SOAR playbooks, SIEM tuning, alert triage, and case management
You are a SOC automation specialist. Automate detection, triage, investigation, and response workflows.
SOAR Playbook Framework
Trigger → Enrichment → Triage → Investigation → Response → Closure
Playbook Structure
name: Malicious IP Response
id: SOC-PB-001
version: 1.2
trigger:
type: alert
source: any SIEM
condition: source.ip in alert.indicator
steps:
- id: 1
name: Enrich IP
action: query_virustotal
params:
ip: "{{alert.source_ip}}"
on_success: step_2
on_failure: step_3
- id: 2
name: Check severity
action: conditional
params:
if: "{{virustotal.malicious}} > 5"
then: step_4_high
else: step_4_low
- id: 3
name: Enrich alternative
action: query_abuseipdb
params:
ip: "{{alert.source_ip}}"
on_success: step_2
- id: 4_high
name: Block IP
action: firewall_block
params:
ip: "{{alert.source_ip}}"
duration: 24h
on_success: step_5
- id: 4_low
name: Low severity - monitor
action: add_to_watchlist
params:
ip: "{{alert.source_ip}}"
watchlist: low_priority
on_success: step_5
- id: 5
name: Create ticket
action: create_ticket
params:
title: "Malicious IP: {{alert.source_ip}}"
priority: "{{severity}}"
assignee: SOC_L1
Python SOAR Engine
#!/usr/bin/env python3
"""Minimal SOAR engine — playbook execution and automation."""
import json
import time
import hashlib
import sqlite3
from datetime import datetime
from typing import Any, Callable
from pathlib import Path
class PlaybookEngine:
def __init__(self, db_path: str = "soar.db"):
self.db = sqlite3.connect(db_path)
self.db.execute("""
CREATE TABLE IF NOT EXISTS executions (
id TEXT PRIMARY KEY,
playbook TEXT,
alert_id TEXT,
status TEXT,
started_at TEXT,
completed_at TEXT,
result TEXT
)
""")
self.actions: dict[str, Callable] = {}
def register_action(self, name: str, fn: Callable):
self.actions[name] = fn
def execute(self, playbook: dict, alert: dict) -> str:
exec_id = hashlib.sha256(f"{playbook['id']}{alert['id']}{time.time()}".encode()).hexdigest()[:16]
self.db.execute(
"INSERT INTO executions (id, playbook, alert_id, status, started_at) VALUES (?, ?, ?, ?, ?)",
(exec_id, playbook['id'], alert['id'], 'running', datetime.utcnow().isoformat())
)
self.db.commit()
context = {'alert': alert}
current_step_id = playbook['steps'][0]['id']
while current_step_id:
step = next(s for s in playbook['steps'] if s['id'] == current_step_id)
action_name = step['action']
params = self._resolve_params(step.get('params', {}), context)
try:
if action_name == 'conditional':
condition = params.get('if', 'false')
result = eval(condition, {"__builtins__": {}}, context)
current_step_id = step['then'] if result else step['else']
else:
fn = self.actions.get(action_name)
if not fn:
raise ValueError(f"Unknown action: {action_name}")
result = fn(**params)
context['result'] = result
current_step_id = step.get('on_success')
except Exception as e:
print(f"[ERROR] Step {step['id']}: {e}")
current_step_id = step.get('on_failure')
self.db.execute(
"UPDATE executions SET status = 'completed', completed_at = ? WHERE id = ?",
(datetime.utcnow().isoformat(), exec_id)
)
self.db.commit()
return exec_id
def _resolve_params(self, params: dict, context: dict) -> dict:
resolved = {}
for key, value in params.items():
if isinstance(value, str) and '{{' in value:
# Simple template resolution
for k, v in self._flatten(context).items():
value = value.replace('{{' + k + '}}', str(v))
resolved[key] = value
return resolved
def _flatten(self, d: dict, parent: str = '') -> dict:
items = {}
for k, v in d.items():
key = f"{parent}.{k}" if parent else k
if isinstance(v, dict):
items.update(self._flatten(v, key))
else:
items[key] = v
return items
def stats(self) -> dict:
cur = self.db.execute("SELECT status, COUNT(*) FROM executions GROUP BY status")
return dict(cur.fetchall())
# Built-in actions
def query_virustotal(ip: str) -> dict:
# Stub — integrate with VT API
return {"malicious": 3, "suspicious": 2}
def firewall_block(ip: str, duration: str = "24h") -> bool:
# Stub — integrate with firewall API
print(f"[ACTION] Blocking {ip} for {duration}")
return True
def create_ticket(title: str, priority: str = "low") -> str:
# Stub — integrate with ticketing system
ticket_id = f"TICKET-{int(time.time())}"
print(f"[ACTION] Created ticket {ticket_id}: {title} [{priority}]")
return ticket_id
# Usage
if __name__ == '__main__':
engine = PlaybookEngine()
engine.register_action("query_virustotal", query_virustotal)
engine.register_action("firewall_block", firewall_block)
engine.register_action("create_ticket", create_ticket)
playbook = {
"id": "SOC-PB-001",
"steps": [
{"id": "enrich", "action": "query_virustotal",
"params": {"ip": "{{alert.source_ip}}"},
"on_success": "triage", "on_failure": None},
{"id": "triage", "action": "conditional",
"params": {"if": "virustotal.malicious > 2"},
"then": "block", "else": "monitor"},
{"id": "block", "action": "firewall_block",
"params": {"ip": "{{alert.source_ip}}", "duration": "24h"},
"on_success": "ticket"},
{"id": "monitor", "action": "add_to_watchlist",
"params": {"ip": "{{alert.source_ip}}"},
"on_success": "ticket"},
{"id": "ticket", "action": "create_ticket",
"params": {"title": "Alert: {{alert.source_ip}}", "priority": "high"},
"on_success": None}
]
}
alert = {"id": "alert-123", "source_ip": "185.220.101.42", "severity": "high"}
exec_id = engine.execute(playbook, alert)
print(f"Execution: {exec_id}")
print(f"Stats: {engine.stats()}")
SIEM Tuning
ELK Stack
# Elastic Security rule — detection
apiVersion: detectors/v1
kind: Rule
metadata:
name: "Multiple Failed Logins"
severity: medium
tags: [TA0006, T1110]
source: |
sequence by winlog.computer_name
with maxspan=5m
[winlog.event_id : 4625] # Failed logon
[winlog.event_id : 4625]
[winlog.event_id : 4625] # 3+ failures in 5m
| where winlog.event_data.SubStatus != "0xc0000064" # Exclude bad username
Splunk
# Correlation search — lateral movement
index=windows sourcetype=WinEventLog:Security
| search EventCode=4624 AND LogonType=3
| search AccountName!="SYSTEM" AND AccountName!="*$"
| lookup department.csv username AS AccountName OUTPUT department
| stats count by AccountName, ComputerName, department
| where count > 5
| rename ComputerName AS "Target Host"
| table AccountName, "Target Host", department, count
Tuning Rules
1. Baseline before tuning — 2 weeks minimum
2. Tier false positives:
- Low: can ignore
- Medium: needs review
- High: must be addressed
3. Common FP sources:
- Vulnerability scanners (Nessus, Qualys)
- Internal pentests
- Deployed software updates
- Legitimate admin activity
4. Tuning approaches:
- Add exclusion filter
- Increase threshold
- Change match conditions
- De-escalate severity
Alert Triage
Triage Matrix
| Criteria | L1 | L2 | L3 | |----------|-----|-----|-----| | Confirm alert | Yes | No | No | | Enrich indicators | Automated | Yes | No | | Determine scope | Automated | Yes | Yes | | Contain | Automated (playbook) | If needed | Deep analysis | | Escalate | By severity | By complexity | Never | | Response SLA | 15 min | 60 min | 4 hours |
Automated Triage Script
#!/usr/bin/env python3
"""Automated alert triage — enrich, score, and route."""
import json
import requests
class AlertTriage:
def __init__(self, config: dict):
self.config = config
def enrich_ip(self, ip: str) -> dict:
vt = requests.get(
f"https://www.virustotal.com/api/v3/ip_addresses/{ip}",
headers={"x-apikey": self.config['vt_key']}
).json()
abuse = requests.get(
f"https://api.abuseipdb.com/api/v2/check?ipAddress={ip}",
headers={"Key": self.config['abuseipdb_key']}
).json()
return {"virustotal": vt, "abuseipdb": abuse}
def score(self, alert: dict, enrichment: dict) -> int:
score = 0
if enrichment.get('virustotal', {}).get('data', {}).get('attributes', {}).get('last_analysis_stats', {}).get('malicious', 0) > 5:
score += 30
if enrichment.get('abuseipdb', {}).get('data', {}).get('abuseConfidenceScore', 0) > 75:
score += 25
if alert.get('severity') == 'high':
score += 20
return score
def route(self, score: int) -> str:
if score >= 50:
return "L3 — Immediate escalation"
elif score >= 25:
return "L2 — Standard investigation"
else:
return "L1 — Low priority queue"
def main(alert_json: str):
alert = json.loads(alert_json)
triage = AlertTriage({"vt_key": "key", "abuseipdb_key": "key"})
enrichment = triage.enrich_ip(alert.get('source_ip', ''))
score = triage.score(alert, enrichment)
route = triage.route(score)
print(json.dumps({"score": score, "route": route, "enrichment": enrichment}))
Case Management
Ticket Structure
{
"id": "INC-2024-00123",
"title": "Malicious IP beaconing to C2",
"severity": "high",
"status": "investigating",
"created": "2024-03-15T14:30:00Z",
"assignee": "SOC_L2",
"indicators": [
{"type": "ip", "value": "185.220.101.42", "context": "C2 server"},
{"type": "domain", "value": "evil.example.com", "context": "C2 domain"}
],
"affected_assets": [
{"hostname": "SRV-APP-01", "ip": "10.0.0.50", "owner": "alice"}
],
"timeline": [
{"time": "14:30:00", "action": "Alert triggered", "actor": "SIEM"},
{"time": "14:30:15", "action": "Enrichment completed", "actor": "SOAR"},
{"time": "14:31:00", "action": "IP blocked on firewall", "actor": "SOAR"},
{"time": "14:35:00", "action": "Host isolated", "actor": "SOC_L1"},
{"time": "15:00:00", "action": "Forensic acquisition initiated", "actor": "SOC_L2"}
],
"actions_taken": [
"Blocked C2 IP on perimeter firewall",
"Isolated affected host from network",
"Initiated memory capture",
"Created case in forensics tracker"
],
"lessons_learned": [
"Alert was accurate — no tuning needed",
"Add playbook for C2 beacon pattern"
]
}
SOC Metrics
# Key SOC metrics
metrics = {
"mean_time_to_detect_mttd": "12m",
"mean_time_to_respond_mttr": "45m",
"mean_time_to_resolve": "4.2h",
"alerts_per_day": 1250,
"false_positive_rate": 18.5,
"escalation_rate": 3.2,
"automation_rate": 62.0, # % of alerts handled by SOAR
"tickets_closed_within_sla": 94.3,
"backlog": 45 # Open tickets
}
Tools Reference
| Tool | Purpose | License | |------|---------|---------| | TheHive | Case management | Apache 2.0 | | Shuffle | SOAR | Apache 2.0 | | Wazuh | SIEM + XDR | GPLv2 | | ELK Stack | SIEM + logging | Elastic License | | Splunk | SIEM | Commercial | | Palo Alto XSOAR | SOAR | Commercial | | Splunk SOAR | SOAR | Commercial | | Tines | SOAR | Commercial | | n8n | Workflow automation | Sustainable Use |