HackTheBox Breaking Grad Write Up

This was such a challenging box for me. Not only because it was one of the first Medium boxes I attacked, but because I really had to learn about JavaScript (something I’ve never dabbled in) and about a vulnerability I’d never encountered or even read about before. The vulnerability is specific to JavaScript so this box took me a significant amount of time.

Website Overview

The website seems to report the grades of two students. The box introduction says that their lecturer has a vendetta with them and is failing them because of this. The site allows you to check the grades of either Kenny Baker or Jack Purvis. You simply select the student and click the “Did I pass?” button. For both students, the website returns “Pass: Noooope” (or a random variation of the word ‘nope’).

There isn’t a lot to this site. It’s just a single page with this single function. Luckily, with this challenge box I also get access to the code and the Dockerfile.

Recon

Firstly, I took a look at the Dockerfile and package.json file to see what I can find. The site is using NodeJS version 12.18.1. A quick google revealed that this version of Node is riddled with vulnerabilities like Out-of-Bounds Read, DoS, Prototype Pollution, Request Smuggling and more. It was actually a little overwhelming because you don’t know how to narrow it down, or if the vulnerability is even in Node for this box. This could be a massive rabbit hole, or it could hold the gem I need. Chances are that one of these vulnerabilities are where I need to aim.

Checking out the dependencies was far less fruitful. The application is using a version of body parser and express that didn’t seem to have any vulnerabilities. It also uses ‘randomatic’ to generate the random string for the flag name.

Code Analysis

This part took me a good amount of time because I knew nothing about JavaScript. The first thing I noticed in /challenge/routes/index.js was that there was a debug function as well as an API to calculate the students grade.

challenge/routes/index.js

Opening the DebugHelper file I noticed that it contained 2 commands /debug/version and /debug/ram. So I tried this out and had a look at the results:

Version Command

 

Ram Command

The ram command was immediately interesting to me because I could see that it was attempting to execute a bash command (free -m) on the server. Obviously, the server didn’t have ‘free’ installed (a command used to show free memory on the machine), but this did mean that DebugHelper has rights to execute commands on the machine. Immediately Remote Code Execution comes to mind, but how? I had a closer look at the code for DebugHelper and found that the command was hard-coded and not a variable:

This meant that there wouldn’t be a way to get RCE by adding /debug/ram;ls for example, as this command was not being passed to the execSync function. I still wasn’t willing to give up on this though, so I put it on back-burner and continued to dig into the code.  At this point it’s probably prudent to mention that I have observed the request being made in BURP and it’s a simple JSON string:

{“name”: “Kenny Baker”}

 

Having a look at the /api/calculate code I noticed that it’s using the ObjectHelper class and the clone method. It’s sending the request body (the JSON part of the request) to ObjectHelper.clone.

The clone function creates a copy of the original object so that it can be edited and used in one part of the code, while the original object remains unchanged (it may be needed in another part of the code). The clone function uses the merge function to recursively copy the value of the original object into the new cloned object.

Now, what was interesting to me, was that the ObjectHelper class is filtering out the ‘__proto__’ string so that it’s not used in the new object if it existed in the original. I’m a JS dumb-dumb so I Googled ‘__proto__’ to see what came up.  Google came up straight away with something called Prototype Pollution. This rang some bells in my mind from when I was looking into the NodeJS version vulnerabilities earlier, as I remembered it being one of them. Problem is I had no idea what it was. More Googling ensued. So for other JS dumb-dumbs like me, let me try to explain:

JavaScript is a prototype language. What does that mean, one might query? Basically, every object in JS has a prototype which it inherits its properties from. When you create a new object in JS, it will take its properties from the prototype, and if the object doesn’t contain certain properties or methods, it will look to its prototype to see if it has them.  So, if you can edit or ‘pollute’ the prototype, then you can change the object too, as it will inherit the properties of the prototype you edited.

A Demonstration of Prototype Pollution

So let me just demonstrate this for you using my console. First, I’m going to create an an object called ‘user’, with two properties: name, and birth. Super simple:

Next, I’ll just call the object user, and the property ‘name’ and print it to the console:

Now, I’m going to pollute the prototype of user using ‘__proto__’ property, so that the next time I call the name property, I will get an alert box declaring that the prototype was polluted:

Next, if I try to print user.name again I get this:

So why am I showing you this? The proto property is being filtered out by the code, so this is useless isn’t it? Well the programmers made a mistake with this code, because there is more than one way to modify the prototype. We can also use constructor.prototype to do the exact same thing:

Here I have modified the name property again using constructor.prototype so that if I call the name property again, I will now get a pop up saying “Got you!”:

Great success! We learned something new! I hope. Now how do I use this for nefarious purposes?

Polluting Grades

Now that I’ve explained how this works, I’m going to show you how I polluted the student object and ‘paper’ property to get a ‘Passed’ response for all students (If you look at the Index.js code snippet at the start you’ll notice that the student object has two properties: name and paper). If we go back to the code we can see the requirements to get a ‘Passed’ response are that name mustn’t contain Baker or Purvis, and your grade needs to be 10 or more:

StudentHelper.js

 

 

As I showed earlier, if we simply use the name “Test” we will get a response of “Pass:Nope”. This will be the same for any name I send. If I include the paper property in my payload I can get a “Passed” response like this:

This is not beneficial, as if I now send the name “Kenny” without the paper property, I will get a “Nope” response again. So I’m going to pollute the paper property to make the value 10 for all students:

Now that it’s polluted, I can test a new name without including the paper property, and get “Passed” as a response:

Using Prototype Pollution to achieve Remote Code Execution (RCE)

This is where it got really tricky for me. Getting this right took a ton of time and research. It meant looking further into, and getting a better understanding of the DebugHelper code, and everything it does. One block of code stood out for me:

The fork process creates a new Node process (child) and executes a specified module with the given arguments. In this case it is executing the VersionCheck,js file. The command to create a fork looks like this:

child_process.fork(modulePath[, args][, options])

So the code here is giving modulePath as VersionCheck.js, it provides no arguments, and then provides some options: stdio: [‘ignore’, ‘pipe’, ‘pipe’, ‘ipc’]. When looking at the Node documentation for the fork function I noticed that there are a lot more options we can pass to the function. Specifically, I can give it execPath and execArgv:

  • execPath <string> Executable used to create the child process.
  • execArgv <string[]> List of string arguments passed to the executable. Default: process.execArgv

So what if I can pollute all objects, not just a specific object (like I did before with the paper property), and get this child process to execute code? The way to do that seems to be to use constructor.prototype because when you modify the constructor.prototype object, you’re modifying the prototype of the constructor function, which means that any new objects created using that constructor function will inherit those modifications. So the payload to do that would be:

{“constructor”:{“prototype”:{“execPath”:”ls”,”execArgv”:[“-la”,”.”]}}}

This means now that all objects created by the constructor function will contain the execPath and execArgv properties. When the proc object (code snippet above) is created with the options object that includes the stdio property, it inherits the execPath and execArgv properties from the stdio property due to the prototype pollution vulnerability. Therefore, the proc object has these additional properties, which can be used to execute arbitrary commands in the child process.

So once the payload is delivered, all I have to do is visit /debug/version to execute the code and see the results:

Payload and Response
ls -la executed

Now all that’s left to do is read the flag and submit it!

What did I learn?

Phew! This one challenged me! I learned about how JS objects work and I learned about a vulnerability I’d never heard of before. There were points where I felt like my brain was breaking a little because I couldn’t make sense of it all. This took me a week or so of trying and researching in my free time. I still need to learn JS properly so that I can exploit these sorts of challenges faster in the future.

Resources:

Thanks to TCMSecurityAcademy for teaching me about Prototype Pollution in 1 minute

Node Documentation

About the Author

Kevin Cochrane

As a husband, father, and dedicated teacher, I've traversed various professional paths in search of my true passion. Now, I'm embarking on an exciting journey as an aspiring Ethical Hacker, driven by a deep commitment to cybersecurity. With each passing day, I immerse myself in learning, honing my skills, and embracing the challenges of this dynamic field.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may also like these