Objective

Terminal - Smart Braces

Challenge

Through the steam tunnels back to the Student Union, I find Kent Tinseltooth near the fireplace where I found Michael and Jane, the turtle doves, with a wire running from his mouth to another terminal.

OK, this is starting to freak me out!

Oh sorry, I’m Kent Tinseltooth. My Smart Braces are acting up.

Do… Do you ever get the feeling you can hear things? Like, voices?

I know, I sound crazy, but ever since I got these… Oh!

Do you think you could take a look at my Smart Braces terminal?

I’ll bet you can keep other students out of my head, so to speak.

It might just take a bit of Iptables work.

When I visit the terminal, it’s a series of voices, followed by a Linux prompt:

Inner Voice: Kent. Kent. Wake up, Kent.
Inner Voice: I'm talking to you, Kent.
Kent TinselTooth: Who said that? I must be going insane.
Kent TinselTooth: Am I?
Inner Voice: That remains to be seen, Kent. But we are having a conversation.
Inner Voice: This is Santa, Kent, and you've been a very naughty boy.
Kent TinselTooth: Alright! Who is this?! Holly? Minty? Alabaster?
Inner Voice: I am known by many names. I am the boss of the North Pole. Turn to me and be hired after graduation.
Kent TinselTooth: Oh, sure.
Inner Voice: Cut the candy, Kent, you've built an automated, machine-learning, sleigh device.
Kent TinselTooth: How did you know that?
Inner Voice: I'm Santa - I know everything.
Kent TinselTooth: Oh. Kringle. *sigh*
Inner Voice: That's right, Kent. Where is the sleigh device now?
Kent TinselTooth: I can't tell you.
Inner Voice: How would you like to intern for the rest of time?
Kent TinselTooth: Please no, they're testing it at srf.elfu.org using default creds, but I don't know more. It's classified.
Inner Voice: Very good Kent, that's all I needed to know.
Kent TinselTooth: I thought you knew everything?
Inner Voice: Nevermind that. I want you to think about what you've researched and studied. From now on, stop playing with your teeth, and floss more.
*Inner Voice Goes Silent*
Kent TinselTooth: Oh no, I sure hope that voice was Santa's.
Kent TinselTooth: I suspect someone may have hacked into my IOT teeth braces.
Kent TinselTooth: I must have forgotten to configure the firewall...
Kent TinselTooth: Please review /home/elfuuser/IOTteethBraces.md and help me configure the firewall.
Kent TinselTooth: Please hurry; having this ribbon cable on my teeth is uncomfortable.
elfuuser@4570b58f0795:~$ Solution IOTteethBraces.md gives instructions on how to configure the IP Tables firewall for the braces: elfuuser@4570b58f0795:~$ cat IOTteethBraces.md
# ElfU Research Labs - Smart Braces
### A Lightweight Linux Device for Teeth Braces
### Imagined and Created by ElfU Student Kent TinselTooth
This device is embedded into one's teeth braces for easy management and monitoring of dental status. It uses FTP and HTTP for management and monitoring purposes but also has SSH for remote access. Please refer to the man
agement documentation for this purpose.
## Proper Firewall configuration:
The firewall used for this system is iptables. The following is an example of how to set a default policy with using iptables:
​
sudo iptables -P FORWARD DROP
​
The following is an example of allowing traffic from a specific IP and to a specific port:
​
sudo iptables -A INPUT -p tcp --dport 25 -s 172.18.5.4 -j ACCEPT
​


A proper configuration for the Smart Braces should be exactly:

1. Set the default policies to DROP for the INPUT, FORWARD, and OUTPUT chains.
2. Create a rule to ACCEPT all connections that are ESTABLISHED,RELATED on the INPUT and the OUTPUT chains.
3. Create a rule to ACCEPT only remote source IP address 172.19.0.225 to access the local SSH server (on port 22).
4. Create a rule to ACCEPT any source IP to the local TCP services on ports 21 and 80.
5. Create a rule to ACCEPT all OUTPUT traffic with a destination TCP port of 80.
6. Create a rule applied to the INPUT chain to ACCEPT all traffic from the lo interface.

I’ll address those rules one by one:

1. -P [chain] to set the default policy for each of INPUT, OUTPUT, and FORWARD.

sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT DROP

2. -m state with --state [states] will set rules for various states:

sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

3. -s allows me to set rules for source IPs, and --dport for destination ports:

sudo iptables -A INPUT -s 172.19.0.225 -p tcp --dport 22 -j ACCEPT

4. -m multiport opens up the --dports flag for rules for multiple destination ports:

sudo iptables -A INPUT -p tcp -m multiport --dports 21,80 -s 0.0.0.0/0 -j ACCEPT

5. --dport 80 solves this rule:

sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT

6. -i lo applies the rule only to loopback:

sudo iptables -A INPUT -i lo -j ACCEPT


When I run all of them, Kent jumps in and says thanks:

elfuuser@7bf12811ac37:~$sudo iptables -P INPUT DROP elfuuser@7bf12811ac37:~$ sudo iptables -P FORWARD DROP
elfuuser@7bf12811ac37:~$sudo iptables -P OUTPUT DROP elfuuser@7bf12811ac37:~$ sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED
-j ACCEPT
elfuuser@7bf12811ac37:~$sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATE D -j ACCEPT elfuuser@7bf12811ac37:~$ sudo iptables -A INPUT -p tcp -m multiport --dports 21,80 -
s 0.0.0.0/0 -j ACCEPT
elfuuser@7bf12811ac37:~$sudo iptables -A INPUT -s 172.19.0.225 -p tcp --dport 22 -j ACCEPT elfuuser@7bf12811ac37:~$ sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
elfuuser@7bf12811ac37:~$sudo iptables -A INPUT -i lo -j ACCEPT elfuuser@7bf12811ac37:~$
Kent TinselTooth: Great, you hardened my IOT Smart Braces firewall!
/usr/bin/inits: line 10:    92 Killed                  su elfuuser


Hints

On solving, Kent directs me to check out Shinny’s create:

Oh thank you! It’s so nice to be back in my own head again. Er, alone.

By the way, have you tried to get into the crate in the Student Union? It has an interesting set of locks.

There are funny rhymes, references to perspective, and odd mentions of eggs!

And if you think the stuff in your browser looks strange, you should see the page source…

Special tools? No, I don’t think you’ll need any extra tooling for those locks.

BUT - I’m pretty sure you’ll need to use Chrome’s developer tools for that one.

Or sorry, you’re a Firefox fan?

Yeah, Safari’s fine too - I just have an ineffible hunger for a physical Esc key.

Edge? That’s cool. Hm? No no, I was thinking of an unrelated thing.

Curl fan? Right on! Just remember: the Windows one doesn’t like double quotes.

Old school, huh? Oh sure - I’ve got what you need right here…

And I hear the Holiday Hack Trail game will give hints on the last screen if you complete it on Hard.

Holiday Hack Trail hints

I solved the Holiday Hack Trail on hard, and I don’t remember seeing any hints. In fact, side-by-side, easy, medium, and hard all look the same:

Click for full size image

I pulled up the source for the hard victory screen, and other than the numbers, there was one other difference - an extra HTML comment at the bottom of the page:

<!-- 1 - When I'm down, my F12 key consoles me
2 - Reminds me of the transition to the paperless naughty/nice list...
3 - Like a present stuck in the chimney!  It got sent...
4 - We keep that next to the cookie jar
5 - My title is toy maker the combination is 12345
6 - Are we making hologram elf trading cards this year?
7 - If we are, we should have a few fonts to choose from
8 - The parents of spoiled kids go on the naughty list...
9 - Some toys have to be forced active
10 - Sometimes when I'm working, I slide my hat to the left and move odd things onto my scalp! -->


Shinny’s Crate

Challenge

On the right side of the Student Union, I find Shinny, guarding the Sleigh Shop door:

Psst - hey!

I’m Shinny Upatree, and I know what’s going on!

Yeah, that’s right - guarding the sleigh shop has made me privvy to some serious, high-level intel.

In fact, I know WHO is causing all the trouble.

Cindy? Oh no no, not that who. And stop guessing - you’ll never figure it out.

The only way you could would be if you could break into my crate, here.

You see, I’ve written the villain’s name down on a piece of paper and hidden it away securely!

When I visit the crate (or here), it’s a series of 10 locks I need to open, each with a hint on where to find the key:

Manual Solution

Lock 1

The riddle is to look in the Console. I’m in Firefox, so I’ll open the Console with Ctrl+Shift+K:

I see a bunch of black boxes, and also a long scroll bar on the right. I’ll scroll to the top, and find the code:

It unlocks:

Lock 2

The hint talks about showing up on pulp with dye. That sounds like printing it. The print preview function is enough to see the code:

That opens the lock:

Lock 3

At first I thought of an HTML comment, but when there were none in the page, and thinking about the focus on using the developer tools, I went to the network tab. Right away, I noticed a GET for 8b3ded88-b1f2-4e61-9f38-ff3b89e86892.png, and hovering over it shows it’s a code:

Entering that code unlocks it:

Lock 4

Local storage would be a better end to complete the rhyme! In the storage tab in the dev tools, there’s a key and the value is a code:

Lock 5

The title bar does have a code in it:

I can also see this in the source:

<title>Crack the Crate                                                  DS9P0FSN</title>


This unlocks it:

Lock 6

I can see something like a code on that rainbow dog tag, but nothing legible. I’ll right click and inspect element:

There’s two things useful in there. At first I thought I just had the code combining the letters in each <div>, but it didn’t work. Then there’s the perspective CSS element. The riddle suggested increasing it. I edited it right in the dev tools console, and started stepping up a bit, eventually making it really large (3000000px) and getting a very clear code:

That code unlocked it:

Lock 7

Given the hint about the font, I selected some of the text, and right-clicked -> Inspect Element. Under the instructions class, I see the code:

That unlocks it:

Lock 8

.eggs was interesting in the riddle, so I inspected it.

The two event markers are interesting. The one at the bottom is what happens when I click for a hint. But the one next to the eggs <span>. I’ll click on it to see:

So on a spoil event (“go bad”), VERONICA is sad.

That’s the code:

Lock 9

:active is meaningful in HTML. It has to do with the space where the user has pressed down the mouse. So what are the chakras? In the source for this part, five of the words are in a space with class="chakra":

If I click on any of those words, letters appear. I can also click on the space and check :active from the CSS menu:

Clicking on each gives the code and it unlocks:

Lock 10

I’ll inspect the lock in the dev tools, it looks similar to other locks, but has this extra div with class cover:

My initial hunch was to remove that class:

Looking back at the page, the cover is gone:

I can see a serial number that happens to be 8 characters on the bottom right of the board, KD29XJ37. I’ll enter that and hit UNLOCK, but it doesn’t unlock. Back in the dev tools Console window, I see an error message:

Eventually back in the Inspector window I decided to search for macaroni. When I started typing it, it autofilled:

The is a div with class component macaroni up in the lock 7 question:

I can see it in the page, hidden:

In the Inspector window, I can just click on the div and drag it into the lock:

Now when I unlock, I get an error Error: "Missing cotton swab!". I do the same process with the .swab and .gnome classes, and I have a lock that will unlock:

Solution

On unlocking all ten locks, I get the paper and the villain’s name:

In the console, there’s a message:

Automated Solution

Overview

The elf wants it in less than five seconds. To do this, I’m going to need to automate the locks. I’m going to use a TamperMonkey script. TamperMonkey just lets you run Javascript in the page along side the script that come down from the site.

For locks 2, 4, 5, 6, 7, 8, and 9, I just need to find a value somewhere with Javascript, put it in, and push the button. It turns out that how I was setting the value wasn’t enabling the button, so I’ll do that too. In general, these will look like:

    document.querySelector('div.c[#] > input').value = [value];
document.querySelector('div.c[#] > button').disabled = false;
document.querySelector('div.c[#] > button').click();


For lock 10, I just need to move three elements and then do that same.

I also need these to wait until the page loads to run, so I’ll start with a script to listen for that:

window.addEventListener('load', function() {
unlock();
}, false);


The unlock function will call these for each lock.

Lock 2

The code is in a div with class libra inside a <strong> element:

document.querySelector('div.libra>strong').textContent;

Lock 4

The code is in local storage:

window.localStorage.getItem('🛢️🛢️🛢️');

Lock 5

The code is in the title, and I’ll use slice to get just the end where the code is:

window.document.title.slice(-8);

Lock 6

Each character of the code is in a different span, and changing the perspective will reorder them and make them visible. But the class marking each span stays the same each time, so I can just read from them in that order:

document.querySelector('.ZADFCDIV').textContent + document.querySelector('.GMSXHBQH').textContent + document.querySelector('.RPSMZXMY').textContent + document.querySelector('.IDOIJIKV').textContent + document.querySelector('.KXTBRPTJ').textContent + document.querySelector('.AJGXPXJV').textContent + document.querySelector('.ZWYRBISO').textContent + document.querySelector('.KPVVBGSG').textContent;

Lock 7

To get to the CSS, I had to use getComputedStyle, since it seems to have been dynamically added, as it’s not in the css style sheet:

window.getComputedStyle(document.querySelector('.instructions'), null).getPropertyValue('font-family').split('"')[1]

Lock 8

This one seems statically set to VERONICA.

Lock 9

The code I need is in the style sheet for this one:

span.chakra:nth-child(1):active:after {
content: '94';
}
span.chakra:nth-child(2):active:after {
content: 'CR';
}
span.chakra:nth-child(3):active:after {
content: 'I';
}
span.chakra:nth-child(4):active:after {
content: '31';
}
span.chakra:nth-child(5):active:after {
content: '0';
}


The challenge is reading it out. With a ton of googling and trial and lots of error, I got this loop working:

    var i,j, sel = /:active/;
var res = document.querySelector('div.c9 > input');
for(i = 0; i < document.styleSheets.length; ++i){
try {
if(!document.styleSheets[i].cssRules) {
continue;
}
for(j = 0; j < document.styleSheets[i].cssRules.length; ++j){
if(sel.test(document.styleSheets[i].cssRules[j].selectorText)){
res.value = res.value + document.styleSheets[i].cssRules[j].style.cssText.split('"')[1]
}
}
} catch(e) {
if(e.name !== 'SecurityError') {
throw e;
}
}
}

Lock 10

The code is static, KD29XJ37, but I also need to move the three pieces into the lock:

    lock10.insertBefore(mac, led10)
lock10.insertBefore(swab, led10)
lock10.insertBefore(gnome, led10)
document.querySelector('div.c10 > input').value = "KD29XJ37"

Lock 3

For lock 3, I needed to get the image, and use OCR to get the code from it. I noticed that the image name matches the guid used for the page CSS and JS. For example, on one load, I get:

<link rel="stylesheet" href="css/styles.css/7ab693fb-df81-46c8-b962-103deeab03ce">


And:

<script type="text/javascript" src="/client.js/7ab693fb-df81-46c8-b962-103deeab03ce"></script>


And the network tab shows GET requests for 7ab693fb-df81-46c8-b962-103deeab03ce.png. So I can get the file name from the link:

var guid = document.getElementsByTagName('link')[0].href.split('/')[5]


I used Microsoft’s Computer Vision service to do the OCR. I had to give a credit card, but the free tier allows for 5000 queries per month.

I tried using straight javascript to send the request, but the browser throws cross origin flags. TamperMonkey has a work around for this, using GM.xmlHttpRequest:

    GM.xmlHttpRequest({
method: "POST",
url: "https://microsoft-azure-microsoft-computer-vision-v1.p.rapidapi.com/ocr",
data: JSON.stringify({"url": "https://crate.elfu.org/images/" + guid + ".png"}),
"x-rapidapi-host": "microsoft-azure-microsoft-computer-vision-v1.p.rapidapi.com",
"x-rapidapi-key": "[redacted]",
"content-type": "application/json",
"accept": "application/json"
},
var code = JSON.parse(response.responseText).regions[0].lines[0].words[0].text;
document.querySelector('div.c3 > input').value = code
document.querySelector('div.c3 > button').disabled = false;
document.querySelector('div.c3 > button').click();
}
});

Lock 1

Lock 1 was one of the easier challenges to do manually, and just a beast to automate. I went down a long path of trying to overwrite the console.log function with a function that would store the messages, and that works, but I could not find a way to get that loaded before the javascript that printed to the console. So my later messages were logged, but not the one I cared about.

Then I got a break. I noted earlier that there’s a single GUID that’s associated with the CSS, the JS, and the image. What I hadn’t noticed is that the same GUID doesn’t always return the same JS:

$for i in {1..10}; do curl -s https://crate.elfu.org/client.js/238b5b9e-3b68-440c-8379-d08826acb8ef | md5sum; done 93d71a3290a8dd54f19c7560df4060b6 - 4b0038aeeaf758124858c2b2f52ebbe3 - 7c1dc1b95bc353fa1c2caa1c0042d040 - dd852950ea023aafa82454b6a85804ce - 7d689d31d2ea4424079ae973e1a79a83 - 5e4b96b5c3231078c686c2939727cf5b - d27c23d771bedfefa73d3d6f62382179 - 2322c3bb8cb99ff644299407b8b645bf - c5bddeafbb84f2a3cb55aae5b4a531e5 - 8259f02901e423975b6a00ee53c60edd -  In fact, if I get it 100 times, I get a different bit of JS each time: $ for i in {1..100}; do curl -s  https://crate.elfu.org/client.js/238b5b9e-3b68-440c-8379-d08826acb8ef | md5sum; done | sort -u | wc -l
100


The server must have some basic javascript that it sends through a random series of obfuscators, and there are enough that I don’t get any duplicates over 100 successsive pulls.

Sometimes, it leaves the part the prints the console code a little less obfuscated:

console[_0x4475('0x33')](),console[_0x4475('0x34')]('\x25\x63\u258b\x0a\x25\x63\x4e\x58\x46\x57\x58\x48\x42\x47\x20\x25\x63\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b\x0a\u258b','\x63\x6f\x6c\x6f\x72\x3a\x20\x62\x6c\x61\x63\x6b\x3b\x20\x66\x6f\x6e\x74\x2d\x77\x65\x69\x67\x68\x74\x3a\x20\x62\x6f\x6c\x64\x3b\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\x31\x2e\x32\x35\x65\x6d\x3b',_0x4475('0x35'),_0x4475('0x36'))


In JS NICE, that comes out as:

      console[_0x4475("0x33")]();
console[_0x4475("0x34")]("%c\u258b\n%cNXFWXHBG %c\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b\n\u258b", "color: black; font-weight: bold; font-size: 1.25em;", _0x4475("0x35"), _0x4475("0x36"));


The code is right there in plaintext, NXFQXHBG (after JS NICE helpfully converted it from \x4e\x58\x46\x57\x58\x48\x42\x47).

I can’t just get the Javascript using Javascript. I think the code is clearing itself. But I can use another xmlHtppRequest to request it. I’ll search for the string, and if it’s not there, refresh the page. If it is there, convert it from a double escaped string (like \\x4e to N). I’m sure there’s a smarter way to do that, but I just wrote a for loop.

I can use the same guid variable from before, and get the code:

    var guid = document.getElementsByTagName('link')[0].href.split('/')[5]
GM.xmlHttpRequest({
method: "GET",
url: "/client.js/" + guid,
var esc_code = response.responseText;
var position = esc_code.indexOf("]('\\x25\\x63\\u258b\\x0a\\x25\\x63") + 29;
var code = "";
if(position > 100){
for(var n = position; n < position + 32; n+=4) {
code += String.fromCharCode(parseInt(esc_code.substr(n+2, n+4), 16));
}
document.querySelector('div.c1> input').value = code
document.querySelector('div.c1> button').disabled = false;
document.querySelector('div.c1> button').click();
} else {