Toru Lin
Toru Lin
Oct 19, 2018 · 8 min read

(Part 1 of 1, 2, 3)

It’s hard to believe that the 5th annual HackMIT puzzle has come to an end! In the past, we have dealt with lots of memes and movies, but this year, we decided to take a more serious approach to today’s issues with the h a c k m i r r o r (jk we all just like TV and tech).

We’d like to give a special shout out to all our puzzle makers and administrators: Pat, Shreyas, Matt, Anton, Rahul, Toru, Claire, and Noah!

Entry Point

(website, GitHub)

The entry point to our puzzle is hidden in our splash page, as always. So where is it?


Not here either.

Almost there!

The answer is… The entry point is hidden at the very bottom of HackMIT splash page because we want you to “explore” our cityscape till the very end (pun intended). If you mouse over the div at the bottom, you will see a faint purple glow on it.

(See the difference?)

But what next? If you click on the bottom div with JavaScript console open alongside, this is what you will see (after 6 clicks):

The wording of this counter should come as a strong hint. If you still haven’t realized what’s happening, another hint is embedded in the HTML source of our splash page:

<! — Show Valid ID to Go Underground →

So, yup, keep clicking until you’ve reached 21, and welcome to the world of HACK MIRROR 😈.

Command Center


For the first time in HackMIT puzzle history, we built a ~ s o c i a l ~ command center where you can interact with your fellow puzzlers and… just kidding, we have Slack for that.

You won’t see any cute cat pics from your friends on this site, just your own posts and the occasional contribution from your friendly Coach. However, it provides the standard functionality puzzlers might expect — it presents a link to each puzzle as you progress and lets you submit your answers in the form of social media posts.

Veteran puzzlers may have noticed that after solving the first puzzle this year, two puzzles unlocked. This ensured that even if you got stuck on one puzzle, you had a second to work on. This also let you set a puzzle aside for a while and come back to it at the end — though we tried to order the puzzles by difficulty, we were surprised to see all the different orders they were actually solved in.

Along with not-so-subtly ripping off everyone’s favorite evil friendly social network, we embedded a few fun extras into the command center — can you figure out how your rating is calculated? How about the number of likes on the posts?

I think the rating is based on GitHub profile photo attractiveness…


(website, GitHub)

The first puzzle leads the hackers to an innocuous-looking dashboard that looks like the ArkAngel user interface from the Black Mirror episode.

Once they click on the sight stream button, the puzzlers are greeted by a video of a pretty normal day, with anything that increases the viewer’s cortisol levels blurred out for safety.

Oh no! The button to switch of the blur filter is broken. Many puzzlers quickly realized that they had to figure out a way to un-censor the blur.

The solution was to simply inspect the HTML and delete the blur overlay video, which reveals the raw footage underneath.

We always try to keep the first puzzle on the simpler side. This allows the puzzlers to learn the mechanics of how the puzzles are supposed to work, as well as give something solvable for people with little technical background.

As always, we tried to make the puzzle answers unique to each user. This was particularly interesting to automate, since the answer is motion-tracked to the signboard. We generated a bunch of answers and wrote a janky After Effects script to automate rendering out a number of different videos.


(website, GitHub)

Nosedive was the second puzzle in the HackMIT 2018 admissions puzzle sequence. It was based off the identically named Black Mirror episode “Nosedive”, where every interact between two people can be rated, and that rating determines your status in society.

Upon entering the puzzle, we’re greeted with a simple page we a few apparent features: a photo, a rating system protected by a Captcha, and a sidebar with our current rating, some more profile information, a “Prime Influencers Program” advertisement, and a help chat.

What you see when you first enter the puzzle.

The first thing to try is the rating system. We find that we can change the rating that we want to give from 1 to 5. After we rate a picture, we see that our rating in the top right changes, depending on how many “stars” we gave the other picture.

A good rating boosts your own rating.
Giving a bad rating, though, drops your rating. By much more, in fact.

From this, we also realize that the captcha system being used is Google’s latest version of reCaptcha, and thus is unlikely that the goal of the puzzle is to bruteforce your rating. In fact, it was impossible to reach a 5.0 by repeatedly rating other photos, because the formula used server-side to compute the new ratings limited values to be below 4.7. It was necessary to find some vulnerability in the site in order to get impossible 5.0 score.

Instead, we turn our attention to the user’s profile. We see that we can set our biography, get more information about the Prime Influencer’s Program, and type messages to a very unhelpful chatbot.

You can change your biography.

Next, we find a button tagged “Boost”. Unfortunately, clicking the button just results in a very unhelpful “Sorry, this feature is still in beta”. Continuing down the sidebar, we find a link to more information about the “Prime Influencers Program”. This link leads us to a static website with two more links: a signup page, and an API documentation.

Clicking the Boost button has disappointing results.
There’s two links: signup and API.

The signup page doesn’t have anything interesting; it’s just a simple form that, once submitted, tells us whether or not we qualify for the Prime Influencer’s Program.

There really isn’t anything to see here.

However, the API documentation is quite interesting. In particular, the final REST API endpoint, /beta/boost_rating, seems to be what we want: we want our rating to be artificially boosted. Sadly for us, sending a POST request to that endpoint by ourselves results in a “bad permissions” message, which makes sense: only support is allowed to access that endpoint.

The API documentation has an interesting final entry: boost_rating.
Only support can access boost_rating at the moment.

There’s only one way to interact with support: through the chatbox. After sending just a few messages, we come across the following response: “Sorry I don’t understand what your problem is. Maybe if you gave me a link I could see.”

“Sorry I don’t understand what your problem is. Maybe if you gave me a link I could see.”

That’s it! If we can get the support bot to visit /beta/boost_rating on our behalf, then we could raise our rating artificially! Had the API endpoint been a GET endpoint, we would be done; we could just supply the link to /beta/boost_rating with our username in the URL and send it to the bot. Unfortunately, we have to get the bot to send a POST request — how can we manage that?

There’s one piece of the puzzle that we haven’t played around with much: the biography. Whenever there’s arbitrary text input that can be saved or submitted, there’s potential for it be vulnerable to cross-site scripting, or XSS. Trying out the most simple type of XSS, though, is a no-go.

The typical <script>alert(0);</script> doesn’t work here.

If we look into why, though, we can see the problem: the server is wrapping our biography in quotes, which prevents our XSS from being interpreted as HTML tags. But that means we can escape out of the quotes by purposefully inserting our own quotation marks.

Adding quotation marks strategically so that the HTML is still syntactically correct gives us code execution.
Successful XSS!

Now that we can get arbitrary code to run when someone visits our profile page, we need set up that code to send a POST request to the /beta/boost_rating API endpoint. We can do this quickly in JavaScript with jQuery’s $.post() function, which we luckily have because jQuery is included on the site (if it wasn’t, we could use an XMLHttpRequest to accomplish the same task).

Setting our biography to this will send a POST request on our behalf.

The final thing that’s left to do is send the link to our profile to the chatbot. If we do so…

We win!

Thanks for reading so far! Check out Part 2 to see some ~ b l o c k c h a i n ~

HackMIT Stories

Stories from MIT's largest undergraduate hackathon.

Toru Lin

Written by

Toru Lin

HackMIT Stories

Stories from MIT's largest undergraduate hackathon.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade