4T$ CTF Writeup: Homelab ? More like Pwnlab !

4T$ CTF Writeup: Homelab ? More like Pwnlab !

In this Capture The Flag (CTF) challenge, we were given access to two main targets: an SSH server running on a GoTTY shell and a NAS interface that allowed file uploads. My initial analysis revealed two key pieces of information. First, the SSH server had two primary users, one of whom, the admin, had a flag in their home directory that they could only read.

Second, the NAS interface was vulnerable to insecure file uploads, which allowed us to replace existing templates with our files and execute commands through template injection, as the application was running with root privileges.

With the plan to exploit the file upload vulnerability and perform template injection, our first step was to copy /bin/bash to a location accessible through the NAS server, specifically /home/user/nas_storage from the SSH shell and this would have been reachable form /app/public/uploads in NAS, where we could later manipulate it to execute commands with elevated privileges.

We executed the following command to place the binary in the nas_storage directory:

cp /bin/bash /home/user/nas_storage

Since we have the file upload allowing us to write to arbitrary location, I crafted a template with JavaScript code that invoked child_process to execute shell commands. The injected template contained the following payload:

<%=import('child_process').then(c=>{console.log(c.execSync('chown+1000:1000+/app/public/uploads/bash').toString())})%>

We uploaded this template as /app/views/index.ejs, replacing the main template on the NAS interface, and then accessing the / route to trigger its execution. This action successfully executed the chown command, changing the ownership of bash to 1000:1000.

Verifying through SSH, we confirmed that the ownership change was applied as intended.

With ownership transferred to our target user, we proceeded to escalate privileges by setting the SUID bit on the bash binary, enabling it to execute with admin privileges. For this, we created another template injection payload:

<%=import('child_process').then(c=>{console.log(c.execSync('chmod u+s app/public/uploads/bash').toString())})%>

We repeated the upload and execution process, using /app/views/index.ejs to set the SUID bit on our bash binary. Again, we confirmed the change through SSH, finding that the bash was now marked with the SUID bit.

At this point, we had a bash binary capable of executing as admin, which allowed us to retrieve the flag in the admin's directory. By running the following command, we initiated a root shell and accessed files restricted to the admin user:

cd nas_storage/
./bash -p
cat /home/admin/flag

Reference: