PROJECT_001 · Detection engineering · SOC automation

Wazuh × SOAR SSH brute-force detection & response

A SIEM-SOAR pipeline on Azure: Wazuh detects a live SSH attack, Shuffle triages it by severity, and an analyst is emailed within about five seconds, extended with three custom privilege-escalation rules mapped to MITRE ATT&CK.
haadi@portfolio: ~

haadi@portfolio:~$ eog 05-complete-shuffle-workflow.png

Shuffle SOAR workflow canvas: a webhook trigger connected by a condition to an email action
haadi@portfolio: ~

haadi@portfolio:~$ cat project.yml

technologies
Wazuh 4.14.6, Shuffle SOAR, Azure VM, Docker, Gmail SMTP, MITRE ATT&CK
role
Solo: architecture, deployment, detection tuning, automation
year
2026
status
Complete

How it wasbuilt

haadi@portfolio: ~

haadi@portfolio:~$ cat -n the-problem.txt

  1. A SIEM detects threats, but it doesn't act on them. Without automation, every alert waits for someone to be watching the dashboard, and at thousands of events a day, that doesn't scale.
  2. There's a gap on the other side too: behaviour Wazuh's default ruleset doesn't flag on its own, like an account being quietly added to the sudo group.
haadi@portfolio: ~

haadi@portfolio:~$ cat -n the-approach.txt

  1. I treated it as one pipeline with two ends. Detection engineering decides what gets flagged and how seriously; SOAR decides what happens next. A badly tuned rule level upstream breaks the automation downstream, so both had to be designed together.
  • Forward Wazuh alerts to Shuffle over a webhook, as JSON.
  • Route by severity: only level 10+ reaches an analyst, so low-level noise never causes alert fatigue.
  • Customise detection through local_rules.xml overrides: upgrade-safe, never editing Wazuh's core rules.
  • Test every component in isolation before debugging the pipeline end to end.
haadi@portfolio: ~

haadi@portfolio:~$ cat -n implementation.txt

  1. Wazuh Manager 4.14.6 and Shuffle (backend, frontend, worker, orborus) run as Docker containers on a single Azure Standard B2s VM with Ubuntu Server 22.04. The Wazuh manager's integration block ships every alert at level 10 or above to Shuffle's webhook trigger.
  2. The Shuffle workflow has four stages: webhook trigger, field extraction (rule.id, rule.level), an IF condition on level > 9, then an email via Gmail SMTP, or a silent drop.
haadi@portfolio: ~

haadi@portfolio:~$ cat ossec.conf

<integration>
  <name>shuffle</name>
  <hook_url><SHUFFLE_WEBHOOK_URL></hook_url>
  <level>10</level>
  <alert_format>json</alert_format>
</integration>

What itproved

haadi@portfolio: ~

haadi@portfolio:~$ cat -n security-details.txt

  1. The test alert, Rule 5710, SSH login with a non-existent user, defaults to level 5 and would never pass the threshold. Instead of lowering the threshold (too noisy for real use), a local override (ID 100100) raises that specific rule to level 10, mapped to T1110.001 and T1021.004.
  2. I then wrote three rules for Linux privilege escalation that the default ruleset doesn't meaningfully cover, each tuned to the difference between normal mistakes and suspicious behaviour:
  • 100101

    Scenario

    Repeated failed sudo authentication

    Tuning

    2 failures within 60s: catches brute force, not typos

    Level

    12

    ATT&CK

    T1548.003

  • 100102

    Scenario

    User added to the sudo group

    Tuning

    Scoped to usermod -aG sudo, not any group change

    Level

    12

    ATT&CK

    T1098

  • 100103

    Scenario

    Unauthorized user attempts sudo

    Tuning

    No frequency: suspicious on the first try

    Level

    11

    ATT&CK

    T1548.003

haadi@portfolio: ~

haadi@portfolio:~$ cat -n result.txt

  1. An SSH attempt with an invalid user is detected, forwarded, evaluated and delivered to the inbox in about five seconds, with every workflow node executing successfully. Each custom rule was verified against a real simulated attack on a throwaway account.
  2. Two issues were worth the time: Gmail rejected SMTP credentials (535 5.7.8), isolated with a standalone Python smtplib test and fixed with an App Password, and rule 100102 initially didn't fire because the match checked a different field than the decoder populated, found by reading the raw decoded event.
SSH attempt → email received
~5s
SSH attempt → email received
Pipeline checks passed
7 of 7
Pipeline checks passed
Custom privilege-escalation rules
3
Custom privilege-escalation rules

Evidence fromthe lab

haadi@portfolio: ~

haadi@portfolio:~$ eog auto-02-rule-100101-alert.png

Wazuh alert detail for custom rule 100101, level 12, mapped to MITRE T1548.003
fig.01Rule 100101 firing on repeated failed sudo attempts, level 12, T1548.003.
haadi@portfolio: ~

haadi@portfolio:~$ eog 04-rule-5710-alert-level-10.png

Wazuh alert for rule 5710 raised to level 10
fig.02Rule 5710 (SSH, non-existent user) raised from level 5 to 10 via local override.
haadi@portfolio: ~

haadi@portfolio:~$ eog 06-ifelse-routing-condition.png

Shuffle IF/ELSE routing condition checking rule.level greater than 9
fig.03Severity routing: only rule.level > 9 continues to notification.
haadi@portfolio: ~

haadi@portfolio:~$ eog 07-email-notification-received.png

Automated alert email received in Gmail inbox
fig.04The automated notification landing in the analyst inbox.
haadi@portfolio: ~

haadi@portfolio:~$ eog auto-01-agent-overview.png

Wazuh dashboard showing the monitored Ubuntu agent
fig.05Wazuh agent overview for the monitored Ubuntu endpoint.
haadi@portfolio: ~

haadi@portfolio:~$ eog 03-docker-containers-running.png

Terminal output listing Wazuh and Shuffle Docker containers
fig.06Wazuh and Shuffle running side by side as containers on one VM.

Stack &links

haadi@portfolio: ~

haadi@portfolio:~$ cat -n technologies-used.txt

wazuh 4.14.6shuffle soarazure vmdockergmail smtpmitre att&ck
haadi@portfolio: ~

Next project

ThreatCast PK