← THE INDEX  ·  DETECTION ENG

SOC Automation Lab

An end-to-end SOC lab: Wazuh fires the alert, Shuffle enriches it, TheHive gets a pre-populated case. Hands off from rule match to analyst notification.

SOC Automation Lab

The problem it solves

Three things happened by hand on every alert: pull reputation data on the indicator, open a case in TheHive, ping the analyst channel. Doing that for every level-10-and-above rule match in a lab is tedious and slow; doing it in a real environment at volume is not sustainable. The playbook does all of it in the few seconds between Wazuh firing and an analyst opening TheHive.

The pipeline: telemetry from Windows and Linux endpoints reaches the Wazuh manager via agent. When a rule match clears the severity threshold, a Python integration posts the alert to a Shuffle webhook. Shuffle pulls VirusTotal and OTX reputation on the offending indicator, opens a TheHive case with the verdict attached, sets severity from the score, and drops a message in the analyst channel. Everything after the rule match is hands-off.

integrations/custom-thehive.py: Wazuh alert → Shuffle webhook bridge
def build_payload(alert):
    rule = alert.get("rule", {})
    agent = alert.get("agent", {})
    data = alert.get("data", {})
    return {
        "source": "wazuh",
        "rule_id": rule.get("id"),
        "rule_level": rule.get("level"),
        "rule_description": rule.get("description"),
        "mitre": rule.get("mitre", {}),
        "agent_id": agent.get("id"),
        "agent_name": agent.get("name"),
        "agent_ip": agent.get("ip"),
        "src_ip": data.get("srcip"),
        "dst_ip": data.get("dstip"),
        "full_log": alert.get("full_log"),
        "timestamp": alert.get("timestamp"),
        "raw": alert,
    }


def post(hook_url, payload):
    body = json.dumps(payload).encode("utf-8")
    req = request.Request(
        hook_url,
        data=body,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with request.urlopen(req, timeout=TIMEOUT) as response:
        return response.status

What the rules cover

The custom rules in local_rules.xml add detections on top of the Wazuh default ruleset for the cases I wanted automated:

  • Process launches from user-writable paths (AppData, Temp, ProgramData) via Sysmon Event ID 1
  • Office application spawning a scripting host (cmd, powershell, wscript, mshta)
  • LSASS access with the granted-access masks associated with credential dumping tools
  • New Windows service creation and scheduled-task persistence
  • SSH and RDP brute force above the default threshold
  • Outbound connections to indicators in the CTI watchlists generated by the CTI detection automation pipeline

Rules at level 10 and above hand off to the Shuffle integration. Everything below stays in the dashboard.

Running it

The full stack (Wazuh indexer, manager, dashboard, TheHive, Cassandra, Elasticsearch, and Cortex) comes up with ./scripts/deploy.sh after setting the indexer and TheHive passwords in .env. Shuffle runs as a separate compose stack so it survives a SIEM teardown.

Endpoints enroll via agent scripts for Windows and Linux. The Windows group ships a Sysmon-aware config so process-creation and network events arrive with enough context for the custom rules to be useful. This is a single-node lab build; Wazuh indexer and TheHive's Cassandra both want dedicated nodes and real headroom under production load.