# 3 Wazuh detections I wrote and tested in my home lab

Three detections from my home SOC lab, each written by hand for Wazuh, tested against a real attack, and checked for false positives. For every one I followed the same loop: **trigger the attack → find the raw event → check what Wazuh already does → write the rule → trigger again → try something harmless that looks similar**. The lab itself (Proxmox, OPNsense, Wazuh, Sysmon) is described in [My InfoSec HomeLab](https://braindump.0x8r41nr07.xyz/my-infosec-homelab).

All rules, as Wazuh XML and as portable Sigma, are in [detection-lab on GitHub](https://github.com/0x8R41NR07/detection-lab).

| # | Detects | ATT&CK | Data source | Wazuh rule |
| --- | --- | --- | --- | --- |
| 1 | PowerShell with an encoded command | T1059.001 | Sysmon Event 1 | 100101, level 12 |
| 2 | Scheduled task created with `schtasks` | T1053.005 | Sysmon Event 1 | 100103, level 10 |
| 3 | SSH brute force | T1110.001 | Linux auth log | 100105, level 12 |

---

## 1\. PowerShell started with an encoded command (T1059.001)

**What it is:** PowerShell accepts a whole command as Base64 after `-EncodedCommand`. Attackers use it so the real command doesn't appear in plain text. PowerShell also accepts any unambiguous shortening of the flag, and `-enc` is the one you see most in the wild.

**Test:** a harmless command, encoded by hand:

```powershell
$t = [Text.Encoding]::Unicode.GetBytes('Write-Host sprint30')
$b = [Convert]::ToBase64String($t)
powershell.exe -enc $b
```

![Sysmon Event 1: powershell.exe with -enc and a Base64 blob](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d01-raw-event.png align="center")

**What Wazuh does by default: surprisingly little.** The built-in rule 92057 (*base64 encoded command*, level 12) stayed silent. Its regex lists `-e`, `-ec`, `-en`, `-enco` and others, but **not** `-enc`, and it also requires the parent to be PowerShell. The event only hit 92027 at level 4, which nobody looks at.

**Rule:**

```xml
<rule id="100101" level="12">
  <!-- 61603 = every Sysmon Event 1; 92027/92057 = built-ins that can win the match first -->
  <if_sid>61603, 92027, 92057</if_sid>
  <field name="win.eventdata.image" type="pcre2">(?i)\\powershell\.exe$</field>
  <field name="win.eventdata.commandLine" type="pcre2">(?i)\s-e(c|nc|ncodedcommand)?\s</field>
  <description>Sprint30 D1: PowerShell encoded command</description>
  <mitre><id>T1059.001</id></mitre>
</rule>
```

The lesson behind the three parents: in Wazuh a child rule is only checked under a parent that matched. If a built-in rule on the same event wins first, a rule hanging only under the base rule 61603 never gets a chance.

![Wazuh alert: the -enc command line from WIN11](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d01-alert.png align="center")

**Validation:** `-enc` from PowerShell ✅ fires · `-enc` from `cmd.exe` ✅ fires (92057 stays silent) · `-Command "Get-Date"` ✅ no alert.

**What it misses:** PowerShell accepts *every* shortening (`-en`, `-encod`, `-encodedcomman` …), a slash instead of a dash, and Unicode dash look-alikes. A wider regex for that is in the repo notes. Also out of scope: `pwsh.exe` and a renamed `powershell.exe`.

---

## 2\. Scheduled task created via schtasks (T1053.005)

**What it is:** a scheduled task lets an attacker run something again at every logon or boot. With `/ru system` it runs as SYSTEM. `schtasks.exe` is built into Windows, so nothing has to be downloaded.

**Test:** Atomic Red Team, *Scheduled Task Startup Script*. It creates two tasks that start `calc.exe`, one at logon and one at boot as SYSTEM.

**Rule:**

```xml
<rule id="100103" level="10">
  <if_sid>61603</if_sid>
  <field name="win.eventdata.image" type="pcre2">(?i)\\schtasks\.exe$</field>
  <field name="win.eventdata.commandLine" type="pcre2">(?i)\s/create\s</field>
  <description>Sprint30 D3: scheduled task created via schtasks</description>
  <mitre><id>T1053.005</id></mitre>
</rule>
```

![Wazuh alert 100103: schtasks /create ... /ru system](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d03-alert.png align="center")

**Validation:** the Atomic test ✅ 2 alerts (one per task) · `schtasks /query` ✅ no alert.

**The interesting part, the gap:** PowerShell's `Register-ScheduledTask` talks to the Task Scheduler API directly, so `schtasks.exe` never starts and my rule sees nothing. I tested it: **100103 stayed silent**. What did fire was Windows Security event **4698** (*a scheduled task was created*, Wazuh rule 60228), which contains the full task XML, but only at **level 4**.

![Register-ScheduledTask: no 100103, but event 4698 with the task name s30test](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d03-gap.png align="center")

So 100103 detects the **tool**, while 4698 detects the **result**, however the task was made. The better design is both: a raised-level rule on 4698 for suspicious task content (`cmd`/`powershell` actions, SYSTEM, paths in `%TEMP%`), plus 100103 for the command-line context. That's my next iteration.

---

## 3\. SSH brute force (T1110.001)

**What it is:** password guessing against SSH. One wrong password is a typo; eight in a minute from the same IP is a tool.

**Test:** Hydra from Kali against a throwaway lab account, with the top 200 passwords of `rockyou`:

```bash
hydra -l labtest -P top200.txt -t 4 ssh://10.10.10.30
```

![Hydra finds the (blurred) lab password in about two minutes](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/ir01-hydra.png align="center")

**What Wazuh does by default: a lot.** Rule 5760 fires for every failed password, and 5763 *brute force*, 2502, 5551 and 40111 (all level 10) fired within seconds. The most valuable one: **40112, *multiple authentication failures followed by a success*** (level 12).

![Built-in alerts during the attack: 5760, 2502, 5763 and 40112](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d05-builtin.png align="center")

**Rule:** counts Wazuh's own failure alerts per source IP.

```xml
<rule id="100105" level="12" frequency="8" timeframe="60">
  <!-- 5760 = sshd: authentication failed -->
  <if_matched_sid>5760</if_matched_sid>
  <same_srcip />
  <description>Sprint30 D5: SSH brute force, 8+ fails in 60s from one IP</description>
  <mitre><id>T1110.001</id></mitre>
</rule>
```

![Wazuh alert 100105: level 12, srcip, frequency 8, T1110.001](https://raw.githubusercontent.com/0x8R41NR07/detection-lab/main/docs/img/d05-alert.png align="center")

**Validation:** Hydra ✅ fires within seconds (46 alerts in one run) · one typo followed by the correct password ✅ no alert.

**Honest notes:**

- This rule doesn't fill a hole: the built-ins already caught it. What it adds is a clear name, an ATT&CK ID and a threshold I control.
- **Sigma can't express it.** A Sigma rule describes one event, and one failed password isn't an attack. The Sigma file in the repo matches the single `Failed password` line; the counting lives in Wazuh.
- **The real gap is after the attack.** When I logged in with the cracked password four minutes later, Wazuh only produced level-3 *login success* events. Nothing linked them to the brute force. Next rule: *successful login from an IP that just triggered 100105*.
- In my lab every attack comes from 10.10.10.2, because Proxmox NATs Kali into the lab network. In a real network you'd see the attacker, or their VPN.

The full investigation of this attack (timeline, IOCs, NIST 800-61 recommendations) is in [its own post](https://braindump.0x8r41nr07.xyz/ssh-brute-force-investigation).

---

## What surprised me

- **Most raw events are invisible.** Sysmon process creations land on a level-0 base rule. Unless you write a child rule, they never show up in the dashboard.
- **Built-in rules can hide your rule.** If a built-in matches first, your rule under the base rule never runs. You have to list the built-in as a second parent.
- **The spelling of an attack matters.** `-enc` vs `-ec`, `schtasks` vs `Register-ScheduledTask`: same technique, different telemetry, different result. Testing the variants found more than testing the textbook case.

Two more detections, LSASS credential dumping and Run key persistence, are next.

Rules: [detection-lab/wazuh](https://github.com/0x8R41NR07/detection-lab/tree/main/wazuh) · [detection-lab/sigma](https://github.com/0x8R41NR07/detection-lab/tree/main/sigma)
