# Investigating an SSH brute force in my home lab: 357 failed logins, one stolen account

> Structured on the NIST SP 800-61 incident response lifecycle. Own lab: the attack was run on purpose against a throwaway account, then investigated from the logs as if it were a real alert.

| Field | Value |
| --- | --- |
| Case ID | IR-2026-001 |
| Analyst | 0x8R41NR07 |
| Date of incident | 2026-11-10 |
| Source | Own lab (Proxmox): Ubuntu server `dockers` 10.10.10.30, Wazuh 4.14.8 |
| Data | `auth.log` from `dockers`, `last` / `lastb` output, Wazuh alert export (CSV, 1,385 alerts) |
| Tools used | grep, cut, sort, uniq, last/lastb, a small Python CSV counter, Wazuh dashboard |
| Classification | Credential access via SSH password guessing, followed by a valid-account login |

## 1\. Executive summary

On 10 November 2026 between 10:19 and 11:01 UTC, a single source (10.10.10.2) made 357 failed SSH password attempts against the account `labtest` on the server `dockers`, in two automated runs. Both runs guessed the correct password, and the attacker used it for an interactive 44-second login at 10:26 UTC; no privilege escalation, new accounts or persistence were found, but the commands run during that session could not be seen because the server doesn't log them. Wazuh flagged the brute force within seconds (rules 5763, 40112 and the custom rule 100105), but the follow-up interactive login only produced low-level "login success" events that nobody would review. Recommended: reset and lock the account, block the source, switch SSH to key-only authentication, and add an alert for a successful login from an IP that just triggered a brute-force alert.

## 2\. Scenario

Alert: *"Possible SSH brute force on dockers"*. Questions to answer:

1. When did the activity start and end?
2. From which IP?
3. How many attempts, against which usernames?
4. Did it succeed? Which account, when?
5. What happened after the successful login?
6. What did the SIEM detect, and how fast?

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

## 3\. Timeline of events (UTC, 2026-11-10)

| Time | Source | Event | ATT&CK |
| --- | --- | --- | --- |
| 10:14:42 | auth.log | Account `labtest` created by admin `brainrot` (lab setup, before the attack) | – |
| 10:19:37 | auth.log | **First failed password** for `labtest` from 10.10.10.2 (run 1) | T1110.001 |
| 10:19 | Wazuh | First brute-force alerts within seconds: 5551, 5763 (level 10) | – |
| 10:21:42 | auth.log | **Accepted password** for `labtest` from 10.10.10.2; session closed after ~1 s (the tool verifying the password) | T1110.001 |
| 10:21 | Wazuh | 40112 *Multiple authentication failures followed by a success* (level 12) | – |
| 10:26:14 | auth.log | **Interactive login** as `labtest` from 10.10.10.2 | T1078 |
| 10:26:58 | auth.log | `labtest` disconnects (session length 44 s) | – |
| 10:59 | auth.log | Second run starts: same source, same account | T1110.001 |
| 10:59 | Wazuh | Custom rule **100105** fires (46 alerts during run 2) | – |
| 11:01:19 | auth.log | Accepted password for `labtest` (run 2), closed after ~1 s | T1110.001 |
| 11:07:58 – 11:08:12 | auth.log | One failed password, then a login as `labtest` (false-positive test, not the attacker) | – |
| 11:19 – 11:31 | auth.log | Analyst logins and `sudo` for evidence collection | – |

Times come from `auth.log` on `dockers`. The Wazuh dashboard shows local time (CET, UTC+1), and its timestamps differ from the server's log times by ~15 s because the two machines' clocks aren't perfectly synced.

## 4\. Analysis

### 4.1 Initial access

**Evidence:** 357 lines `Failed password for labtest from 10.10.10.2`, then:

```plaintext
2026-11-10T10:21:42.982610+00:00 docker sshd[11808]: Accepted password for labtest from 10.10.10.2 port 54926 ssh2
```

- **All 357 failures** come from one IP and target one existing account (no `invalid user` lines). One user, many passwords = **password guessing** (T1110.001), not spraying.
- **Rate:** about 4 attempts every 2–4 seconds. Failures per minute: 10:19 = 32, 10:20 = 84, 10:21 = 63, then a gap, then 10:59 = 70, 11:00 = 80, 11:01 = 27. That's machine speed with ~4 parallel connections, consistent with a tool like Hydra.
- **Success:** the password was guessed at 10:21:42 and again at 11:01:19. Each of those sessions closed within a second, which is what a brute-force tool does to confirm a hit.

**Source IP note:** 10.10.10.2 is the lab's Proxmox host, which NATs traffic from outside the lab network. The attacker's real address is hidden behind it. In production that would point to a NAT / VPN gateway that needs its own logs.

**Confidence: High.**

### 4.2 Execution and persistence

**Evidence:**

```plaintext
2026-11-10T10:26:14.829555+00:00 docker sshd[11913]: Accepted password for labtest from 10.10.10.2 port 40272 ssh2
2026-11-10T10:26:58.739686+00:00 docker sshd[11984]: Disconnected from user labtest 10.10.10.2 port 40272
```

- Four and a half minutes after the first successful guess, the same source logged in **interactively** and stayed 44 seconds: a human using the stolen password (T1078 Valid Accounts).
- **What was typed is unknown.** `auth.log` records logins, `sudo` and account changes, not commands. This is a **visibility gap** (see section 8).
- **No persistence found** in the available logs: no `useradd`, `usermod`, `passwd` or group changes by `labtest`, and no further logins by the attacker. SSH keys in `~labtest/.ssh/authorized_keys` were not checked; that would be the next step on a real host.

**Confidence:** High for the login. Low for "nothing else happened", because of the gap.

### 4.3 Privilege escalation / lateral movement

- **No** `sudo` **lines for** `labtest`**.** The only `sudo` activity that day is the admin `brainrot` (account creation before the attack, evidence collection after it). The three failed `sudo` attempts at 11:21 (Wazuh 5404) were the analyst mistyping, not the attacker.
- No outbound SSH from `dockers` in the logs.

**Confidence: Medium** (based on auth.log only).

### 4.4 Actions on objective / impact

- **Confirmed impact:** the account `labtest` is compromised (its password is known to the attacker).
- **Unknown:** what was read or changed during the 44-second session.
- `last` still listed a `labtest` session from 11:08 as "still logged in" with no matching *session closed* line. It belongs to the false-positive test, but on a real host any unexplained open session should be checked with `who` and killed.

## 5\. Indicators of compromise

| Type | Value | Context |
| --- | --- | --- |
| IP | 10\[.\]10\[.\]10\[.\]2 | Source of all 357 failures and of the interactive login (lab NAT gateway, see 4.1) |
| Account | `labtest` | Targeted and compromised account on `dockers` |
| Password | `[REDACTED]` | Guessed password, from the 200 most common entries of the `rockyou` wordlist |
| Behaviour | ~4 attempts / 2–4 s, 4 parallel connections, *Too many authentication failures* disconnects | Fingerprint of an automated SSH brute-force tool |

## 6\. MITRE ATT&CK summary

| Tactic | Technique | ID |
| --- | --- | --- |
| Credential Access | Brute Force: Password Guessing | T1110.001 |
| Initial Access / Persistence | Valid Accounts: Local Accounts | T1078.003 |
| Persistence | Create Account: Local Account | T1136.001 (**not observed**) |

## 7\. Response recommendations (NIST 800-61 phases)

- **Containment:** lock `labtest` (`passwd -l labtest`) and end its sessions (`pkill -u labtest`); block 10.10.10.2 at the firewall (in production, find the real source behind the NAT first).
- **Eradication:** reset the password; check `~labtest/.ssh/authorized_keys`, crontabs and `~labtest/.bash_history` for anything the attacker left. Delete the account if it isn't needed (here: it was a test account, deleted on 10 Nov).
- **Recovery:** bring the account back only with a strong, unique password or key-based login, and watch it closely for a week.
- **Lessons learned / prevention:**
  - **Key-only SSH** (`PasswordAuthentication no`): makes password guessing pointless.
  - **fail2ban** or a similar tool to block an IP after a few failures.
  - Don't expose SSH widely: allow it only from admin networks or through a VPN.
  - A password policy that rejects common passwords. This one was in the top 200 of a public wordlist.
  - **Log commands** (auditd `execve` rules, or Sysmon for Linux) so the next investigation can see what was done.

## 8\. Detection opportunities

**What fired:** the brute force was caught fast and from several angles: 5760 for each failure, 5551 / 5763 / 2502 / 40111 (level 10) within seconds, **40112** (level 12) at each successful guess, and the custom rule **100105** (level 12, 8 failures in 60 s from one IP) in run 2. Detection write-up: [3 Wazuh detections, tested in my home lab](https://braindump.0x8r41nr07.xyz/3-wazuh-detections-home-lab) (section 3). Rule files: [detection-lab](https://github.com/0x8R41NR07/detection-lab).

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

**What didn't:** the **interactive login at 10:26**, the moment the attacker actually got in, only produced level-3 events (5715 *authentication success*, 5501 *session opened*). Nothing connects them to the brute force minutes earlier. Two improvements:

1. A correlation rule: **successful SSH login from an IP that triggered 100105 or 40112 in the last hour** → level 13+.
2. Command logging (auditd), so the session's contents become visible.

## 9\. Analyst notes

- **Separating myself from the attacker was half the job.** My own account creation, evidence collection, a mistyped `sudo` password and a false-positive test all land in the same log. I marked each one as analyst activity in the timeline before drawing conclusions.
- `sort | uniq -c` answered most questions (IPs, usernames, attempts per minute). The gotcha: `uniq` only merges lines next to each other, so `sort` has to come first.
- **Use the server's log for times.** Dashboard times are local time and the two clocks differ by seconds.
- The biggest lesson is the gap in 4.2: the logs proved **that** someone got in, but not **what** they did. That's the argument for command logging on any server that allows SSH.
