Hack The Box - Sherlocks\Brutus

Sherlocks are defensive investigatory scenarios within Hack the Box’s Dedicated Labs, designed enhance digital forensics and incident response (DFIR) capabilities, provide a deeper understanding of security tools and technologies, improved ability to prioritize during real investigations and proficiency in technical analysis. This writeup is for Brutus (DFIR very easy), aimed at familiriazing with Unix auth.log and wtmp logs.

Sherlock Information

Scenario

A Confluence server was brute-forced via its SSH service. After gaining access to the server, the actor performed additional activities ranging from privelege escalation to persistence.

Artifacts
  • Brutus/auth.log
  • Brutus/wtmp

Initial Review and Inference

Artifact Review and Research

In the context of Linux systems, an “auth log” (often found as /var/log/auth.log or /var/log/secure) records events related to user authentication and authorization, including successful and failed user/service login attempts, SSH authentication attempts, sudo/root user actions, and output from authentication modules(PAM).

Alternatively, “wtmp” files (often found as /var/log/wtmp) are binary log files that store historical records of user logins/logouts, system reboots/shutdowns providing a record of all user sessions/activity on the system. Other related files include utmp (current users) and btmp (bad login attempts) with ‘who’ command to query utmp binary, ’last’ command to query wtmp binary and ’lastb’ command to query btmp binary.

Tasks
Task 1: What is the IP address used by the attacker to carry out a brute force attack?
Failed Login Python Script
# Python script to parse auth.log, look at failed login and count per IP
#!/usr/bin/python3

import re
from collections import defaultdict
import sys

def analyze_auth_log(log_file):
    # Dictionary to store failed attempts per IP
    ip_attempts = defaultdict(int)
    # Dictionary to store unique usernames tried per IP
    ip_usernames = defaultdict(set)
    
    # Regular expressions to match failed login attempts
    failed_pattern = r'Failed password for (?:invalid user )?(\w+) from (\d+\.\d+\.\d+\.\d+)'
    invalid_user_pattern = r'Invalid user (\w+) from (\d+\.\d+\.\d+\.\d+)'
    
    try:
        with open(log_file, 'r') as f:
            for line in f:
                # Check for failed password attempts
                failed_match = re.search(failed_pattern, line)
                if failed_match:
                    username, ip = failed_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
                    continue
                
                # Check for invalid user attempts
                invalid_match = re.search(invalid_user_pattern, line)
                if invalid_match:
                    username, ip = invalid_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
    
        # Print results
        print("\nBrute Force Attack Analysis:")
        print("-" * 50)
        for ip in ip_attempts:
            if ip_attempts[ip] >= 10:  # Threshold for considering it a brute force attempt
                print(f"\nIP Address: {ip}")
                print(f"Total Failed Attempts: {ip_attempts[ip]}")
                print(f"Unique Usernames Attempted: {len(ip_usernames[ip])}")
                print("Attempted Usernames: {")
                for username in sorted(ip_usernames[ip]):
                    print(f"    {username}")
                print("}")
                
    except FileNotFoundError:
        print(f"Error: Could not find log file: {log_file}")
        sys.exit(1)
    except Exception as e:
        print(f"Error analyzing log file: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python3 analyze_auth_log.py <path_to_auth.log>")
        sys.exit(1)
    
    analyze_auth_log(sys.argv[1]) 

# Script Output
Brute Force Attack Analysis:
--------------------------------------------------

IP Address: 65.2.161.68
Total Failed Attempts: 82
Unique Usernames Attempted: 5
Attempted Usernames: {
    admin
    backup
    root
    server_adm
    svc_account
}
Task 2: The bruteforce attempts were successful and attacker gained access to an account on the server. What is the username of the account?

Updating the previous script to look at successful logins as well by creating an additional dictionary, regular expression match and login attempt logic within the for loop reading through each line

Failed & Successful Login Python Script
# Updated python script
#!/usr/bin/python3

import re
from collections import defaultdict
import sys

def analyze_auth_log(log_file):
    # Dictionary to store failed attempts per IP
    ip_attempts = defaultdict(int)
    # Dictionary to store unique usernames tried per IP
    ip_usernames = defaultdict(set)
    # Dictionary to store successful logins
    successful_logins = []
    
    # Regular expressions to match failed login attempts and successful logins
    failed_pattern = r'Failed password for (?:invalid user )?(\w+) from (\d+\.\d+\.\d+\.\d+)'
    invalid_user_pattern = r'Invalid user (\w+) from (\d+\.\d+\.\d+\.\d+)'
    success_pattern = r'Accepted password for (\w+) from (\d+\.\d+\.\d+\.\d+)'
    
    try:
        with open(log_file, 'r') as f:
            for line in f:
                # Check for successful login attempts
                success_match = re.search(success_pattern, line)
                if success_match:
                    username, ip = success_match.groups()
                    successful_logins.append((username, ip))
                    continue
                
                # Check for failed password attempts
                failed_match = re.search(failed_pattern, line)
                if failed_match:
                    username, ip = failed_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
                    continue
                
                # Check for invalid user attempts
                invalid_match = re.search(invalid_user_pattern, line)
                if invalid_match:
                    username, ip = invalid_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
    
        # Print results
        print("\nBrute Force Attack Analysis:")
        print("-" * 50)
        for ip in ip_attempts:
            if ip_attempts[ip] >= 10:  # Threshold for considering it a brute force attempt
                print(f"\nIP Address: {ip}")
                print(f"Total Failed Attempts: {ip_attempts[ip]}")
                print(f"Unique Usernames Attempted: {len(ip_usernames[ip])}")
                print("Attempted Usernames: {")
                for username in sorted(ip_usernames[ip]):
                    print(f"    {username}")
                print("}")
        
        print("\nSuccessful Login Attempts:")
        print("-" * 50)
        for username, ip in successful_logins:
            print(f"Username: {username}")
            print(f"IP Address: {ip}")
            print("-" * 30)
                
    except FileNotFoundError:
        print(f"Error: Could not find log file: {log_file}")
        sys.exit(1)
    except Exception as e:
        print(f"Error analyzing log file: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python3 analyze_auth_log.py <path_to_auth.log>")
        sys.exit(1)
    
    analyze_auth_log(sys.argv[1]) 

# Script Output
Brute Force Attack Analysis:
--------------------------------------------------

IP Address: 65.2.161.68
Total Failed Attempts: 82
Unique Usernames Attempted: 5
Attempted Usernames: {
    admin
    backup
    root
    server_adm
    svc_account
}

Successful Login Attempts:
--------------------------------------------------
Username: root
IP Address: 203.101.190.9
------------------------------
Username: root
IP Address: 65.2.161.68
------------------------------
Username: root
IP Address: 65.2.161.68
------------------------------
Username: cyberjunkie
IP Address: 65.2.161.68
------------------------------
Task 3: Identify the timestamp when the attacker logged in manually to the server to carry out their objectives (The login time will be different than the authentication time)

Pivoting off of the attacker IP (65.2.161.68), and searching auth.log for Accepted password for root from 65.2.161.68 we observe first session establishment at Mar 6 06:31:40 followed by immediate session closure from the attacker IP possibly pointing to attacker using a bruteforcing script to try different username/password combinations. Subsequently, second session possibly corresponding to attacker manually SSHing is observed at Mar 6 06:32:44.

Reviewing the wtmp binary, we observe solely a single timestamp 2024-03-06T06:32:45 corresponding to the root username.

Bash analysis of wtmp
$ utmpdump HTB/Sherlock/Brutus/wtmp | grep "65.2.161.68" | cat
Utmp dump of HTB/Sherlock/Brutus/wtmp
[7] [02549] [ts/1] [root    ] [pts/1       ] [65.2.161.68         ] [65.2.161.68    ] [2024-03-06T06:32:45,387923+00:00]
[7] [02667] [ts/1] [cyberjunkie] [pts/1       ] [65.2.161.68         ] [65.2.161.68    ] [2024-03-06T06:37:35,475575+00:00]

This timing difference can be attributed to auth.log showing authentication time (created immediately when password was accepted) as opposed to wtmp that shows the actual login time (created after the session was fully established). The difference is a small delay between successful authentication and complete establishment of SSH session, which includes time for setting up SSH channel, creating user session, initilizing shell environment and recording session in wtmp.

In conclusion, auth.log records authentication/password acceptance events and wtmp records actual user sessions and their durations and is more accurate for determining when a user actually started using the system. The timing difference is important in forensic investigations as well as it can help identify patterns in attacker behavior (quick failed attempts vs sustained sessions)

Task 4: SSH login sessions are tracked and assigned a session number upon login. What is the session number assigned to the attacker’s session for the user account?

Continuing to look through auth.log for events after the second Accepted password for root from 65.2.161.68 at Mar 6 06:32:44, we observe systemd-logind[411]: New session 37 of user root. Session numbers are assigned immediately after password is accepted so this method should be easy to generalize for identifying similar session identifiers.

Task 5: The attacker added a new user as part of their persistence strategy on the server and gave this new user account higher privileges. What is the name of this account?

Updating the previous python script to generalize the search by adding user_creation, group_creation, sudo_addition and sudo_usage pattern identifiers

Failed, Successful Login & User Creation/Privelege Changes Python Script
# Updated python script
#!/usr/bin/python3

import re
from collections import defaultdict
import sys

def analyze_user_creation(log_file):
    # Patterns to match user creation and privilege changes
    patterns = {
        'user_creation': r'new user: name=(\w+), UID=(\d+), GID=(\d+)',
        'group_creation': r'new group: name=(\w+), GID=(\d+)',
        'sudo_addition': r"add '(\w+)' to group 'sudo'",
        'sudo_usage': r'sudo: (\w+) : TTY=.*?USER=(\w+)'
    }
    
    try:
        with open(log_file, 'r') as f:
            print("\nUser Creation and Privilege Analysis:")
            print("-" * 50)
            
            for line in f:
                # Check for user creation
                user_match = re.search(patterns['user_creation'], line)
                if user_match:
                    username, uid, gid = user_match.groups()
                    print(f"\nNew User Created:")
                    print(f"Username: {username}")
                    print(f"UID: {uid}")
                    print(f"GID: {gid}")
                    print(f"Timestamp: {' '.join(line.split()[0:3])}")
                
                # Check for group creation
                group_match = re.search(patterns['group_creation'], line)
                if group_match:
                    groupname, gid = group_match.groups()
                    print(f"\nNew Group Created:")
                    print(f"Group: {groupname}")
                    print(f"GID: {gid}")
                    print(f"Timestamp: {' '.join(line.split()[0:3])}")
                
                # Check for sudo group addition
                sudo_match = re.search(patterns['sudo_addition'], line)
                if sudo_match:
                    username = sudo_match.group(1)
                    print(f"\nUser Added to Sudo Group:")
                    print(f"Username: {username}")
                    print(f"Timestamp: {' '.join(line.split()[0:3])}")
                
                # Check for sudo usage
                sudo_usage = re.search(patterns['sudo_usage'], line)
                if sudo_usage:
                    user, target = sudo_usage.groups()
                    print(f"\nSudo Usage Detected:")
                    print(f"User: {user}")
                    print(f"Target User: {target}")
                    print(f"Timestamp: {' '.join(line.split()[0:3])}")
                    
    except Exception as e:
        print(f"Error in user creation analysis: {str(e)}")

def analyze_auth_log(log_file):
    # Dictionary to store failed attempts per IP
    ip_attempts = defaultdict(int)
    # Dictionary to store unique usernames tried per IP
    ip_usernames = defaultdict(set)
    # Dictionary to store successful logins
    successful_logins = []
    
    # Regular expressions to match failed login attempts and successful logins
    failed_pattern = r'Failed password for (?:invalid user )?(\w+) from (\d+\.\d+\.\d+\.\d+)'
    invalid_user_pattern = r'Invalid user (\w+) from (\d+\.\d+\.\d+\.\d+)'
    success_pattern = r'Accepted password for (\w+) from (\d+\.\d+\.\d+\.\d+)'
    
    try:
        with open(log_file, 'r') as f:
            for line in f:
                # Check for successful login attempts
                success_match = re.search(success_pattern, line)
                if success_match:
                    username, ip = success_match.groups()
                    successful_logins.append((username, ip))
                    continue
                
                # Check for failed password attempts
                failed_match = re.search(failed_pattern, line)
                if failed_match:
                    username, ip = failed_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
                    continue
                
                # Check for invalid user attempts
                invalid_match = re.search(invalid_user_pattern, line)
                if invalid_match:
                    username, ip = invalid_match.groups()
                    ip_attempts[ip] += 1
                    ip_usernames[ip].add(username)
    
        # Print results
        print("\nBrute Force Attack Analysis:")
        print("-" * 50)
        for ip in ip_attempts:
            if ip_attempts[ip] >= 10:  # Threshold for considering it a brute force attempt
                print(f"\nIP Address: {ip}")
                print(f"Total Failed Attempts: {ip_attempts[ip]}")
                print(f"Unique Usernames Attempted: {len(ip_usernames[ip])}")
                print("Attempted Usernames: {")
                for username in sorted(ip_usernames[ip]):
                    print(f"    {username}")
                print("}")
        
        print("\nSuccessful Login Attempts:")
        print("-" * 50)
        for username, ip in successful_logins:
            print(f"Username: {username}")
            print(f"IP Address: {ip}")
            print("-" * 30)
        
        # Add user creation analysis after the existing analysis
        analyze_user_creation(log_file)
                
    except FileNotFoundError:
        print(f"Error: Could not find log file: {log_file}")
        sys.exit(1)
    except Exception as e:
        print(f"Error analyzing log file: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python3 analyze_auth_log.py <path_to_auth.log>")
        sys.exit(1)
    
    analyze_auth_log(sys.argv[1]) 

# Script Output
Brute Force Attack Analysis:
--------------------------------------------------

IP Address: 65.2.161.68
Total Failed Attempts: 82
Unique Usernames Attempted: 5
Attempted Usernames: {
    admin
    backup
    root
    server_adm
    svc_account
}

Successful Login Attempts:
--------------------------------------------------
Username: root
IP Address: 203.101.190.9
------------------------------
Username: root
IP Address: 65.2.161.68
------------------------------
Username: root
IP Address: 65.2.161.68
------------------------------
Username: cyberjunkie
IP Address: 65.2.161.68
------------------------------

User Creation and Privilege Analysis:
--------------------------------------------------

New Group Created:
Group: cyberjunkie
GID: 1002
Timestamp: Mar 6 06:34:18

New User Created:
Username: cyberjunkie
UID: 1002
GID: 1002
Timestamp: Mar 6 06:34:18

User Added to Sudo Group:
Username: cyberjunkie
Timestamp: Mar 6 06:35:15

Sudo Usage Detected:
User: cyberjunkie
Target User: root
Timestamp: Mar 6 06:37:57

Sudo Usage Detected:
User: cyberjunkie
Target User: root
Timestamp: Mar 6 06:39:38
Task 6: What is the MITRE ATT&CK sub-technique ID used for persistence by creating a new account?

The creation of user and corresponding group cyberjunkie can be attributed to MITRE ATT&CK T1136.001 (https://attack.mitre.org/techniques/T1136/001/) as this technique is described as “Adversaries may create a new account to maintain access to victim systems. Accounts may be created on the local system or within a domain or cloud tenant.”

Task 7: What time did the attacker’s first SSH session end?

We can pivot off of the previous availaible session ID to search auth.log for Removed session XX where XX is 37 to identify when the session ended.

Task 8: The attacker logged into their backdoor account and utilized their higher privileges to download a script. What is the full command executed using sudo?

Pivoting off of the previously identified sudo usage timestamps from the python script, we can identify the two instances of commands executed using the cyberjunkie account.